From 91dbae596b5b372949a27f98595f4f8d3fef27c1 Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Fri, 14 Apr 2023 23:47:13 -0700 Subject: [PATCH] feat!: Remove some Xcode build dir overrides Once upon a time, Xcode's default directory values had issues when there were spaces in project names, so we override the CONFIGURATION_BUILD_DIR and SHARED_PRECOMPS_DIR variables with our own paths. However, this can interfere with Cocoapods, and Xcode should be able to handle things sensibly nowadays. Unfortunately, that results in our build artifacts living somewhere in a randomly-named Xcode DerivedData directory, and then we can't do things like run the app in a simulator or on a device because we don't know the path to it. Setting SYMROOT allows us to control the output directory of the built products, with the caveat that Xcode always creates a subdirectory named with the current configuration. So instead of `build/emulator`, we'll have `build/Debug-iphonesimulator` and instead of `build/device`, we'll have `build/Release-iphoneos`. Hypothetically this is better because now we are sure that debug and release files never get mixed up in the same output directory. The downside is that this is a breaking change because it alters the path for the output .ipa files. Closes #617. Closes #659. Closes #671. Co-Authored-By: Susan Tan --- lib/build.js | 37 +++++++++++++------- lib/run.js | 7 ++-- tests/spec/unit/build.spec.js | 65 ++++++++++++++++++++++++----------- 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/lib/build.js b/lib/build.js index a41057ae9..5a32af938 100644 --- a/lib/build.js +++ b/lib/build.js @@ -219,9 +219,9 @@ module.exports.run = function (buildOpts) { events.emit('log', `\tPlatform: ${buildOpts.device ? 'device' : 'emulator'}`); events.emit('log', `\tTarget: ${emulatorTarget}`); - const buildOutputDir = path.join(projectPath, 'build', (buildOpts.device ? 'device' : 'emulator')); + const buildOutputDir = path.join(projectPath, 'build', `${configuration}-${(buildOpts.device ? 'iphoneos' : 'iphonesimulator')}`); - // remove the build/device folder before building + // remove the build output folder before building fs.removeSync(buildOutputDir); const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, emulatorTarget, buildOpts); @@ -264,7 +264,8 @@ module.exports.run = function (buildOpts) { const exportOptionsPlist = plist.build(exportOptions); const exportOptionsPath = path.join(projectPath, 'exportOptions.plist'); - const buildOutputDir = path.join(projectPath, 'build', 'device'); + const configuration = buildOpts.release ? 'Release' : 'Debug'; + const buildOutputDir = path.join(projectPath, 'build', `${configuration}-iphoneos`); function checkSystemRuby () { const ruby_cmd = which.sync('ruby', { nothrow: true }); @@ -346,10 +347,16 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, emulatorTar '-archivePath', customArgs.archivePath || `${projectName}.xcarchive` ]; buildActions = ['archive']; - settings = [ - customArgs.configuration_build_dir || `CONFIGURATION_BUILD_DIR=${path.join(projectPath, 'build', 'device')}`, - customArgs.shared_precomps_dir || `SHARED_PRECOMPS_DIR=${path.join(projectPath, 'build', 'sharedpch')}` - ]; + settings = []; + + if (customArgs.configuration_build_dir) { + settings.push(customArgs.configuration_build_dir); + } + + if (customArgs.shared_precomps_dir) { + settings.push(customArgs.shared_precomps_dir); + } + // Add other matched flags to otherFlags to let xcodebuild present an appropriate error. // This is preferable to just ignoring the flags that the user has passed in. if (customArgs.sdk) { @@ -370,17 +377,23 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, emulatorTar } } else { // emulator options = [ - '-workspace', customArgs.project || `${projectName}.xcworkspace`, + '-workspace', customArgs.workspace || `${projectName}.xcworkspace`, '-scheme', customArgs.scheme || projectName, '-configuration', customArgs.configuration || configuration, '-sdk', customArgs.sdk || 'iphonesimulator', '-destination', customArgs.destination || `platform=iOS Simulator,name=${emulatorTarget}` ]; buildActions = ['build']; - settings = [ - customArgs.configuration_build_dir || `CONFIGURATION_BUILD_DIR=${path.join(projectPath, 'build', 'emulator')}`, - customArgs.shared_precomps_dir || `SHARED_PRECOMPS_DIR=${path.join(projectPath, 'build', 'sharedpch')}` - ]; + settings = [`SYMROOT=${path.join(projectPath, 'build')}`]; + + if (customArgs.configuration_build_dir) { + settings.push(customArgs.configuration_build_dir); + } + + if (customArgs.shared_precomps_dir) { + settings.push(customArgs.shared_precomps_dir); + } + // Add other matched flags to otherFlags to let xcodebuild present an appropriate error. // This is preferable to just ignoring the flags that the user has passed in. if (customArgs.archivePath) { diff --git a/lib/run.js b/lib/run.js index 678bd130b..aff907d77 100644 --- a/lib/run.js +++ b/lib/run.js @@ -42,6 +42,7 @@ module.exports.run = function (runOptions) { } let useDevice = !!runOptions.device; + const configuration = runOptions.release ? 'Release' : 'Debug'; return require('./listDevices').run() .then(devices => { @@ -60,8 +61,8 @@ module.exports.run = function (runOptions) { } }).then(() => build.findXCodeProjectIn(projectPath)) .then(projectName => { - let appPath = path.join(projectPath, 'build', 'emulator', `${projectName}.app`); - const buildOutputDir = path.join(projectPath, 'build', 'device'); + let appPath = path.join(projectPath, 'build', `${configuration}-iphonesimulator`, `${projectName}.app`); + const buildOutputDir = path.join(projectPath, 'build', `${configuration}-iphoneos`); // select command to run and arguments depending whether // we're running on device/emulator @@ -91,7 +92,7 @@ module.exports.run = function (runOptions) { }) .then( () => { - appPath = path.join(projectPath, 'build', 'device', `${projectName}.app`); + appPath = path.join(projectPath, 'build', `${configuration}-iphoneos`, `${projectName}.app`); let extraArgs = []; if (runOptions.argv) { // argv.slice(2) removes node and run.js, filterSupportedArgs removes the run.js args diff --git a/tests/spec/unit/build.spec.js b/tests/spec/unit/build.spec.js index 1f855b167..0b1a94918 100644 --- a/tests/spec/unit/build.spec.js +++ b/tests/spec/unit/build.spec.js @@ -43,11 +43,9 @@ describe('build', () => { 'generic/platform=iOS', '-archivePath', 'TestProjectName.xcarchive', - 'archive', - `CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'device')}`, - `SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}` + 'archive' ]); - expect(args.length).toEqual(13); + expect(args.length).toEqual(11); }); it('should generate appropriate args if buildFlags are passed in', () => { @@ -93,11 +91,9 @@ describe('build', () => { 'generic/platform=iOS', '-archivePath', 'TestProjectName.xcarchive', - 'archive', - `CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'device')}`, - `SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}` + 'archive' ]); - expect(args.length).toEqual(13); + expect(args.length).toEqual(11); }); it('should generate appropriate args for simulator', () => { @@ -114,10 +110,42 @@ describe('build', () => { '-destination', 'platform=iOS Simulator,name=iPhone 5s', 'build', - `CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'emulator')}`, - `SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}` + `SYMROOT=${path.join(testProjectPath, 'build')}` ]); - expect(args.length).toEqual(13); + expect(args.length).toEqual(12); + }); + + it('should generate appropriate args for simulator if buildFlags are passed in', () => { + const buildFlags = [ + '-workspace TestWorkspaceFlag', + '-scheme TestSchemeFlag', + '-configuration TestConfigurationFlag', + '-destination TestDestinationFlag', + '-archivePath TestArchivePathFlag', + 'CONFIGURATION_BUILD_DIR=TestConfigBuildDirFlag', + 'SHARED_PRECOMPS_DIR=TestSharedPrecompsDirFlag' + ]; + + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', 'iPhone 5s', { device: false, buildFlag: buildFlags }); + expect(args).toEqual([ + '-workspace', + 'TestWorkspaceFlag', + '-scheme', + 'TestSchemeFlag', + '-configuration', + 'TestConfigurationFlag', + '-sdk', + 'iphonesimulator', + '-destination', + 'TestDestinationFlag', + 'build', + `SYMROOT=${path.join(testProjectPath, 'build')}`, + 'CONFIGURATION_BUILD_DIR=TestConfigBuildDirFlag', + 'SHARED_PRECOMPS_DIR=TestSharedPrecompsDirFlag', + '-archivePath', + 'TestArchivePathFlag' + ]); + expect(args.length).toEqual(16); }); it('should add matched flags that are not overriding for device', () => { @@ -136,12 +164,10 @@ describe('build', () => { '-archivePath', 'TestProjectName.xcarchive', 'archive', - `CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'device')}`, - `SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}`, '-sdk', 'TestSdkFlag' ]); - expect(args.length).toEqual(15); + expect(args.length).toEqual(13); }); it('should add matched flags that are not overriding for simulator', () => { @@ -160,12 +186,11 @@ describe('build', () => { '-destination', 'platform=iOS Simulator,name=iPhone 5s', 'build', - `CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'emulator')}`, - `SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}`, + `SYMROOT=${path.join(testProjectPath, 'build')}`, '-archivePath', 'TestArchivePathFlag' ]); - expect(args.length).toEqual(15); + expect(args.length).toEqual(14); }); it('should generate appropriate args for automatic provisioning', () => { @@ -196,11 +221,9 @@ describe('build', () => { '12345', '-authenticationKeyIssuerID', '00000000-0000-0000-0000-000000000000', - 'archive', - `CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'device')}`, - `SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}` + 'archive' ]); - expect(args.length).toEqual(20); + expect(args.length).toEqual(18); }); });