Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions commandLine/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ The GUI shows them as System frameworks / System libraries with Advanced options



## Bundle ID

./projectGenerator -o"pathToOF" --bundle-id=com.example.myApp pathToProject

Saved in `config.make` as `APP_BUNDLE_ID = com.example.myApp` and set on every update in the Xcode targets (`PRODUCT_BUNDLE_IDENTIFIER`), `Project.xcconfig`, a literal `CFBundleIdentifier` in a custom Info.plist, CMake `set(APP_BUNDLE_ID ...)` and the Android `applicationId` (`-` becomes `_` there). Without `--bundle-id` the project's own is kept: `config.make`, then `CMakeLists.txt`, the Xcode target, `Project.xcconfig`, `build.gradle`. The template's `cc.openFrameworks.<name>` is the default.


## Environment variable

If you want to skip using `-o` (for specifying the path to openframeworks) you can set an environment variable "PG_OF_PATH". For example, on OSX, you can set this in your `~/.bash_profile`:
Expand Down
2 changes: 1 addition & 1 deletion commandLine/src/defines.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#define OFPROJECTGENERATOR_MAJOR_VERSION "0"
#define OFPROJECTGENERATOR_MINOR_VERSION "121"
#define OFPROJECTGENERATOR_MINOR_VERSION "122"
#define OFPROJECTGENERATOR_PATCH_VERSION "0"

#define PG_VERSION (OFPROJECTGENERATOR_MAJOR_VERSION "." OFPROJECTGENERATOR_MINOR_VERSION "." OFPROJECTGENERATOR_PATCH_VERSION)
Expand Down
18 changes: 16 additions & 2 deletions commandLine/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ enum optionIndex { UNKNOWN,
MSYS2ENV,
EMSCRIPTEN_WASM,
APP_VERSION,
APP_BUILD
APP_BUILD,
BUNDLE_ID
};

constexpr option::Descriptor usage[] = {
Expand Down Expand Up @@ -74,6 +75,7 @@ constexpr option::Descriptor usage[] = {

{ APP_VERSION, 0, "", "app-version", option::Arg::Optional, " --app-version \tapp version, eg 1.2.3 (default 1.0): config.make / CMake APP_VERSION, Xcode MARKETING_VERSION, Android versionName" },
{ APP_BUILD, 0, "", "app-build", option::Arg::Optional, " --app-build \tbuild number, eg 42 (default 1): config.make / CMake APP_BUILD, Xcode CURRENT_PROJECT_VERSION, Android versionCode" },
{ BUNDLE_ID, 0, "", "bundle-id", option::Arg::Optional, " --bundle-id \tbundle id, eg com.example.myApp (default cc.openFrameworks.<name>): config.make / CMake APP_BUNDLE_ID, Xcode PRODUCT_BUNDLE_IDENTIFIER, Android applicationId" },
{ 0, 0, 0, 0, 0, 0 }
};

Expand Down Expand Up @@ -108,6 +110,7 @@ string postBuildScript;
string emscriptenWasm; // empty keeps the project's config.make value
string appVersion; // empty keeps the project's value
string appBuild;
string appBundleId; // empty keeps the project's value
string ofPathEnv;
string templateName;
string msys2Env = "ucrt64";
Expand Down Expand Up @@ -281,6 +284,7 @@ void updateProject(const fs::path & path, const string & target, bool bConsiderP
project->setMsys2Environment(msys2Env);
project->setEmscriptenWasm(emscriptenWasm);
project->setAppVersion(appVersion, appBuild);
project->setAppBundleId(appBundleId);
project->setSystemFrameworks(frameworks, bFrameworksPassedIn, systemLibs, bSystemLibsPassedIn);
project->create(path, templateName);

Expand Down Expand Up @@ -476,7 +480,7 @@ int pgMain(int argc, char ** argv) {
// optionparser only takes attached values (-posx, --platforms=osx); join -p osx / --platforms osx,
// but leave the last argument as the project path (-f <path> is an empty -f)
static const std::string valueShort { "paotsmfDPkW" };
static const std::vector<std::string> valueLong { "platforms", "addons", "ofPath", "template", "source", "ofmenu", "frameworks", "system-libs", "defines", "postbuild", "msys2env", "emscripten-wasm", "app-version", "app-build" };
static const std::vector<std::string> valueLong { "platforms", "addons", "ofPath", "template", "source", "ofmenu", "frameworks", "system-libs", "defines", "postbuild", "msys2env", "emscripten-wasm", "app-version", "app-build", "bundle-id" };
std::vector<std::string> joinedArgs;
for (int i = 0; i < argc; i++) {
std::string a { argv[i] };
Expand Down Expand Up @@ -709,6 +713,15 @@ int pgMain(int argc, char ** argv) {
}
}

if (options[BUNDLE_ID].count() > 0 && options[BUNDLE_ID].arg != NULL) {
std::string id = ofTrim(options[BUNDLE_ID].arg);
if (std::regex_match(id, std::regex(R"([A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+)"))) {
appBundleId = id;
} else {
ofLogWarning() << "bundle id " << id << " should be letters, digits, - and dots (eg com.example.myApp) - keeping the project's bundle id";
}
}

if (options[POSTBUILD].count() > 0 && options[POSTBUILD].arg != NULL) {
postBuildScript = ofTrim(options[POSTBUILD].arg);
}
Expand Down Expand Up @@ -866,6 +879,7 @@ int pgMain(int argc, char ** argv) {
project->setMsys2Environment(msys2Env);
project->setEmscriptenWasm(emscriptenWasm);
project->setAppVersion(appVersion, appBuild);
project->setAppBundleId(appBundleId);
project->setSystemFrameworks(frameworks, bFrameworksPassedIn, systemLibs, bSystemLibsPassedIn);
project->create(projectPath, templateName);
if(bAddonsPassedIn){
Expand Down
122 changes: 122 additions & 0 deletions commandLine/src/projects/baseProject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,59 @@ static std::pair<std::string, std::string> readGradleAppVersion(const fs::path &
return { version, build };
}

// APP_BUNDLE_ID in config.make
static std::string readMakeBundleId(const fs::path & configMake) {
auto values = readMakeList(configMake, "APP_BUNDLE_ID");
return values.empty() ? "" : values.back();
}

// set(APP_BUNDLE_ID ...) in an openFrameworks CMakeLists.txt
static std::string readCMakeBundleId(const fs::path & cmakeLists) {
static const std::regex bundleIdSet(R"re(^\s*set\(\s*APP_BUNDLE_ID\s+"?([^\s")]+)"?\s*\))re");
std::string bundleId;
std::smatch m;
if (!fs::exists(cmakeLists)) return bundleId;
for (auto & line : fileToStrings(cmakeLists)) {
if (std::regex_search(line, m, bundleIdSet)) bundleId = m[1];
}
return bundleId;
}

// PRODUCT_BUNDLE_IDENTIFIER in the .xcodeproj target settings or Project.xcconfig (first one)
static std::string readXcodeBundleId(const fs::path & file) {
static const std::regex bundleIdSet(R"re(^\s*"?PRODUCT_BUNDLE_IDENTIFIER"?\s*[:=]\s*"?([^\s";]+)"?)re");
std::smatch m;
if (!fs::exists(file)) return "";
for (auto & line : fileToStrings(file)) {
if (std::regex_search(line, m, bundleIdSet)) return m[1];
}
return "";
}

// applicationId in the android ofApp/build.gradle
static std::string readGradleBundleId(const fs::path & gradle) {
static const std::regex bundleIdSet(R"re(^\s*applicationId\s+['"]([^'"]+)['"])re");
std::smatch m;
if (!fs::exists(gradle)) return "";
for (auto & line : fileToStrings(gradle)) {
if (std::regex_search(line, m, bundleIdSet)) return m[1];
}
return "";
}

// the templates' own ids: cc.openFrameworks.$(TARGET_NAME), com.openFrameworks.emptyExample, cc.openframeworks.<name>
static bool isTemplateBundleId(const std::string & bundleId, const std::string & projectName) {
if (bundleId.empty() || bundleId.find('$') != std::string::npos) return true;
auto lower = ofToLower(bundleId);
auto name = ofToLower(projectName);
for (std::string prefix : { "cc.openframeworks.", "com.openframeworks." }) {
if (lower.rfind(prefix, 0) != 0) continue;
auto rest = lower.substr(prefix.size());
if (rest == "emptyexample" || rest == "androidassimpexample" || rest == name || rest == name + "debug") return true;
}
return false;
}

bool baseProject::create(const fs::path & _path, string templateName){
// alert("baseProject::create " + path.string() + " : " + templateName, 35);
auto path = _path; // just because it is const
Expand Down Expand Up @@ -334,6 +387,29 @@ bool baseProject::create(const fs::path & _path, string templateName){
appBuild = !appBuildOption.empty() ? appBuildOption : (!build.empty() ? build : "1");
ofLogNotice() << "{ \"appVersion\": \"" << appVersion << "\", \"appBuild\": \"" << appBuild << "\" }";
}

// bundle id: the CLI, then config.make, CMakeLists, Xcode target, Project.xcconfig, Android; the templates' default ids don't count
{
std::vector<std::pair<std::string, std::string>> found {
{ "config.make", readMakeBundleId(projectDir / "config.make") },
{ "CMakeLists.txt", readCMakeBundleId(projectDir / "CMakeLists.txt") },
{ "xcodeproj", readXcodeBundleId(projectDir / (projectName + ".xcodeproj") / "project.pbxproj") },
{ "Project.xcconfig", readXcodeBundleId(projectDir / "Project.xcconfig") },
{ "build.gradle", readGradleBundleId(projectDir / "ofApp" / "build.gradle") },
};
std::string bundleId, bundleIdFrom;
for (auto & f : found) {
if (isTemplateBundleId(f.second, projectName)) continue;
if (bundleId.empty()) {
bundleId = f.second;
bundleIdFrom = f.first;
} else if (f.second != bundleId && ofToLower(f.second) != ofToLower(ofJoinString(ofSplitString(bundleId, "-"), "_"))) {
ofLogWarning() << "bundle id " << f.second << " in " << f.first << " differs from " << bundleId << " in " << bundleIdFrom << " - using " << bundleIdFrom;
}
}
appBundleId = !appBundleIdOption.empty() ? appBundleIdOption : bundleId;
if (!appBundleId.empty()) ofLogNotice() << "{ \"appBundleId\": \"" << appBundleId << "\" }";
}
if (emscriptenWasm == "64") {
bool bHasWasm64Libs = false;
if (fs::exists(getOFRoot() / "libs")) {
Expand Down Expand Up @@ -486,6 +562,7 @@ bool baseProject::save(){
bool bWroteAppBuild = false;
bool bWroteFrameworks = false;
bool bWroteSystemLibs = false;
bool bWroteBundleId = 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));
Expand Down Expand Up @@ -557,6 +634,9 @@ bool baseProject::save(){
else if( isMakeVar(str, "PROJECT_SYSTEM_LIBS") ){
writeMakeList(str, "PROJECT_SYSTEM_LIBS", systemLibs, bWroteSystemLibs);
}
else if( isMakeVar(str, "APP_BUNDLE_ID") ){
writeMakeList(str, "APP_BUNDLE_ID", appBundleId.empty() ? std::vector<std::string>{} : std::vector<std::string>{ appBundleId }, bWroteBundleId);
}
else if( emscriptenWasm == "32" && str.rfind("EMSCRIPTEN_MEMORY64", 0) == 0 ){
saveConfig << "# " << str << std::endl;
} else {
Expand All @@ -581,9 +661,15 @@ bool baseProject::save(){
if (systemFrameworks.size() && !bWroteFrameworks) saveConfig << "PROJECT_FRAMEWORKS = " << ofJoinString(systemFrameworks, " ") << std::endl;
if (systemLibs.size() && !bWroteSystemLibs) saveConfig << "PROJECT_SYSTEM_LIBS = " << ofJoinString(systemLibs, " ") << std::endl;
}
if (!appBundleId.empty() && !bWroteBundleId) {
if (lines.size()) saveConfig << std::endl;
saveConfig << "# bundle id (CFBundleIdentifier, Android applicationId)" << std::endl;
saveConfig << "APP_BUNDLE_ID = " << appBundleId << std::endl;
}
saveConfig.close();
saveCMakeLists();
saveAppVersionFiles();
saveAppBundleIdFiles();

#ifdef OFADDON_OUTPUT_JSON_DEBUG
saveAddonsToJson();
Expand Down Expand Up @@ -1237,6 +1323,31 @@ void baseProject::saveAppVersionFiles() {
});
}

void baseProject::saveAppBundleIdFiles() {
if (appBundleId.empty()) return;
// target settings override this, but a reader of Project.xcconfig sees the real id
replaceLines(projectDir / "Project.xcconfig", {
{ std::regex(R"re(^(\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*)\S+)re"), appBundleId },
});
// Android application ids can't have '-'
replaceLines(projectDir / "ofApp" / "build.gradle", {
{ std::regex(R"re(^(\s*applicationId\s+)['"][^'"]*['"])re"), "\"" + ofJoinString(ofSplitString(appBundleId, "-"), "_") + "\"" },
});
// a custom Info.plist with a literal id instead of $(PRODUCT_BUNDLE_IDENTIFIER)
static const std::regex plistId(R"re((<key>CFBundleIdentifier</key>\s*<string>)([^<$]*)(</string>))re");
for (auto & name : { "openFrameworks-Info.plist", "ofxiOS-Info.plist", "ofxtvOS-Info.plist" }) {
fs::path plist { projectDir / name };
if (!fs::exists(plist)) continue;
std::ifstream in(plist);
std::string text((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
in.close();
std::smatch m;
if (!std::regex_search(text, m, plistId) || m[2].str() == appBundleId) continue;
std::ofstream out(plist);
out << m.prefix().str() << m[1].str() << appBundleId << m[3].str() << m.suffix().str();
}
}

// OF_ROOT and PROJECT_EMSCRIPTEN_WASM in an openFrameworks app CMakeLists.txt, like config.make
void baseProject::saveCMakeLists() {
fs::path cmakeLists { projectDir / "CMakeLists.txt" };
Expand All @@ -1253,6 +1364,8 @@ void baseProject::saveCMakeLists() {
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");
static const std::regex appVersionSet(R"re(^\s*#?\s*set\(\s*APP_(VERSION|BUILD)\b)re");
static const std::regex bundleIdSet(R"re(^\s*#?\s*set\(\s*APP_BUNDLE_ID\b)re");
bool bWroteBundleId = appBundleId.empty();
static const std::regex addApp(R"re(^\s*of_add_app\s*\()re");
string comment = isDefaultAppVersion() ? "# " : "";
bool bWroteAppVersion = false;
Expand All @@ -1269,10 +1382,19 @@ void baseProject::saveCMakeLists() {
writeAppVersion(out);
continue;
}
if (!appBundleId.empty() && std::regex_search(line, bundleIdSet)) {
if (!bWroteBundleId) out << "set(APP_BUNDLE_ID " << appBundleId << ")" << std::endl;
bWroteBundleId = true;
continue;
}
if (std::regex_search(line, addApp) && !bWroteAppVersion) {
if (!isDefaultAppVersion()) writeAppVersion(out);
bWroteAppVersion = true;
}
if (std::regex_search(line, addApp) && !bWroteBundleId) {
out << "set(APP_BUNDLE_ID " << appBundleId << ")" << std::endl;
bWroteBundleId = true;
}
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;
Expand Down
5 changes: 5 additions & 0 deletions commandLine/src/projects/baseProject.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ class baseProject {
// 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; }
void setAppVersion(const std::string & version, const std::string & build) { appVersionOption = version; appBuildOption = build; }
// bundle id (--bundle-id), written to config.make APP_BUNDLE_ID; empty keeps the project's (default cc.openFrameworks.<name>)
void setAppBundleId(const std::string & bundleId) { appBundleIdOption = bundleId; }


#ifdef OFADDON_OUTPUT_JSON_DEBUG
Expand Down Expand Up @@ -201,6 +203,9 @@ class baseProject {
std::string appVersionOption, appBuildOption; // from the CLI, empty = keep
std::string appVersion { "1.0" }, appBuild { "1" }; // value for this project
bool isDefaultAppVersion() const { return appVersion == "1.0" && appBuild == "1"; }
std::string appBundleIdOption; // from the CLI, empty = keep
std::string appBundleId; // value for this project, empty = the template's cc.openFrameworks.<name>
void saveAppBundleIdFiles();

//cached addons - if an addon is requested more than once, avoid loading from disk as it's quite slow
std::map<std::string,std::map<std::string, ofAddon>> addonsCache; //indexed by [platform][supplied path]
Expand Down
3 changes: 2 additions & 1 deletion commandLine/src/projects/xcodeProject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,7 @@ bool xcodeProject::saveProjectFile() {
}
}

// app version / build on every target configuration (target settings override Project.xcconfig)
// app version / build / bundle id on every target configuration (target settings override Project.xcconfig)
try {
auto & objects = j["objects"];
for (auto & target : objects) {
Expand All @@ -1153,6 +1153,7 @@ bool xcodeProject::saveProjectFile() {
auto & config = objects[configUUID.get<string>()];
config["buildSettings"]["MARKETING_VERSION"] = appVersion;
config["buildSettings"]["CURRENT_PROJECT_VERSION"] = appBuild;
if (!appBundleId.empty()) config["buildSettings"]["PRODUCT_BUNDLE_IDENTIFIER"] = appBundleId;
}
}
} catch (std::exception & e) {
Expand Down
11 changes: 10 additions & 1 deletion frontend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,18 @@ ipcRenderer.on('setGenerateMode', (event, arg) => {
$('#appVersion').val('');
$('#appBuild').val('');
$('#appVersionNote').hide();
$('#bundleId').val('');
$('#systemFrameworks').val('');
$('#systemLibs').val('');
}
});

//-------------------------------------------
// existing project's app version / build, so updating keeps them
ipcRenderer.on('setAppVersion', (event, { version, build, differs }) => {
ipcRenderer.on('setAppVersion', (event, { version, build, differs, bundleId }) => {
$('#appVersion').val(version || '');
$('#appBuild').val(build || '');
$('#bundleId').val(bundleId || '');
const note = (differs || []).map((d) => t('note.appVersionDiffers', { file: d.file, value: d.value })).join(' ');
$('#appVersionNote').text(note).toggle(!!defaultSettings.advancedMode && note.length > 0);
});
Expand Down Expand Up @@ -1207,6 +1209,9 @@ function saveDefaultSettings() {
}

//----------------------------------------
// letters, digits, - and dots, at least two parts (same check as the commandLine)
const BUNDLE_ID_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+$/;

function generate() {
// let's get all the info:
const platformValueArray = getPlatformList();
Expand Down Expand Up @@ -1287,6 +1292,7 @@ function generate() {
emscriptenWasm: $('#wasmField').css('display') !== 'none' ? $('#wasmDropdown').dropdown('get value') : '',
appVersion: $('#appVersion').val().trim(),
appBuild: $('#appBuild').val().trim(),
appBundleId: $('#bundleId').val().trim(),
// only for Apple targets; hidden = keep the project's config.make values
systemFrameworks: $('#systemFrameworksField').css('display') !== 'none' ? $('#systemFrameworks').val().split(/[\s,]+/).filter(Boolean) : null,
systemLibs: $('#systemFrameworksField').css('display') !== 'none' ? $('#systemLibs').val().split(/[\s,]+/).filter(Boolean) : null
Expand All @@ -1303,6 +1309,8 @@ function generate() {
$("#appVersion").oneTimeTooltip(t('tooltip.appVersionInvalid'));
} else if (gen.appBuild !== '' && !/^[1-9]\d*$/.test(gen.appBuild)) {
$("#appBuild").oneTimeTooltip(t('tooltip.appBuildInvalid'));
} else if (gen.appBundleId !== '' && !BUNDLE_ID_RE.test(gen.appBundleId)) {
$("#bundleId").oneTimeTooltip(t('tooltip.bundleIdInvalid'));
} else {
// 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
Expand Down Expand Up @@ -1481,6 +1489,7 @@ function updateRendererFieldVisibility() {
$('#glesField').toggle(!!defaultSettings.advancedMode && selectedPlatforms.some((p) => ['ios', 'macos'].includes(p)));
// advanced only; hidden fields keep the project's values, so updating still keeps them
$('#appVersionField').css('display', defaultSettings.advancedMode ? 'flex' : 'none');
$('#bundleIdField').toggle(!!defaultSettings.advancedMode);
$('#appVersionNote').toggle(!!defaultSettings.advancedMode && $('#appVersionNote').text().length > 0);
$('#systemFrameworksField').css('display', defaultSettings.advancedMode && selectedPlatforms.some((p) => ['osx', 'ios', 'macos', 'tvos'].includes(p)) ? 'flex' : 'none');

Expand Down
Loading
Loading