diff options
| author | Albrecht Schlosser <albrechts.fltk@online.de> | 2020-07-18 16:17:59 +0200 |
|---|---|---|
| committer | Albrecht Schlosser <albrechts.fltk@online.de> | 2020-07-21 11:11:03 +0200 |
| commit | af7f485e0692dc2eff7da057b6ecd038032ce8ec (patch) | |
| tree | 10ff42e0fa92aba6f5d572ce5d7013ac01298a01 | |
| parent | 11dec24ccf699f79005099fe50762c9147d07901 (diff) | |
Consolidate test/demo for all build systems
This is an attempt to unify the code for all build systems (CMake,
autotools with make), platforms (operating systems) and toolchains
(make + gcc/clang, Visual Studio IDE, Xcode ...) to avoid duplicate
code and clarify the differences for future devs and to simplify
maintenance.
The goal is to minimize the platform specific code.
Much of the new code are comments to describe the different situations.
The main program does now all the "hard work" to construct the paths
necessary to access the other applications and data files.
Use macOS specific code to determine the application path (app_path)
in main() instead of function dobut(), when test apps are activated.
Remove obsolete comments and dead code.
Tested on Windows and Linux with both autotools/configure/make
and CMake with make (Linux + MinGW), Visual Studio.
| -rw-r--r-- | test/CMakeLists.txt | 19 | ||||
| -rw-r--r-- | test/demo.cxx | 480 | ||||
| -rw-r--r-- | test/demo.menu | 2 |
3 files changed, 313 insertions, 188 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f1265978c..b477d6647 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,8 +15,8 @@ # ####################################################################### -set (EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/../bin/examples) -set (TESTFILE_PATH ${EXECUTABLE_OUTPUT_PATH}) +set (EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/../bin) +set (TESTFILE_PATH ${CMAKE_CURRENT_BINARY_DIR}/../data) include (../CMake/FLTK-Functions.cmake) include (../CMake/fl_create_example.cmake) @@ -204,13 +204,11 @@ endif (NOT ANDROID) # We need some support files for the demo programs: -# Note: this is incomplete as of 11 Feb 2015 -# Todo: currently all files are copied, but some of them need configuration: -# - demo.menu: fluid can't be started (wrong path) +# Note: this is incomplete as of July 2020 +# Todo: currently all files are copied, but some of them may need configuration: # - demo.menu: help_dialog (help_dialog.html) can't find its images (not copied) -# - maybe more ... -# create data directory for test and demo programs +# create data directory for test and demo programs if it doesn't exist file (MAKE_DIRECTORY ${TESTFILE_PATH}) # copy the test files @@ -221,13 +219,16 @@ file (COPY DESTINATION ${TESTFILE_PATH} ) +# the main test program 'demo' needs additional hints and configurations + +target_compile_definitions (demo PRIVATE GENERATED_BY_CMAKE) +target_compile_definitions (demo PRIVATE CMAKE_SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") + # Apple macOS creates bundles instead of executables and needs a little bit # more help for demos to run correctly if (APPLE AND (NOT OPTION_APPLE_X11) AND (NOT OPTION_APPLE_SDL)) - target_compile_definitions (demo PUBLIC USING_XCODE) - # make the menu structure part of the app target_sources (demo PRIVATE demo.menu) set_target_properties (demo PROPERTIES MACOSX_BUNDLE TRUE RESOURCE demo.menu) diff --git a/test/demo.cxx b/test/demo.cxx index e7f9445e1..e02b81def 100644 --- a/test/demo.cxx +++ b/test/demo.cxx @@ -14,9 +14,58 @@ // https://www.fltk.org/bugs.php // +/* + General information on directory structure and file handling. + + The "classic" autotools/make system creates executables in their source + folders, i.e. fluid/fluid, test/demo and test/xyz, resp.. The menu file is + in folder test/, as is the main demo(.exe) program. In the following text + and directory lists all test and demo executables are represented by "demo" + and the fluid executable by "fluid", no matter what OS (under Windows: *.exe). + + The CMake build system generates all executables in the build tree and copies + the supporting test data files to the build tree as well. This structure is + different and needs to be handled separately in this program. + + Additionally, different OS platforms create different types of files, for + instance "app bundles" on macOS. All this needs to be considered. + + The overall structure, relative to the FLTK source dir (fltk) and the build + tree (build): + + (1) Autotools / Make: + + fltk/fluid fluid (../fluid/fluid) + fltk/test demo, demo.menu, working directory, data files + fltk/documentation/src images for help_dialog(.html) + + (2) CMake + make (e.g. Unix) + + build/bin fluid, demo + build/data demo.menu, working directory, data files + + (3) CMake + Visual Studio (TYPE == build type: Debug, Release, ...) + + build/bin/TYPE fluid, demo + build/data demo.menu, working directory, data files + + (4) CMake + macOS + Xcode + + *FIXME* special code to handle bundles: do we need this? + + The built executable 'demo' can also be executed with the menu filename + as commandline argument. In this case all the support (data) files are + expected to be in the same directory as the menu file or relative paths + as needed by the test programs, for instance help_dialog which needs + help_dialog.html and related image files. +*/ + +#define DEBUG_VARS (1) // 1 = output variables to stderr, 0 = no + #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <errno.h> #if defined __APPLE__ #include <ApplicationServices/ApplicationServices.h> @@ -29,21 +78,7 @@ #include <FL/Fl_Choice.H> #include <FL/filename.H> #include <FL/platform.H> - -/* Define a macro to decide whether a trailing 'd' needs to be removed - from the executable file name. Previous versions of Visual Studio - added a 'd' to the executable file name ('demod.exe') in Debug - configurations that needed to be removed. - This is no longer true with CMake-generated IDE's since FLTK 1.4. - Just in case we add it again: leave macro DEBUG_EXE_WITH_D defined - and leave the code using this macro as-is. -*/ - -// #if defined(_MSC_VER) && defined(_DEBUG) // Visual Studio in Debug mode -// # define DEBUG_EXE_WITH_D 1 -// #else -# define DEBUG_EXE_WITH_D 0 -// #endif +#include <FL/fl_ask.H> // fl_alert() /* The form description */ @@ -57,6 +92,40 @@ void doscheme(Fl_Choice *c, void *) { Fl_Double_Window *form; Fl_Button *but[9]; +// Allocate space to edit commands and arguments from demo.menu. +// We "trust demo.menu" that strings don't overflow + +char cmdbuf[256]; // commandline w/o arguments +char params[256]; // commandline arguments + +// Global path variables for all platforms and build systems +// to avoid duplication and dynamic allocation + +char src_path [FL_PATH_MAX]; // directory of this souce file +char app_path [FL_PATH_MAX]; // directory of all demo binaries +char fluid_path [FL_PATH_MAX]; // binary directory of fluid +char data_path [FL_PATH_MAX]; // working directory of all demos +char command [2 * FL_PATH_MAX + 40]; // command to be executed + +// platform specific suffix for executable files + +#ifdef _WIN32 +const char *suffix = ".exe"; +#elif defined __APPLE__ +const char *suffix = ".app"; +#else +const char *suffix = ""; +#endif + +// debug output function (to stderr or ...) + +void debug_var(const char *varname, const char *value) { +#if (DEBUG_VARS) + fprintf(stderr, "%-10s = '%s'\n", varname, value); + fflush(stderr); // Windows needs this +#endif // DEBUG_VARS +} + void create_the_forms() { Fl_Widget *obj; form = new Fl_Double_Window(350, 440); @@ -214,190 +283,145 @@ void dobut(Fl_Widget *, long arg) { int men = find_menu(stack[stsize-1]); int n = menus[men].numb; int bn = but2numb( (int) arg, n-1); + + // menu ? + if (menus[men].icommand[bn][0] == '@') { push_menu(menus[men].icommand[bn]); + return; + } + + // not a menu: run test/demo/fluid executable + // find and separate "command" and "params" + + // skip leading spaces in command + char *start_command = menus[men].icommand[bn]; + while (*start_command == ' ') ++start_command; + + strcpy(cmdbuf, start_command); // here still full command w/params + + // find the space between the command and parameters if one exists + char *start_params = strchr(cmdbuf, ' '); + if (start_params) { + *start_params = '\0'; // terminate command + start_params++; // skip space + strcpy(params, start_params); // copy parameters } else { + params[0] = '\0'; // empty string + } -#ifdef _WIN32 + // select application path: either app_path or fluid_path - STARTUPINFO suInfo; // Process startup information - PROCESS_INFORMATION prInfo; // Process information + const char *path = app_path; + if (!strncmp(cmdbuf, "fluid", 5)) + path = fluid_path; -# if DEBUG_EXE_WITH_D - const char *exe = "d.exe"; // exe name with trailing 'd' -# else - const char *exe = ".exe"; // exe name w/o trailing 'd' -# endif + // format commandline with optional parameters - memset(&suInfo, 0, sizeof(suInfo)); - suInfo.cb = sizeof(suInfo); +#if defined(__APPLE__) // macOS - int icommand_length = strlen(menus[men].icommand[bn]); + if (params[0]) { + // we assume that we have only one argument (which is a filename) + sprintf(command, "open '%s/%s%s' --args '%s'", path, cmdbuf, suffix, params); + } else { + sprintf(command, "open '%s/%s%s'", path, cmdbuf, suffix); + } - char* copy_of_icommand = new char[icommand_length+1]; - strcpy(copy_of_icommand,menus[men].icommand[bn]); +#else // other platforms - // On Windows the .exe suffix needs to be appended to the command - // whilst leaving any additional parameters unchanged - this - // is required to handle the correct conversion of cases such as : - // `../fluid/fluid valuators.fl' to '../fluid/fluid.exe valuators.fl'. + if (params[0]) + sprintf(command, "%s/%s%s %s", path, cmdbuf, suffix, params); + else + sprintf(command, "%s/%s%s", path, cmdbuf, suffix); - // skip leading spaces. - char* start_command = copy_of_icommand; - while (*start_command == ' ') ++start_command; +#endif - // find the space between the command and parameters if one exists. - char* start_parameters = strchr(start_command,' '); + // finally, execute program (the system specific part) - char* command = new char[icommand_length+6]; // 6 for extra 'd.exe\0' +#ifdef _WIN32 - if (start_parameters==NULL) { // no parameters required. - sprintf(command, "%s%s", start_command, exe); - } else { // parameters required. - // break the start_command at the intermediate space between - // start_command and start_parameters. - *start_parameters = 0; - // move start_paremeters to skip over the intermediate space. - ++start_parameters; + STARTUPINFO suInfo; // Process startup information + PROCESS_INFORMATION prInfo; // Process information - sprintf(command, "%s%s %s", start_command, exe, start_parameters); - } + memset(&suInfo, 0, sizeof(suInfo)); + suInfo.cb = sizeof(suInfo); - CreateProcess(NULL, command, NULL, NULL, FALSE, - NORMAL_PRIORITY_CLASS, NULL, NULL, &suInfo, &prInfo); + debug_var("Command", command); - delete[] command; - delete[] copy_of_icommand; + BOOL stat = CreateProcess(NULL, command, NULL, NULL, FALSE, + NORMAL_PRIORITY_CLASS, NULL, + NULL, &suInfo, &prInfo); + if (!stat) { + DWORD err = GetLastError(); + fl_alert("Error starting process, error #%d\n'%s'", err, command); + } #elif defined __APPLE__ - /* - Starting with version 1.4.0, FLTK uses CMake as the only supported build - system. On macOS, the app developer is expected to run CMake in a - directory named './build/Xcode' or './build/Makefiles' to generate the - build environment. - - When building FLTK in the next step, teh macOS app bundles are then - stored in either: - './build/Xcode/bin/examples/hello.app/' for Makefiles - './build/Xcode/bin/examples/Debug/hello.app/' for XCode Debug - or - './build/Xcode/bin/examples/Release/hello.app/' as a symbolic - into the Archive system of macOS - - 'Demo' needs to find and run all of these app bundles, some requiring - an additional file name and path for resource files. - - This is my attempt to find the bundles and resources so that Demo.app - and all its dependencies will run without any further configuration. - They will stop running however if any of the bundles or rresources - are moved. - */ - { - char src_path[PATH_MAX]; - char app_path[PATH_MAX]; - char app_name[PATH_MAX]; - char command[2*PATH_MAX+2]; - char *cmd = strdup(menus[men].icommand[bn]); - char *args = strchr(cmd, ' '); - - /* - Get the path to the source code at compile time. This is where the other - resources are located. - */ - strcpy(src_path, __FILE__); - char *src_path_end = (char*)fl_filename_name(src_path); - if (src_path_end) *src_path_end = 0; - - /* - All example app bundles are in the same directory as 'Demo', so set the - current dir to the location of Demo.app . - - Starting with macOS 10.12, the actual location of the app has a randomized - path to fix a vulnerability. This still works in Debug mode which is - */ - { - app_path[0] = 0; - CFBundleRef app = CFBundleGetMainBundle(); - CFURLRef url = CFBundleCopyBundleURL(app); - CFStringRef cc_app_path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); - CFRelease(url); - CFStringGetCString(cc_app_path, app_path, 2048, kCFStringEncodingUTF8); - CFRelease(cc_app_path); - if (app_path[0]) { - char *app_path_end = (char*)fl_filename_name(app_path); - if (app_path_end) *app_path_end = 0; - fl_chdir(app_path); - } - } - - // extract the executable name from the command in the menu file - strcpy(app_name, cmd); - // remove any additioanl command line arguments - if (args) app_name[args-cmd] = 0; - // make the file name into a bundle name - strcat(app_name, ".app"); - - if (args) { - if (strcmp(app_name, "../fluid/fluid.app")==0) { - // CMake -G 'Unix Makefiles' ... : ./bin/fluid.app - // CMake -G 'Xcode' ... : ./bin/Debug/fluid.app or ./bin/Release/fluid.app - // so removing the '/example' path segment from the app_path should - // always work. - char *examples = strstr(app_path, "/examples"); - if (examples) { - memmove(examples, examples+9, strlen(examples+9)+1); - } - sprintf(command, "open '%sfluid.app' --args '%s%s'", app_path, src_path, args+1); - } else { - // we assume that we have only one argument which is a filename, so we add a path - sprintf(command, "open '%s%s' --args '%s%s'", app_path, app_name, src_path, args+1); - } - } else { - sprintf(command, "open '%s%s'", app_path, app_name); - } - system(command); - free(cmd); - } -#else // Non Windows systems. + debug_var("Command", command); + system(command); - int icommand_length = strlen(menus[men].icommand[bn]); - char* command = new char[icommand_length+5]; // 5 for extra './' and ' &\0' - sprintf(command, "./%s &", menus[men].icommand[bn]); - if (system(command)==-1) { /* ignore */ } +#else // other platforms (Unix, Linux) - delete[] command; + strcat(command, " &"); // run in background + debug_var("Command", command); -#endif // _WIN32 + if (system(command) == -1) { + fl_alert("Could not start program, errno = %d\n'%s'", errno, command); } + +#endif // _WIN32 + } void doback(Fl_Widget *, void *) {pop_menu();} void doexit(Fl_Widget *, void *) {exit(0);} -/* Load the menu file. Returns whether successful. */ -int load_the_menu(char* fname) { +/* + Load the menu file. Returns whether successful. + + New strategy: the menu file *should* be usable as is! + + Old strategy was: + + We have different situations: + + (1) Visual Studio: see main(): nothing to do + (2) Standard (autotools/make): nothing to do + (3) CMake: see main(): nothing to do + + (4) macOS: ??? *FIXME* +*/ +int load_the_menu(char *menu) { FILE *fin = 0; char line[256], mname[64],iname[64],cname[64]; int i, j; - fin = fl_fopen(fname,"r"); -#if defined ( __APPLE__ ) + + fin = fl_fopen(menu, "r"); + +// // *FIXME* Albrecht: Do we need this? I don't think so! +#if (0) // *FIXME* disabled for testing + +#if defined (__APPLE__) if (fin == NULL) { - // mac os bundle menu detection: - char* pos = strrchr(fname,'/'); + // macOS bundle menu detection: + char *pos = strrchr(menu, '/'); if (!pos) return 0; *pos = '\0'; - pos = strrchr(fname,'/'); + pos = strrchr(menu, '/'); if (!pos) return 0; - strcpy(pos,"/Resources/demo.menu"); - fin = fl_fopen(fname,"r"); + strcpy(pos, "/Resources/demo.menu"); + fin = fl_fopen(menu, "r"); } -#endif - if (fin == NULL) { +#endif // __APPLE __ + +#endif // *FIXME* disabled for testing + + if (fin == NULL) return 0; - } + for (;;) { if (fgets(line,256,fin) == NULL) break; // remove all carriage returns that Cygwin may have inserted @@ -436,32 +460,132 @@ int load_the_menu(char* fname) { return 1; } +// Fix '\' in Windows paths (convert to '/') and cut off filename (optional, default) +void fix_path(char *path, int strip_filename = 1) { + if (!path[0]) + return; +#ifdef _WIN32 // convert '\' to '/' + char *p = path; + while (*p) { + if (*p == '\\') + *p = '/'; + p++; + } +#endif // _WIN32 + if (strip_filename) { + char *pos = strrchr(path, '/'); + if (pos) + *pos = 0; + } +} + int main(int argc, char **argv) { - fl_putenv("FLTK_DOCDIR=../documentation/html"); - char buf[FL_PATH_MAX]; - strcpy(buf, argv[0]); -#if DEBUG_EXE_WITH_D - // MS_Visual Studio appends a 'd' to debugging executables. Remove it. - fl_filename_setext( buf, "" ); - buf[ strlen(buf)-1 ] = 0; + fl_putenv("FLTK_DOCDIR=../documentation/html"); // *FIXME* + + char menu[FL_PATH_MAX]; + + // construct app_path for all executable files + +#ifdef __APPLE__ + { + // Starting with macOS 10.12, the actual location of the app has a randomized + // path to fix a vulnerability. + // We need some "Apple magic" ;-) to find the actual path. + // Albrecht: is this (still) true and necessary? + + app_path[0] = 0; + CFBundleRef app = CFBundleGetMainBundle(); + CFURLRef url = CFBundleCopyBundleURL(app); + CFStringRef cc_app_path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); + CFRelease(url); + CFStringGetCString(cc_app_path, app_path, 2048, kCFStringEncodingUTF8); + CFRelease(cc_app_path); + } +#else + fl_filename_absolute(app_path, sizeof(app_path), argv[0]); #endif - fl_filename_setext(buf,".menu"); - char *fname = buf; + fix_path(app_path); + + // construct src_path in case we want to use it (macOS ?) + +#if defined(GENERATED_BY_CMAKE) + strcpy(src_path, CMAKE_SOURCE_PATH); +#else + strcpy(src_path, app_path); +#endif + fix_path(src_path, 0); + + // fluid's path is the same for CMake builds but not for autoconf/make + + strcpy(fluid_path, app_path); + +#if !defined(GENERATED_BY_CMAKE) + fix_path(fluid_path); // removes folder name (test) + strcat(fluid_path, "/fluid"); +#endif + + // construct data_path for the menu file and all resources (data files) + // CMake: replace "/bin/*"" with "/data" + // autotools: use app_path directly + + strcpy(data_path, app_path); + +#if defined(GENERATED_BY_CMAKE) + { + char *pos = strstr(data_path, "/bin"); + if (pos) + strcpy(pos, "/data"); + } +#endif + + // construct the menu file name, optionally overridden by command args + // CMake: use data_path instead of app_path + + const char *fn = fl_filename_name(argv[0]); + +#if defined(GENERATED_BY_CMAKE) + strcpy(menu, data_path); +#else + strcpy(menu, app_path); +#endif + + // append "/<exe-file-name>.menu" + strcat(menu, "/"); + strcat(menu, fn); + fl_filename_setext(menu, sizeof(menu), ".menu"); + + // parse commandline + int i = 0; if (!Fl::args(argc,argv,i) || i < argc-1) - Fl::fatal("Usage: %s <switches> <menufile>\n%s",argv[0],Fl::help); - if (i < argc) fname = argv[i]; + Fl::fatal("Usage: %s <switches> <menufile>\n%s", argv[0], Fl::help); + if (i < argc) { + // override menu file *and* data path ! + fl_filename_absolute(menu, sizeof(menu), (const char *)argv[i]); + strcpy(data_path, menu); + fix_path(data_path); + } + +#if (DEBUG_VARS) + fprintf(stderr, "\n"); + debug_var("src_path", src_path); + debug_var("app_path", app_path); + debug_var("fluid_path", fluid_path); + debug_var("data_path", data_path); + debug_var("Menu file", menu); + fprintf(stderr, "\n"); +#endif // DEBUG_VARS create_the_forms(); - if (!load_the_menu(fname)) Fl::fatal("Can't open %s",fname); - if (buf != fname) - strcpy(buf,fname); - const char *c = fl_filename_name(buf); - if (c > buf) { - buf[c-buf] = 0; - if (fl_chdir(buf) == -1) { /* ignore */ } - } + // note: load_the_menu() *may* change the `menu` buffer contents ! + if (!load_the_menu(menu)) + Fl::fatal("Can't open %s", menu); + + // set current work directory to 'app_path' + + if (fl_chdir(data_path) == -1) { /* ignore */ } + push_menu("@main"); form->show(argc,argv); Fl::run(); diff --git a/test/demo.menu b/test/demo.menu index 2eedfb772..8b325cca6 100644 --- a/test/demo.menu +++ b/test/demo.menu @@ -58,7 +58,7 @@ @u:fast && slow widgets:fast_slow @u:inactive:inactive -@main:Fluid\n(UI design tool):../fluid/fluid valuators.fl +@main:Fluid\n(UI design tool):fluid valuators.fl @main:Cool\nDemos...:@e @e:X Color\nBrowser:colbrowser |
