diff --git a/commandLine/src/defines.h b/commandLine/src/defines.h index 42340f3c..38e25556 100644 --- a/commandLine/src/defines.h +++ b/commandLine/src/defines.h @@ -1,5 +1,5 @@ #define OFPROJECTGENERATOR_MAJOR_VERSION "0" -#define OFPROJECTGENERATOR_MINOR_VERSION "122" +#define OFPROJECTGENERATOR_MINOR_VERSION "124" #define OFPROJECTGENERATOR_PATCH_VERSION "0" #define PG_VERSION (OFPROJECTGENERATOR_MAJOR_VERSION "." OFPROJECTGENERATOR_MINOR_VERSION "." OFPROJECTGENERATOR_PATCH_VERSION) diff --git a/commandLine/src/main.cpp b/commandLine/src/main.cpp index fe74e36b..38617ed2 100644 --- a/commandLine/src/main.cpp +++ b/commandLine/src/main.cpp @@ -100,6 +100,7 @@ fs::path generatorPath; fs::path ofPath; vector addons; vector srcPaths; +bool bSrcPathsPassedIn = false; // false = keep the project's config.make PROJECT_EXTERNAL_SOURCE_PATHS vector targets; vector frameworks; bool bFrameworksPassedIn = false; @@ -306,6 +307,7 @@ void updateProject(const fs::path & path, const string & target, bool bConsiderP project->addProjectAfterRule(postBuildScript); } + if (!bSrcPathsPassedIn) project->addSavedSrcPaths(); for (auto & srcPath : srcPaths) { project->addSrcRecursively(srcPath); } @@ -478,20 +480,22 @@ int pgMain(int argc, char ** argv) { argv += (argc > 0); // skip program name argv[0] if present // optionparser only takes attached values (-posx, --platforms=osx); join -p osx / --platforms osx, - // but leave the last argument as the project path (-f is an empty -f) + // but leave the last argument as the project path: -t"" (scripts, the GUI) is an empty -t static const std::string valueShort { "paotsmfDPkW" }; static const std::vector valueLong { "platforms", "addons", "ofPath", "template", "source", "ofmenu", "frameworks", "system-libs", "defines", "postbuild", "msys2env", "emscripten-wasm", "app-version", "app-build", "bundle-id" }; std::vector joinedArgs; + std::string platformBeforePath; for (int i = 0; i < argc; i++) { std::string a { argv[i] }; bool isValueOption = (a.size() == 2 && a[0] == '-' && valueShort.find(a[1]) != std::string::npos) || (a.rfind("--", 0) == 0 && std::find(valueLong.begin(), valueLong.end(), a.substr(2)) != valueLong.end()); - // only -f / --frameworks / --system-libs mean something empty (remove), so -f keeps the path - bool emptyMeansRemove = a == "-f" || a == "--frameworks" || a == "--system-libs"; - if (isValueOption && i + 1 < argc && argv[i + 1][0] != '-' && (i + 2 < argc || !emptyMeansRemove)) { + bool keyword = a == "-m" || a == "--ofmenu"; // status / update-libs, no project path after it + if (isValueOption && i + 1 < argc && argv[i + 1][0] != '-' && (i + 2 < argc || keyword)) { joinedArgs.emplace_back(a + (a.size() == 2 ? "" : "=") + argv[i + 1]); i++; } else { + // -p osx with no project path: osx would be taken as the project + if ((a == "-p" || a == "--platforms") && i + 2 == argc && argv[i + 1][0] != '-') platformBeforePath = argv[i + 1]; joinedArgs.emplace_back(a); } } @@ -650,6 +654,7 @@ int pgMain(int argc, char ** argv) { // Additional Src Paths if (options[SRCEXTERNAL].count() > 0) { + bSrcPathsPassedIn = true; // could be empty if (options[SRCEXTERNAL].arg != NULL) { string srcString(options[SRCEXTERNAL].arg); // TODO: TEST @@ -758,8 +763,20 @@ int pgMain(int argc, char ** argv) { messageError("Missing project path"); return EXIT_USAGE; } + if (!platformBeforePath.empty() && projectName == platformBeforePath && getTargetProject(platformBeforePath) && !fs::exists(fs::current_path() / projectName)) { + printHelp(); + consoleSpace(); + messageError("Missing project path (\"" + projectName + "\" after -p is the platform) - eg -p" + projectName + " path/to/myApp"); + return EXIT_USAGE; + } fs::path projectPath = normalizePath(fs::weakly_canonical(fs::current_path() / projectName)); + // -s relative to here, unless it's relative to the project (config.make, the GUI) + for (auto & s : srcPaths) { + if (s.is_relative() && !fs::exists(projectPath / s) && fs::exists(fs::current_path() / s)) { + s = normalizePath(fs::weakly_canonical(fs::current_path() / s)); + } + } fs::path projectNamePath = projectPath.filename(); projectName = projectNamePath.string(); @@ -898,6 +915,7 @@ int pgMain(int argc, char ** argv) { project->addProjectAfterRule(postBuildScript); } + if (!bSrcPathsPassedIn) project->addSavedSrcPaths(); for (auto & s : srcPaths) { project->addSrcRecursively(s); } diff --git a/commandLine/src/projects/baseProject.cpp b/commandLine/src/projects/baseProject.cpp index 4cd33645..47544693 100644 --- a/commandLine/src/projects/baseProject.cpp +++ b/commandLine/src/projects/baseProject.cpp @@ -350,6 +350,7 @@ bool baseProject::create(const fs::path & _path, string templateName){ ofLogNotice() << "{ \"emscriptenWasm\": \"" << emscriptenWasm << "\" }"; } + savedExtSrcPaths = readMakeList(projectDir / "config.make", "PROJECT_EXTERNAL_SOURCE_PATHS"); systemFrameworks.clear(); systemLibs.clear(); for (auto & f : systemFrameworksPassed ? systemFrameworksOption : readMakeList(projectDir / "config.make", "PROJECT_FRAMEWORKS")) { @@ -563,6 +564,7 @@ bool baseProject::save(){ bool bWroteFrameworks = false; bool bWroteSystemLibs = false; bool bWroteBundleId = false; + bool bWroteExtSrcPaths = false; auto isMakeVar = [](const std::string & line, const std::string & name) { auto config = ofTrim(line); if (config.rfind("#", 0) == 0) config = ofTrim(config.substr(1)); @@ -594,12 +596,14 @@ bool baseProject::save(){ } saveConfig << "OF_ROOT = " << path.generic_string() << std::endl; } - // replace this section with our external paths - else if( extSrcPaths.size() && str.rfind("# PROJECT_EXTERNAL_SOURCE_PATHS =", 0) == 0 ){ - for(std::size_t d = 0; d < extSrcPaths.size(); d++){ - ofLog(OF_LOG_VERBOSE) << " adding PROJECT_EXTERNAL_SOURCE_PATHS to config" << extSrcPaths[d].generic_string() << std::endl; - saveConfig << "PROJECT_EXTERNAL_SOURCE_PATHS" << (d == 0 ? " = " : " += ") << extSrcPaths[d].generic_string() << std::endl; + // external source paths, relative to the project like the makefiles read them; replaces an old value too + else if( isMakeVar(str, "PROJECT_EXTERNAL_SOURCE_PATHS") ){ + std::vector paths; + for (auto & p : extSrcPaths) { + fs::path rel = p.is_absolute() ? fs::path(p).lexically_relative(fs::current_path()) : p; + paths.emplace_back((rel.empty() ? p : rel).generic_string()); } + writeMakeList(str, "PROJECT_EXTERNAL_SOURCE_PATHS", paths, bWroteExtSrcPaths); } // make builds (emscripten, linux, msys2) only see defines through config.make else if( projectDefines.size() && (str.rfind("# PROJECT_DEFINES =", 0) == 0 || str.rfind("PROJECT_DEFINES =", 0) == 0) ){ @@ -655,6 +659,14 @@ bool baseProject::save(){ if (!bWroteAppVersion) saveConfig << "APP_VERSION = " << appVersion << std::endl; if (!bWroteAppBuild) saveConfig << "APP_BUILD = " << appBuild << std::endl; } + if (extSrcPaths.size() && !bWroteExtSrcPaths) { + if (lines.size()) saveConfig << std::endl; + saveConfig << "# external source folders, relative to the project" << std::endl; + for (std::size_t d = 0; d < extSrcPaths.size(); d++) { + fs::path rel = extSrcPaths[d].is_absolute() ? extSrcPaths[d].lexically_relative(fs::current_path()) : extSrcPaths[d]; + saveConfig << "PROJECT_EXTERNAL_SOURCE_PATHS" << (d == 0 ? " = " : " += ") << (rel.empty() ? extSrcPaths[d] : rel).generic_string() << std::endl; + } + } if ((systemFrameworks.size() && !bWroteFrameworks) || (systemLibs.size() && !bWroteSystemLibs)) { if (lines.size()) saveConfig << std::endl; saveConfig << "# system frameworks (Security) and libraries (sqlite3 = libsqlite3.tbd / -lsqlite3)" << std::endl; @@ -1202,6 +1214,17 @@ void baseProject::setSystemFrameworks(const std::vector & framework systemLibsPassed = libsPassed; } +void baseProject::addSavedSrcPaths() { + // the project folder is the working directory here, like config.make's relative paths + for (auto & p : savedExtSrcPaths) { + if (fs::exists(p)) { + addSrcRecursively(p); + } else { + ofLogWarning() << "config.make PROJECT_EXTERNAL_SOURCE_PATHS " << p << " doesn't exist - skipped"; + } + } +} + void baseProject::addSystemFrameworks() { for (auto & f : systemFrameworks) { addFramework(f, "Frameworks", true); diff --git a/commandLine/src/projects/baseProject.h b/commandLine/src/projects/baseProject.h index ec7206c4..ef087ac7 100644 --- a/commandLine/src/projects/baseProject.h +++ b/commandLine/src/projects/baseProject.h @@ -103,6 +103,8 @@ class baseProject { // system frameworks / libraries (-f / --system-libs), kept in config.make PROJECT_FRAMEWORKS / PROJECT_SYSTEM_LIBS void setSystemFrameworks(const std::vector & frameworks, bool frameworksPassed, const std::vector & libs, bool libsPassed); void addSystemFrameworks(); + // config.make PROJECT_EXTERNAL_SOURCE_PATHS, when no -s was given + void addSavedSrcPaths(); // public entry point for project-level preprocessor defines (e.g. -D/--defines on the // CLI, or a GUI-driven renderer choice) - addDefine() itself stays protected since it's @@ -191,6 +193,7 @@ class baseProject { std::vector addons; std::vector extSrcPaths; + std::vector savedExtSrcPaths; // config.make PROJECT_EXTERNAL_SOURCE_PATHS, read before the template copies std::vector projectDefines; // also written to config.make PROJECT_DEFINES for make builds std::vector missingAddons; // not found, still written to addons.make (#258) std::vector systemFrameworks, systemLibs; // value for this project diff --git a/frontend/index.js b/frontend/index.js index dddda484..c8add537 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -1885,9 +1885,8 @@ function generateFunction(event, generate) { args.push(`-p${platformList.join(",")}`); } - if (sourcePath != null && sourcePath.length > 0) { - args.push(`-s${sourcePath}`); - } + // always: an empty -s removes the project's external source folders (the field shows config.make's) + args.push(`-s${sourcePath || ''}`); if (templateList != null) { args.push(`-t${templateList.join(",")}`); diff --git a/frontend/npm-shrinkwrap.json b/frontend/npm-shrinkwrap.json index 876124a0..aadafd3f 100644 --- a/frontend/npm-shrinkwrap.json +++ b/frontend/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "projectGenerator", - "version": "0.117.0", + "version": "0.119.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "projectGenerator", - "version": "0.117.0", + "version": "0.119.0", "license": "MIT", "dependencies": { "@types/jquery": "^3.5.34", diff --git a/frontend/package.json b/frontend/package.json index be42d0c4..9c940ef4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "projectGenerator", "productName": "projectGenerator", - "version": "0.117.0", + "version": "0.119.0", "description": "Build electron GUI openFrameworks projectGenerator", "license": "MIT", "main": "index.js", diff --git a/scripts/test_cmdline_args.sh b/scripts/test_cmdline_args.sh index a4522383..44d82c3a 100755 --- a/scripts/test_cmdline_args.sh +++ b/scripts/test_cmdline_args.sh @@ -115,6 +115,31 @@ expect_output() { echo "$OUT" | grep -qE "$1" || fail "output has no '$1'" } +expect_file() { + [ -e "$1" ] || fail "${1#$OF/} missing" +} + +expect_in_file() { + tr -d '\r' < "$1" 2>/dev/null | grep -qF -- "$2" || fail "${1#$OF/} has no '$2'" +} + +# Xcode / Visual Studio list the sources; make projects read them from config.make +project_sources_file() { + if [ "$XCODE" = 1 ]; then echo "$APP_DIR/myApp.xcodeproj/project.pbxproj"; elif [ "$PLATFORM" = vs ]; then echo "$APP_DIR/myApp.vcxproj"; fi +} + +expect_src_in_project() { + local f + f="$(project_sources_file)" + [ -z "$f" ] || expect_in_file "$f" "$1" +} + +expect_src_not_in_project() { + local f + f="$(project_sources_file)" + [ -z "$f" ] || ! tr -d '\r' < "$f" | grep -qF -- "$1" || fail "${f#$OF/} still has $1" +} + expect_pbxproj() { [ "$XCODE" = 1 ] || return 0 grep -q "$1" "$APP_DIR/myApp.xcodeproj/project.pbxproj" 2>/dev/null || fail "project.pbxproj has no $1" @@ -180,6 +205,18 @@ pg -o .. -p "$PLATFORM" -a ofxGui -D A=1 -P "echo done" -s myApps/pgArgTests/sha expect_exit 0; expect_updated; expect_no_stray grep -q ofxGui "$APP_DIR/addons.make" 2>/dev/null || fail "addons.make has no ofxGui" +start "empty values before the path, as OF's CI scripts: -a\"\" -p\"$PLATFORM\" -t\"\" path (was: path taken as the template)" +pg -o"$OF" -v -a"" -p"$PLATFORM" -t"" "$APP" +expect_exit 0; expect_updated; expect_no_stray + +start "bare -t right before the path, as the GUI with no template (was: path taken as the template)" +pg -o.. -a -p"$PLATFORM" -t "$APP" +expect_exit 0; expect_updated; expect_no_stray + +start "empty separate values: -a \"\" -t \"\" path" +pg -o.. -p"$PLATFORM" -a "" -t "" "$APP" +expect_exit 0; expect_updated; expect_no_stray + start "project path only (host platform, OF path from -o)" pg -o.. "$APP" expect_exit 0; expect_no_stray @@ -234,6 +271,136 @@ pg -o.. -p"$PLATFORM" -r myApps/pgArgTests expect_exit 0; expect_updated; expect_no_stray [ -e "$TEST_DIR/other/${PROJECT_FILE/myApp/other}" ] || fail "other/${PROJECT_FILE/myApp/other} not written" +# ---------------------------------------------------------------- how the GUI and openFrameworks' scripts call it + +# frontend/index.js generateFunction: -v -o -a -p -s -t [-D -P -k -W --app-*] [-f --system-libs] path +start "GUI generate, no addons / template / source: -a -s -t path" +pg -v -o"$OF" "-a " -p"$PLATFORM" -s -t "$APP" +expect_exit 0; expect_updated; expect_no_stray + +start "GUI generate with addons, defines, post-build, version" +pg -v -o"$OF" -a"ofxGui,ofxOsc" -p"$PLATFORM" -s -t -D"A=1,B" -P"echo gui-postbuild" --app-version=2.0 --app-build=7 "$APP" +expect_exit 0; expect_updated; expect_no_stray +expect_in_file "$APP_DIR/addons.make" "ofxOsc" + +# frontend/index.js updateFunction (update multiple): -r -v -o -p [-t] folder +start "GUI update multiple: -r -v -o -p folder" +mkdir -p "$TEST_DIR/other/src" && cp "$APP_DIR/src/"* "$TEST_DIR/other/src/" +pg -r -v -o"$OF" -p"$PLATFORM" myApps/pgArgTests +expect_exit 0; expect_updated; expect_no_stray + +# scripts/dev/create_package.sh, scripts/osx/snapshotAllExamples.sh +start "openFrameworks create_package: --recursive -p -o folder" +mkdir -p "$TEST_DIR/other/src" && cp "$APP_DIR/src/"* "$TEST_DIR/other/src/" +pg --recursive -p"$PLATFORM" -o"$OF" "$TEST_DIR" +expect_exit 0; expect_updated; expect_no_stray + +# scripts/ci/cmake/build.sh +if [ -f "$OF/scripts/templates/cmake/CMakeLists.txt" ]; then + start "openFrameworks cmake build.sh: -o -t\"cmake\" -p path" + pg -o"$OF" -t"cmake" -p"$PLATFORM" "$APP_DIR" + expect_exit 0; expect_file "$APP_DIR/CMakeLists.txt"; expect_no_stray +fi + +# ---------------------------------------------------------------- project paths + +start "spaces in the project name" +mkdir -p "$TEST_DIR/my App/src" && cp "$APP_DIR/src/"* "$TEST_DIR/my App/src/" +pg -o.. -p"$PLATFORM" "myApps/pgArgTests/my App" +expect_exit 0; expect_file "$TEST_DIR/my App/${PROJECT_FILE/myApp/my App}"; expect_no_stray + +start "trailing slash and ./ in the project path" +pg -o.. -p"$PLATFORM" "./$APP/" +expect_exit 0; expect_updated; expect_no_stray + +start "project path relative to apps/myApps (a name only)" +mkdir -p "$TEST_DIR/../pgArgTestsByName/src" && cp "$APP_DIR/src/"* "$TEST_DIR/../pgArgTestsByName/src/" +pg -o"$OF" -p"$PLATFORM" pgArgTestsByName +[ -e "$RUN_DIR/myApps/pgArgTestsByName/${PROJECT_FILE/myApp/pgArgTestsByName}" ] || [ -e "$RUN_DIR/pgArgTestsByName/${PROJECT_FILE/myApp/pgArgTestsByName}" ] || fail "pgArgTestsByName not generated" +rm -rf "$RUN_DIR/myApps/pgArgTestsByName" "$RUN_DIR/pgArgTestsByName" +expect_no_stray + +# ---------------------------------------------------------------- addons + +start "missing addon is kept in addons.make" +pg -o.. -p"$PLATFORM" -a"ofxGui,ofxDoesNotExist" "$APP" +expect_exit 0; expect_updated; expect_output "addon not found: ofxDoesNotExist" +expect_in_file "$APP_DIR/addons.make" "ofxDoesNotExist" + +start "addons with spaces and duplicates" +pg -o.. -p"$PLATFORM" -a"ofxGui, ofxOsc,ofxGui" "$APP" +expect_exit 0 +[ "$(tr -d '\r' < "$APP_DIR/addons.make" | grep -cx ofxGui)" = 1 ] || fail "addons.make should list ofxGui once" +expect_in_file "$APP_DIR/addons.make" "ofxOsc" + +start "addons.make comments and #url of a missing addon are kept" +printf 'ofxGui\nofxMissingOne#https://github.com/example/ofxMissingOne\n' > "$APP_DIR/addons.make" +pg -o.. -p"$PLATFORM" "$APP" +expect_exit 0; expect_in_file "$APP_DIR/addons.make" "ofxMissingOne#https://github.com/example/ofxMissingOne" + +start "no addons and no addons.make: none written" +pg -o.. -p"$PLATFORM" "$APP" +expect_exit 0; [ ! -e "$APP_DIR/addons.make" ] || fail "empty addons.make written" + +# ---------------------------------------------------------------- external sources (-s) + +mkshared() { + mkdir -p "$TEST_DIR/shared" && printf 'int sharedFn() { return 1; }\n' > "$TEST_DIR/shared/sharedSource.cpp" +} + +start "-s relative to the current folder (was: silently nothing)" +mkshared +pg -o.. -p"$PLATFORM" -s myApps/pgArgTests/shared "$APP" +expect_exit 0; expect_config PROJECT_EXTERNAL_SOURCE_PATHS "../shared" +expect_src_in_project "sharedSource.cpp" + +start "-s relative to the project" +mkshared +pg -o.. -p"$PLATFORM" -s ../shared "$APP" +expect_exit 0; expect_config PROJECT_EXTERNAL_SOURCE_PATHS "../shared" + +start "-s absolute, written relative to the project" +mkshared +pg -o.. -p"$PLATFORM" -s "$TEST_DIR/shared" "$APP" +expect_exit 0; expect_config PROJECT_EXTERNAL_SOURCE_PATHS "../shared" + +start "external sources kept on update without -s, empty -s removes them (was: dropped / stale)" +mkshared +pg -o.. -p"$PLATFORM" -s ../shared "$APP" +pg -o.. -p"$PLATFORM" "$APP" +expect_exit 0; expect_config PROJECT_EXTERNAL_SOURCE_PATHS "../shared" +expect_src_in_project "sharedSource.cpp" +pg -o.. -p"$PLATFORM" -s -t "$APP" +expect_exit 0; expect_no_config PROJECT_EXTERNAL_SOURCE_PATHS +expect_src_not_in_project "sharedSource.cpp" + +# ---------------------------------------------------------------- other options + +start "-t vscode adds VS Code to the platform (#476)" +pg -o.. -p"$PLATFORM" -tvscode "$APP" +expect_exit 0; expect_updated; expect_file "$APP_DIR/.vscode" + +start "-P post-build script lands in the project" +pg -o.. -p"$PLATFORM" -P "echo pg-postbuild-test" "$APP" +expect_exit 0 +if [ "$XCODE" = 1 ]; then expect_pbxproj "pg-postbuild-test"; elif [ "$PLATFORM" = vs ]; then expect_in_file "$APP_DIR/myApp.vcxproj" "pg-postbuild-test"; fi + +start "-k bad MSYS2 environment warns" +pg -o.. -pmsys2 -k bogus "$APP" +expect_exit 0; expect_output "unknown msys2 environment bogus" + +start "-w / -i / --getofpath" +pg -w +expect_exit 0; expect_output "openFrameworks projectGenerator" +pg -i +expect_exit 0; expect_output "ofHostPlatform" +pg -o.. --getofpath +expect_exit 0; expect_output '"ofPath"' + +start "-l lists templates" +pg -o.. -p"$PLATFORM" -l "$APP" +expect_exit 0 + # ---------------------------------------------------------------- system frameworks / libraries start "frameworks: commas"