diff --git a/CMakeLists.txt b/CMakeLists.txt index c858d4c..970e403 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,9 @@ if(NOT WIN32 AND NOT APPLE) set(GUI gtk2 CACHE STRING "GUI toolkit to use (one of: gtk2 gtk3)") endif() +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + if(NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID) message(FATAL_ERROR "C and C++ compilers should be supplied by the same vendor") endif() diff --git a/README.md b/README.md index 40fffc5..fa8a96e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ After that, build SolveSpace as following: make sudo make install -The application is built as `build/src/solvespace`. +The application is built as `build/bin/solvespace`. A fully functional port to GTK3 is available, but not recommended for use due to bugs in this toolkit. @@ -77,7 +77,7 @@ Or, build 64-bit SolveSpace as following: -DENABLE_TESTS=OFF make -The application is built as `build/src/solvespace.exe`. +The application is built as `build/bin/solvespace.exe`. Space Navigator support will not be available. @@ -103,8 +103,8 @@ After that, build SolveSpace as following: cmake .. -DENABLE_TESTS=OFF make -The application is built in `build/src/solvespace.app`, and -the executable file is `build/src/solvespace.app/Contents/MacOS/solvespace`. +The application is built in `build/bin/solvespace.app`, and +the executable file is `build/bin/solvespace.app/Contents/MacOS/solvespace`. [homebrew]: http://brew.sh/ diff --git a/appveyor.yml b/appveyor.yml index 8d83994..5533b17 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,15 +11,11 @@ build_script: - msbuild "src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - msbuild "test\solvespace_testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test_script: - - test\%BUILD_TYPE%\solvespace_testsuite.exe + - bin\%BUILD_TYPE%\solvespace_testsuite.exe artifacts: - - path: build\src\Debug\solvespace.exe + - path: build\bin\%BUILD_TYPE%\solvespace.exe name: solvespace.exe - - path: build\src\Debug\solvespace.pdb - name: solvespace.pdb - - path: build\src\RelWithDebInfo\solvespace.exe - name: solvespace.exe - - path: build\src\RelWithDebInfo\solvespace.pdb + - path: build\bin\%BUILD_TYPE%\solvespace.pdb name: solvespace.pdb deploy: # Releases to solvespace/solvespace diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index 9531820..18e5736 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -6,7 +6,11 @@ foreach(pkg_config_lib CAIRO) endforeach() add_executable(solvespace_benchmark - harness.cpp) + harness.cpp + $) target_link_libraries(solvespace_benchmark solvespace_headless) + +add_dependencies(solvespace_benchmark + resources) diff --git a/bench/harness.cpp b/bench/harness.cpp index bdae469..cb2367c 100644 --- a/bench/harness.cpp +++ b/bench/harness.cpp @@ -4,55 +4,6 @@ // Copyright 2016 whitequark //----------------------------------------------------------------------------- #include "solvespace.h" -#if defined(WIN32) -#include -#else -#include -#endif - -namespace SolveSpace { - // These are defined in headless.cpp, and aren't exposed in solvespace.h. - extern std::string resourceDir; -} - -static std::string ResourceRoot() { - static std::string rootDir; - if(!rootDir.empty()) return rootDir; - - // No especially good way to do this, so let's assume somewhere up from - // the current directory there's our repository, with CMakeLists.txt, and - // pivot from there. -#if defined(WIN32) - wchar_t currentDirW[MAX_PATH]; - GetCurrentDirectoryW(MAX_PATH, currentDirW); - rootDir = Narrow(currentDirW); -#else - rootDir = "."; -#endif - - // We're never more than four levels deep. - for(size_t i = 0; i < 4; i++) { - std::string listsPath = rootDir; - listsPath += PATH_SEP; - listsPath += "CMakeLists.txt"; - FILE *f = ssfopen(listsPath, "r"); - if(f) { - fclose(f); - rootDir += PATH_SEP; - rootDir += "res"; - return rootDir; - } - - if(rootDir[0] == '.') { - rootDir += PATH_SEP; - rootDir += ".."; - } else { - rootDir.erase(rootDir.rfind(PATH_SEP)); - } - } - - ssassert(false, "Couldn't locate repository root"); -} static bool RunBenchmark(std::function setupFn, std::function benchFn, @@ -90,14 +41,7 @@ static bool RunBenchmark(std::function setupFn, } int main(int argc, char **argv) { -#if defined(_MSC_VER) - _set_abort_behavior(0, _WRITE_ABORT_MSG); -#endif -#if defined(WIN32) - InitHeaps(); -#endif - - resourceDir = ResourceRoot(); + InitPlatform(); std::string mode, filename; if(argc == 3) { diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index c87a492..216b427 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -30,18 +30,23 @@ if(WIN32) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source}") endfunction() elseif(APPLE) - set(app_resource_dir ${CMAKE_BINARY_DIR}/src/solvespace.app/Contents/Resources) + set(app_resource_dir ${CMAKE_BINARY_DIR}/bin/solvespace.app/Contents/Resources) + set(cli_resource_dir ${CMAKE_BINARY_DIR}/res) function(add_resource name) set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) - set(target ${app_resource_dir}/${name}) - set(resource_list "${resource_list};${target}" PARENT_SCOPE) + set(target_app ${app_resource_dir}/${name}) + set(target_cli ${cli_resource_dir}/${name}) + set(resource_list "${resource_list};${target_app};${target_cli}" PARENT_SCOPE) - get_filename_component(target_dir ${target} DIRECTORY) + get_filename_component(target_app_dir ${target_app} DIRECTORY) + get_filename_component(target_cli_dir ${target_cli} DIRECTORY) add_custom_command( - OUTPUT ${target} - COMMAND ${CMAKE_COMMAND} -E make_directory ${target_dir} - COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target} + OUTPUT ${target_app} ${target_cli} + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_app_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target_app} + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_cli_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target_cli} COMMENT "Copying resource ${name}" DEPENDS ${source} VERBATIM) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7880c3b..91cfe79 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,11 @@ else() platform/unixutil.cpp) endif() +if(APPLE) + set(util_LIBRARIES + ${APPKIT_LIBRARY}) +endif() + # libslvs set(libslvs_SOURCES @@ -45,6 +50,9 @@ target_compile_definitions(slvs target_include_directories(slvs PUBLIC ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(slvs + ${util_LIBRARIES}) + set_target_properties(slvs PROPERTIES PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h VERSION ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} @@ -101,9 +109,6 @@ elseif(APPLE) set(platform_BUNDLED_LIBS ${PNG_LIBRARIES} ${FREETYPE_LIBRARIES}) - - set(platform_LIBRARIES - ${APPKIT_LIBRARY}) elseif(HAVE_GTK2 OR HAVE_GTK3) set(platform_SOURCES platform/gtkmain.cpp @@ -192,6 +197,7 @@ add_library(solvespace_cad STATIC target_link_libraries(solvespace_cad dxfrw + ${util_LIBRARIES} ${ZLIB_LIBRARY} ${PNG_LIBRARY} ${FREETYPE_LIBRARY} diff --git a/src/lib.cpp b/src/lib.cpp index 8a4bdf0..d9abe3c 100644 --- a/src/lib.cpp +++ b/src/lib.cpp @@ -81,7 +81,7 @@ void Slvs_MakeQuaternion(double ux, double uy, double uz, void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg) { if(!IsInit) { - InitHeaps(); + InitPlatform(); IsInit = 1; } diff --git a/src/platform/cocoamain.mm b/src/platform/cocoamain.mm index 9b87514..7c17401 100644 --- a/src/platform/cocoamain.mm +++ b/src/platform/cocoamain.mm @@ -7,11 +7,7 @@ //----------------------------------------------------------------------------- #include #include - -#import - -#include -#include +#import #include "solvespace.h" @@ -1138,26 +1134,6 @@ std::vector SolveSpace::GetFontFiles() { return fonts; } -const void *SolveSpace::LoadResource(const std::string &name, size_t *size) { - static NSMutableDictionary *cache; - - if(cache == nil) { - cache = [[NSMutableDictionary alloc] init]; - } - - NSString *key = [NSString stringWithUTF8String:name.c_str()]; - NSData *data = [cache objectForKey:key]; - if(data == nil) { - NSString *path = [[NSBundle mainBundle] pathForResource:key ofType:nil]; - data = [[NSFileHandle fileHandleForReadingAtPath:path] readDataToEndOfFile]; - ssassert(data != nil, "Cannot find resource"); - [cache setObject:data forKey:key]; - } - - *size = [data length]; - return [data bytes]; -} - /* Application lifecycle */ @interface ApplicationDelegate : NSObject diff --git a/src/platform/gtkmain.cpp b/src/platform/gtkmain.cpp index 60e210a..473030b 100644 --- a/src/platform/gtkmain.cpp +++ b/src/platform/gtkmain.cpp @@ -1482,73 +1482,6 @@ std::vector GetFontFiles() { return fonts; } -std::string ExpandPath(std::string path) { - char *expanded_c_path = realpath(path.c_str(), NULL); - if(expanded_c_path == NULL) { - fprintf(stderr, "realpath(%s): %s\n", path.c_str(), strerror(errno)); - return ""; - } - std::string expanded_path = expanded_c_path; - free(expanded_c_path); - return expanded_path; -} - -static std::string resource_dir; -void FindLocalResourceDir(const char *argv0) { - // Getting path to your own executable is a total portability disaster. - // Good job *nix OSes; you're basically all awful here. - std::string self_path; -#if defined(__linux__) - self_path = "/proc/self/exe"; -#elif defined(__NetBSD__) - self_path = "/proc/curproc/exe" -#elif defined(__OpenBSD__) - self_path = "/proc/curproc/file"; -#else - self_path = argv0; -#endif - - resource_dir = ExpandPath(self_path); - if(resource_dir.empty()) { - fprintf(stderr, "Cannot determine path to executable; using global resources.\n"); - return; - } - resource_dir.erase(resource_dir.rfind('/')); - resource_dir += "/../res"; - resource_dir = ExpandPath(resource_dir); -} - -const void *LoadResource(const std::string &name, size_t *size) { - static std::map> cache; - - auto it = cache.find(name); - if(it == cache.end()) { - struct stat st; - std::string path; - - if(resource_dir.empty()) { - path = (UNIX_DATADIR "/") + name; - } else { - path = resource_dir + "/" + name; - } - - if(stat(path.c_str(), &st)) { - ssassert(!stat(path.c_str(), &st), "Cannot find resource"); - } - - std::vector data(st.st_size); - FILE *f = ssfopen(path.c_str(), "rb"); - ssassert(f != NULL, "Cannot open resource"); - fread(&data[0], 1, st.st_size, f); - fclose(f); - - cache.emplace(name, std::move(data)); - it = cache.find(name); - } - - *size = (*it).second.size(); - return &(*it).second[0]; -} /* Space Navigator support */ @@ -1612,9 +1545,6 @@ int main(int argc, char** argv) { ambiguous. */ gtk_disable_setlocale(); - /* If we're running from the build directory, grab the local resources. */ - FindLocalResourceDir(argv[0]); - Gtk::Main main(argc, argv); #ifdef HAVE_SPACEWARE diff --git a/src/platform/headless.cpp b/src/platform/headless.cpp index 7eefa5d..9be35d0 100644 --- a/src/platform/headless.cpp +++ b/src/platform/headless.cpp @@ -251,31 +251,6 @@ std::vector GetFontFiles() { return fontFiles; } -std::string resourceDir; -const void *LoadResource(const std::string &name, size_t *size) { - static std::map> cache; - - auto it = cache.find(name); - if(it == cache.end()) { - std::string path = resourceDir + "/" + name; - std::vector data; - - FILE *f = ssfopen(PathSepUnixToPlatform(path).c_str(), "rb"); - ssassert(f != NULL, "Cannot open resource"); - fseek(f, 0, SEEK_END); - data.resize(ftell(f)); - fseek(f, 0, SEEK_SET); - fread(&data[0], 1, data.size(), f); - fclose(f); - - cache.emplace(name, std::move(data)); - it = cache.find(name); - } - - *size = (*it).second.size(); - return &(*it).second[0]; -} - //----------------------------------------------------------------------------- // Application lifecycle //----------------------------------------------------------------------------- diff --git a/src/platform/unixutil.cpp b/src/platform/unixutil.cpp index f076cc9..9594bf0 100644 --- a/src/platform/unixutil.cpp +++ b/src/platform/unixutil.cpp @@ -7,13 +7,18 @@ // Copyright 2008-2013 Jonathan Westhues. // Copyright 2013 Daniel Richard G. //----------------------------------------------------------------------------- -#include +#include +#include #include #ifdef __APPLE__ # include // for strcasecmp +# include +# include +# include #endif #include "solvespace.h" +#include "config.h" namespace SolveSpace { @@ -89,6 +94,104 @@ void ssremove(const std::string &filename) remove(filename.c_str()); } +static std::string ExpandPath(std::string path) { + char *expanded_c_path = realpath(path.c_str(), NULL); + if(expanded_c_path == NULL) { + fprintf(stderr, "realpath(%s): %s\n", path.c_str(), strerror(errno)); + return ""; + } + std::string expanded_path = expanded_c_path; + free(expanded_c_path); + return expanded_path; +} + +static const std::string &FindLocalResourceDir() { + static std::string resourceDir; + static bool checked; + + if(checked) return resourceDir; + checked = true; + + // Getting path to your own executable is a total portability disaster. + // Good job *nix OSes; you're basically all awful here. + std::string selfPath; +#if defined(__linux__) + selfPath = "/proc/self/exe"; +#elif defined(__NetBSD__) + selfPath = "/proc/curproc/exe" +#elif defined(__OpenBSD__) + selfPath = "/proc/curproc/file"; +#elif defined(__APPLE__) + CFURLRef cfUrl = + CFBundleCopyExecutableURL(CFBundleGetMainBundle()); + CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle); + selfPath.resize(CFStringGetLength(cfPath) + 1); // reserve space for NUL + ssassert(CFStringGetCString(cfPath, &selfPath[0], selfPath.size(), kCFStringEncodingUTF8), + "Cannot convert CFString to C string"); + selfPath.resize(selfPath.size() - 1); + CFRelease(cfUrl); + CFRelease(cfPath); +#else + // We don't know how to find the local resource directory on this platform, + // so use the global one (by returning an empty string). + return resourceDir; +#endif + + resourceDir = ExpandPath(selfPath); + if(!resourceDir.empty()) { + resourceDir.erase(resourceDir.rfind('/')); + resourceDir += "/../res"; + resourceDir = ExpandPath(resourceDir); + } + if(!resourceDir.empty()) { + struct stat st; + if(stat(resourceDir.c_str(), &st)) { + // We looked at the path where the local resource directory ought to be, + // but there isn't one, so use the global one. + resourceDir = ""; + } + } + return resourceDir; +} + +const void *LoadResource(const std::string &name, size_t *size) { + static std::map cache; + + auto it = cache.find(name); + if(it == cache.end()) { + const std::string &resourceDir = FindLocalResourceDir(); + + std::string path; + if(resourceDir.empty()) { +#if defined(__APPLE__) + CFStringRef cfName = + CFStringCreateWithCString(kCFAllocatorDefault, name.c_str(), + kCFStringEncodingUTF8); + CFURLRef cfUrl = + CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfName, NULL, NULL); + CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle); + path.resize(CFStringGetLength(cfPath) + 1); // reserve space for NUL + ssassert(CFStringGetCString(cfPath, &path[0], path.size(), kCFStringEncodingUTF8), + "Cannot convert CFString to C string"); + path.resize(path.size() - 1); + CFRelease(cfName); + CFRelease(cfUrl); + CFRelease(cfPath); +#else + path = (UNIX_DATADIR "/") + name; +#endif + } else { + path = resourceDir + "/" + name; + } + + ssassert(ReadFile(path, &cache[name]), "Cannot read resource"); + it = cache.find(name); + } + + *size = (*it).second.size(); + return static_cast(&(*it).second[0]); +} + //----------------------------------------------------------------------------- // A separate heap, on which we allocate expressions. Maybe a bit faster, // since fragmentation is less of a concern, and it also makes it possible @@ -150,7 +253,7 @@ void MemFree(void *p) { free(p); } -void InitHeaps(void) { +void InitPlatform(void) { /* nothing to do */ } diff --git a/src/platform/w32main.cpp b/src/platform/w32main.cpp index d0837be..7ba6d2a 100644 --- a/src/platform/w32main.cpp +++ b/src/platform/w32main.cpp @@ -1397,16 +1397,6 @@ static void CreateMainWindows() ClientIsSmallerBy = (r.bottom - r.top) - (rc.bottom - rc.top); } -const void *SolveSpace::LoadResource(const std::string &name, size_t *size) { - HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); - ssassert(hres != NULL, "Cannot find resource"); - HGLOBAL res = LoadResource(NULL, hres); - ssassert(res != NULL, "Cannot load resource"); - - *size = SizeofResource(NULL, hres); - return LockResource(res); -} - #ifdef HAVE_SPACEWARE //----------------------------------------------------------------------------- // Test if a message comes from the SpaceNavigator device. If yes, dispatch @@ -1474,7 +1464,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, ShowWindow(GraphicsWnd, SW_SHOW); // Create the heaps for all dynamic memory (AllocTemporary, MemAlloc) - InitHeaps(); + InitPlatform(); // Pull out the Unicode command line. int argc; diff --git a/src/platform/w32util.cpp b/src/platform/w32util.cpp index 29357e5..99ad123 100644 --- a/src/platform/w32util.cpp +++ b/src/platform/w32util.cpp @@ -144,6 +144,16 @@ void ssremove(const std::string &filename) _wremove(Widen(filename).c_str()); } +const void *LoadResource(const std::string &name, size_t *size) { + HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); + ssassert(hres != NULL, "Cannot find resource"); + HGLOBAL res = ::LoadResource(NULL, hres); + ssassert(res != NULL, "Cannot load resource"); + + *size = SizeofResource(NULL, hres); + return LockResource(res); +} + //----------------------------------------------------------------------------- // A separate heap, on which we allocate expressions. Maybe a bit faster, // since no fragmentation issues whatsoever, and it also makes it possible @@ -182,10 +192,17 @@ void vl() { ssassert(HeapValidate(PermHeap, HEAP_NO_SERIALIZE, NULL), "Corrupted heap"); } -void InitHeaps() { +void InitPlatform() { // Create the heap used for long-lived stuff (that gets freed piecewise). PermHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0); // Create the heap that we use to store Exprs and other temp stuff. FreeAllTemporary(); + +#if !defined(LIBRARY) && defined(_MSC_VER) + // Don't display the abort message; it is aggravating in CLI binaries + // and results in infinite WndProc recursion in GUI binaries. + _set_abort_behavior(0, _WRITE_ABORT_MSG); +#endif } + } diff --git a/src/solvespace.h b/src/solvespace.h index faa9bd8..17ae3e0 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -294,7 +294,7 @@ void FreeTemporary(void *p); void FreeAllTemporary(); void *MemAlloc(size_t n); void MemFree(void *p); -void InitHeaps(); +void InitPlatform(); void vl(); // debug function to validate heaps #include "resource.h" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 081477a..fa18451 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,12 +58,16 @@ set(testsuite_SOURCES ) add_executable(solvespace_testsuite - ${testsuite_SOURCES}) + ${testsuite_SOURCES} + $) target_link_libraries(solvespace_testsuite solvespace_headless ${COVERAGE_LIBRARY}) +add_dependencies(solvespace_testsuite + resources) + add_custom_target(solvespace-test ALL COMMAND $ COMMENT "Testing SolveSpace" diff --git a/test/harness.cpp b/test/harness.cpp index 431a446..6cfa996 100644 --- a/test/harness.cpp +++ b/test/harness.cpp @@ -3,18 +3,19 @@ // // Copyright 2016 whitequark //----------------------------------------------------------------------------- -#include "harness.h" #include #include + +#include "harness.h" + #if defined(WIN32) -#include +# include #else -#include +# include #endif namespace SolveSpace { // These are defined in headless.cpp, and aren't exposed in solvespace.h. - extern std::string resourceDir; extern std::vector fontFiles; extern bool antialias; extern std::shared_ptr framebuffer; @@ -291,12 +292,7 @@ int Test::Case::Register(Test::Case testCase) { } int main(int argc, char **argv) { -#if defined(_MSC_VER) - _set_abort_behavior(0, _WRITE_ABORT_MSG); -#endif -#if defined(WIN32) - InitHeaps(); -#endif + InitPlatform(); std::regex filter(".*"); if(argc == 1) { @@ -307,10 +303,6 @@ int main(int argc, char **argv) { return 1; } - resourceDir = HostRoot(); - resourceDir.erase(resourceDir.rfind(HOST_PATH_SEP) + 1); - resourceDir += "res"; - fontFiles.push_back(HostRoot() + HOST_PATH_SEP + "Gentium-R.ttf"); // Different Cairo versions have different antialiasing algorithms.