diff --git a/commandLine/src/defines.h b/commandLine/src/defines.h index 566c806c..e4897d06 100644 --- a/commandLine/src/defines.h +++ b/commandLine/src/defines.h @@ -1,5 +1,5 @@ #define OFPROJECTGENERATOR_MAJOR_VERSION "0" -#define OFPROJECTGENERATOR_MINOR_VERSION "109" +#define OFPROJECTGENERATOR_MINOR_VERSION "110" #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 2a6d9862..917a2ea4 100644 --- a/commandLine/src/main.cpp +++ b/commandLine/src/main.cpp @@ -34,7 +34,8 @@ enum optionIndex { UNKNOWN, CLEANNAME_DISABLE, DEFINES, POSTBUILD, - MSYS2ENV + MSYS2ENV, + EMSCRIPTEN_WASM }; constexpr option::Descriptor usage[] = { @@ -64,6 +65,7 @@ constexpr option::Descriptor usage[] = { { DEFINES, 0, "D", "defines", option::Arg::Optional, " --defines, -D \tpreprocessor defines list (such as OF_USE_ANGLE=1,MY_FLAG)" }, { POSTBUILD, 0, "P", "postbuild", option::Arg::Optional, " --postbuild, -P \tscript to run after the project compiles (Xcode run script / VS post-build event)" }, { MSYS2ENV, 0, "k", "msys2env", option::Arg::Optional, " --msys2env, -k \tMSYS2 environment for the msys2 / VSCode targets: ucrt64 (default) | mingw64 | clang64 | clangarm64" }, + { EMSCRIPTEN_WASM, 0, "W", "emscripten-wasm", option::Arg::Optional, " --emscripten-wasm, -W \temscripten WebAssembly: 32 (default) | 64 (wasm64, MEMORY64), written to config.make PROJECT_EMSCRIPTEN_WASM" }, { 0, 0, 0, 0, 0, 0 } }; @@ -93,6 +95,7 @@ vector targets; vector frameworks; vector defines; string postBuildScript; +string emscriptenWasm; // empty keeps the project's config.make value string ofPathEnv; string templateName; string msys2Env = "ucrt64"; @@ -264,6 +267,7 @@ void updateProject(const fs::path & path, const string & target, bool bConsiderP if (!bDryRun) { auto project = getTargetProject(target); project->setMsys2Environment(msys2Env); + project->setEmscriptenWasm(emscriptenWasm); project->create(path, templateName); if (bConsiderParameterAddons && bAddonsPassedIn) { @@ -615,6 +619,15 @@ int main(int argc, char ** argv) { defines = ofSplitString(options[DEFINES].arg, ",", true, true); } + if (options[EMSCRIPTEN_WASM].count() > 0 && options[EMSCRIPTEN_WASM].arg != NULL) { + std::string bits = ofTrim(options[EMSCRIPTEN_WASM].arg); + if (bits == "32" || bits == "64") { + emscriptenWasm = bits; + } else { + ofLogWarning() << "unknown emscripten wasm " << bits << ", expected 32 or 64 - keeping the project's setting"; + } + } + if (options[POSTBUILD].count() > 0 && options[POSTBUILD].arg != NULL) { postBuildScript = ofTrim(options[POSTBUILD].arg); } @@ -759,6 +772,7 @@ int main(int argc, char ** argv) { ofLogNotice() << "project path is: [" << projectPath << "]"; auto project = getTargetProject(t); project->setMsys2Environment(msys2Env); + project->setEmscriptenWasm(emscriptenWasm); project->create(projectPath, templateName); if(bAddonsPassedIn){ for (auto & addon : addons) { diff --git a/commandLine/src/projects/baseProject.cpp b/commandLine/src/projects/baseProject.cpp index f3a0f701..42ad3f10 100644 --- a/commandLine/src/projects/baseProject.cpp +++ b/commandLine/src/projects/baseProject.cpp @@ -7,6 +7,7 @@ // #include "baseProject.h" +#include #include "ofLog.h" #include "Utils.h" #include "ofConstants.h" @@ -31,6 +32,7 @@ fs::path baseProject::getPlatformTemplateDir() { || target == "linuxarmv6l" || target == "linuxarmv7l" || target == "linuxaarch64" + || target == "emscripten" // vscode base, emscripten template overlaid in create() ) { folder = "vscode"; } @@ -110,6 +112,36 @@ vector baseProject::listAvailableTemplates(string target) return templates; } +// PROJECT_EMSCRIPTEN_WASM from config.make; the old EMSCRIPTEN_MEMORY64=1 also means 64 +static std::string readEmscriptenWasm(const fs::path & configMake) { + std::string bits; + if (!fs::exists(configMake)) return bits; + for (auto & line : fileToStrings(configMake)) { + auto config = ofTrim(line); + if (config.empty() || config[0] == '#') continue; + auto eq = config.find('='); + if (eq == std::string::npos) continue; + auto var = ofTrim(config.substr(0, eq)); + auto value = ofTrim(config.substr(eq + 1)); + if (var == "PROJECT_EMSCRIPTEN_WASM") bits = value; + else if (var == "EMSCRIPTEN_MEMORY64" && value == "1" && bits.empty()) bits = "64"; + } + return bits; +} + +static const std::string OF_CMAKE_INCLUDE { "libs/openFrameworksCompiled/project/CMake/openFrameworks.cmake" }; + +// set(PROJECT_EMSCRIPTEN_WASM 64) written by PG into an openFrameworks CMakeLists.txt (openFrameworks#8586) +static std::string readCMakeEmscriptenWasm(const fs::path & cmakeLists) { + static const std::regex wasmSet(R"re(^\s*set\(\s*PROJECT_EMSCRIPTEN_WASM\s+(32|64)\b)re"); + std::smatch m; + if (!fs::exists(cmakeLists)) return ""; + for (auto & line : fileToStrings(cmakeLists)) { + if (std::regex_search(line, m, wasmSet)) return m[1]; + } + return ""; +} + bool baseProject::create(const fs::path & _path, string templateName){ // alert("baseProject::create " + path.string() + " : " + templateName, 35); auto path = _path; // just because it is const @@ -152,6 +184,29 @@ bool baseProject::create(const fs::path & _path, string templateName){ projectPath = normalizePath(projectPath); ofLogNotice() << "projectPath: [" << projectPath << "]"; projectName = projectPath.filename().string(); + + // read before template copies can replace config.make + emscriptenWasm = emscriptenWasmOption; + if (emscriptenWasm.empty()) emscriptenWasm = readEmscriptenWasm(projectDir / "config.make"); + if (emscriptenWasm.empty()) emscriptenWasm = readCMakeEmscriptenWasm(projectDir / "CMakeLists.txt"); + if (!emscriptenWasm.empty()) { + ofLogNotice() << "{ \"emscriptenWasm\": \"" << emscriptenWasm << "\" }"; + } + if (emscriptenWasm == "64") { + bool bHasWasm64Libs = false; + if (fs::exists(getOFRoot() / "libs")) { + for (auto & lib : fs::directory_iterator(getOFRoot() / "libs")) { + if (fs::exists(lib.path() / "lib" / "emscripten" / "WASM64")) { + bHasWasm64Libs = true; + break; + } + } + } + if (!bHasWasm64Libs) { + ofLogWarning() << "wasm64: no libs/*/lib/emscripten/WASM64 found. Run BITS=64 scripts/emscripten/download_libs.sh (replaces the installed lib/emscripten libs)"; + } + } + bool bDoesSrcDirExist = false; fs::path projectSrc { projectDir / "src" }; if (fs::exists(projectSrc) && fs::is_directory(projectSrc)) { @@ -174,6 +229,22 @@ bool baseProject::create(const fs::path & _path, string templateName){ ofLogError() << "{ \"errorMessage\": \"" << "baseProject::create createProjectFile failed" << "\", \"status:\" \"EXIT_FAILURE\" }"; return false; } + // cmake template (openFrameworks#8586) is a single CMakeLists.txt with no template.config; keep an existing one + if (templateName == "cmake") { + fs::path cmakeTemplate { getOFRoot() / templatesFolder / "cmake" / "CMakeLists.txt" }; + if (!fs::exists(cmakeTemplate)) { + ofLogWarning() << "no scripts/templates/cmake/CMakeLists.txt in this openFrameworks (CMake support is openFrameworks#8586)"; + } else if (!fs::exists(projectDir / "CMakeLists.txt")) { + fs::copy_file(cmakeTemplate, projectDir / "CMakeLists.txt"); + } + templateName = ""; + } + + // emscripten platform: emscripten template (Makefile, config.make, emmake.sh) over the vscode base + if (target == "emscripten") { + recursiveTemplateCopy(getOFRoot() / templatesFolder / "emscripten", projectDir); + if (templateName == "emscripten") templateName = ""; + } if (templateName != "" && !empty(templateName)) { fs::path templateDir = getOFRoot() / templatesFolder / templateName; templateDir = normalizePath(templateDir); @@ -266,6 +337,7 @@ bool baseProject::save(){ vector lines = fileToStrings(projectDir / "config.make"); std::ofstream saveConfig(projectDir / "config.make"); bool bWroteDefines = false; + bool bWroteWasm = false; for (auto & str : lines) { //add the of root path @@ -290,6 +362,16 @@ bool baseProject::save(){ saveConfig << "PROJECT_DEFINES = " << ofJoinString(projectDefines, " ") << std::endl; bWroteDefines = true; } + } + // emscripten wasm32 / wasm64: 32 is the makefile default, so it leaves the line commented + else if( !emscriptenWasm.empty() && (str.rfind("# PROJECT_EMSCRIPTEN_WASM =", 0) == 0 || str.rfind("PROJECT_EMSCRIPTEN_WASM =", 0) == 0) ){ + if (!bWroteWasm) { + saveConfig << (emscriptenWasm == "64" ? "PROJECT_EMSCRIPTEN_WASM = 64" : "# PROJECT_EMSCRIPTEN_WASM = 64") << std::endl; + bWroteWasm = true; + } + } + else if( emscriptenWasm == "32" && str.rfind("EMSCRIPTEN_MEMORY64", 0) == 0 ){ + saveConfig << "# " << str << std::endl; } else { saveConfig << str << std::endl; } @@ -297,6 +379,12 @@ bool baseProject::save(){ if (projectDefines.size() && !bWroteDefines && lines.size()) { saveConfig << "PROJECT_DEFINES = " << ofJoinString(projectDefines, " ") << std::endl; } + if (emscriptenWasm == "64" && !bWroteWasm && lines.size()) { + saveConfig << "# emscripten: 32 (default) or 64 (wasm64, MEMORY64)" << std::endl; + saveConfig << "PROJECT_EMSCRIPTEN_WASM = 64" << std::endl; + } + saveCMakeLists(); + #ifdef OFADDON_OUTPUT_JSON_DEBUG saveAddonsToJson(); #endif @@ -891,3 +979,31 @@ bool baseProject::copyTemplateFile::run() { } return true; } + +// OF_ROOT and PROJECT_EMSCRIPTEN_WASM in an openFrameworks app CMakeLists.txt, like config.make +void baseProject::saveCMakeLists() { + fs::path cmakeLists { projectDir / "CMakeLists.txt" }; + if (!fs::exists(cmakeLists)) return; + auto lines = fileToStrings(cmakeLists); + bool bOFApp = std::any_of(lines.begin(), lines.end(), [](const std::string & l) { return l.find(OF_CMAKE_INCLUDE) != std::string::npos; }); + if (!bOFApp) return; + + fs::path root = getOFRoot(); + string ofRoot = root.generic_string(); + if (projectDir.string().rfind(root.string(), 0) == 0) { + ofRoot = "${CMAKE_CURRENT_SOURCE_DIR}/" + fs::relative(root, projectDir).generic_string(); + } + static const std::regex ofRootSet(R"re(^(\s*)set\(OF_ROOT\s+"[^"]*"\))re"); + static const std::regex wasmSet(R"re(^\s*set\(\s*PROJECT_EMSCRIPTEN_WASM\b)re"); + + std::ofstream out(cmakeLists); + for (auto & line : lines) { + if (!emscriptenWasm.empty() && std::regex_search(line, wasmSet)) continue; + if (line.find(OF_CMAKE_INCLUDE) != std::string::npos && emscriptenWasm == "64") { + out << "# emscripten: 32 (default) or 64 (wasm64, MEMORY64)" << std::endl; + out << "set(PROJECT_EMSCRIPTEN_WASM 64)" << std::endl; + } + if (line.find("# emscripten: 32 (default) or 64") != std::string::npos && !emscriptenWasm.empty()) continue; + out << std::regex_replace(line, ofRootSet, "$1set(OF_ROOT \"" + ofRoot + "\")") << std::endl; + } +} diff --git a/commandLine/src/projects/baseProject.h b/commandLine/src/projects/baseProject.h index 310c850a..fe28e1e2 100644 --- a/commandLine/src/projects/baseProject.h +++ b/commandLine/src/projects/baseProject.h @@ -110,6 +110,8 @@ class baseProject { // MSYS2 environment (ucrt64 | mingw64 | clang64) - only meaningful for VSCodeProject, // a no-op everywhere else so callers don't need to know which target this applies to virtual void setMsys2Environment(const std::string & env) {} + // emscripten wasm32 / wasm64 (-W/--emscripten-wasm), written to config.make PROJECT_EMSCRIPTEN_WASM; empty keeps the project's value + void setEmscriptenWasm(const std::string & bits) { emscriptenWasmOption = bits; } #ifdef OFADDON_OUTPUT_JSON_DEBUG @@ -181,6 +183,9 @@ class baseProject { std::vector addons; std::vector extSrcPaths; std::vector projectDefines; // also written to config.make PROJECT_DEFINES for make builds + std::string emscriptenWasmOption; // from the CLI, empty = keep + std::string emscriptenWasm; // value for this project: "", "32" or "64" + void saveCMakeLists(); //cached addons - if an addon is requested more than once, avoid loading from disk as it's quite slow std::map> addonsCache; //indexed by [platform][supplied path] diff --git a/commandLine/src/utils/Utils.cpp b/commandLine/src/utils/Utils.cpp index 07b408ae..35a29cc2 100644 --- a/commandLine/src/utils/Utils.cpp +++ b/commandLine/src/utils/Utils.cpp @@ -455,7 +455,8 @@ unique_ptr getTargetProject(const string & targ) { targ == "linux64" || targ == "linuxarmv6l" || targ == "linuxarmv7l" || - targ == "linuxaarch64" + targ == "linuxaarch64" || + targ == "emscripten" ) { return unique_ptr(new VSCodeProject(targ)); } diff --git a/frontend/app.js b/frontend/app.js index 709f7578..b842085b 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -206,9 +206,20 @@ ipcRenderer.on('setSourceExtraPath', (event, [arg, index]) => { // TODO: //------------------------------------------- ipcRenderer.on('setGenerateMode', (event, arg) => { switchGenerateMode(arg); + // new projects start at wasm32; an existing project sends its own value via setEmscriptenWasm + if (arg === 'createMode') { + $('#wasmDropdown').dropdown('set selected', '32'); + $('#wasmNote').hide(); + } }); //------------------------------------------- +// existing project's PROJECT_EMSCRIPTEN_WASM, so updating keeps it +ipcRenderer.on('setEmscriptenWasm', (event, bits) => { + $('#wasmDropdown').dropdown('set selected', bits === '64' ? '64' : '32'); + $('#wasmNote').toggle(bits === '64'); +}); + ipcRenderer.on('importProjectSettings', (event, settings) => { $("#projectPath").val(settings.projectPath); $("#projectName").val(settings.projectName).trigger('change'); // change triggers addon scanning @@ -992,6 +1003,7 @@ function setup() { $('#rendererDropdown').dropdown(); $('#glDropdown, #webglDropdown, #glesDropdown').dropdown(); + $('#wasmDropdown').dropdown({ onChange: (value) => $('#wasmNote').toggle(value === '64') }); $('#rendererDropdown').dropdown('set exactly', 'default'); $('#msys2EnvDropdown').dropdown(); $('#msys2EnvDropdown').dropdown('set exactly', 'ucrt64'); @@ -1227,7 +1239,8 @@ function generate() { verbose: bVerbose, defines: definesArr.join(','), postBuild: $('#postBuildScript').val().trim(), - msys2Env: $('#msys2EnvField').css('display') !== 'none' ? $('#msys2EnvDropdown').dropdown('get value') : '' + msys2Env: $('#msys2EnvField').css('display') !== 'none' ? $('#msys2EnvDropdown').dropdown('get value') : '', + emscriptenWasm: $('#wasmField').css('display') !== 'none' ? $('#wasmDropdown').dropdown('get value') : '' }; // console.log(gen); @@ -1241,7 +1254,7 @@ function generate() { // the template pick is cleared in the #projectName change handler instead, // as soon as the user points at a different project - not here, so a plain // single generate doesn't wipe the pick you just used - lastGeneratedProject = { projectName: gen.projectName, projectPath: gen.projectPath, templateList: gen.templateList }; + lastGeneratedProject = { projectName: gen.projectName, projectPath: gen.projectPath, templateList: gen.templateList, platformList: gen.platformList }; openConsoleForOperation(); ipcRenderer.send('generate', gen); } @@ -1398,7 +1411,9 @@ function updateRendererFieldVisibility() { $('#rendererField').hide(); } const selectedTemplates = ($("#templatesDropdown input").val() || '').trim().split(','); - $('#webglField').toggle(!!defaultSettings.advancedMode && selectedTemplates.includes('emscripten')); + const emscripten = selectedTemplates.includes('emscripten') || selectedPlatforms.includes('emscripten'); + $('#webglField').toggle(!!defaultSettings.advancedMode && emscripten); + $('#wasmField').toggle(!!defaultSettings.advancedMode && emscripten); $('#glesField').toggle(!!defaultSettings.advancedMode && selectedPlatforms.some((p) => ['ios', 'macos'].includes(p))); const glPlatforms = ['osx', 'vs', 'msys2', 'linux', 'linux64', 'linuxarmv6l', 'linuxaarch64', 'vscode']; @@ -1458,7 +1473,9 @@ function displayModal(message) { ipcRenderer.send('openExternal', $(e.currentTarget).prop("href") ); }); - const isEmscripten = lastGeneratedProject != null && lastGeneratedProject.templateList.includes('emscripten'); + // emscripten template on osx/linux, or the emscripten platform + const isEmscripten = lastGeneratedProject != null + && (lastGeneratedProject.templateList.includes('emscripten') || (lastGeneratedProject.platformList || []).includes('emscripten')); $("#IDEButton").hide(); $("#FolderButton").hide(); @@ -1635,7 +1652,7 @@ function launchInIDE(){ for (let i = 0; i < templatePicked.length; i++){ templateValueArray.push($(templatePicked[i]).attr("data-value")); } - if (templateValueArray.includes('emscripten')) { + if (templateValueArray.includes('emscripten') || getPlatformList().includes('emscripten')) { displayModal(`\nEmscripten
${t('modal.emscriptenNoIde')}`); return; } diff --git a/frontend/index.html b/frontend/index.html index 97545293..1e80ea3d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -278,6 +278,19 @@ +