123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- /**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
- const path = require('path');
- const which = require('which');
- const {
- CordovaError,
- events,
- superspawn: { spawn }
- } = require('cordova-common');
- const fs = require('fs-extra');
- const plist = require('plist');
- const util = require('util');
-
- const check_reqs = require('./check_reqs');
- const projectFile = require('./projectFile');
-
- // These are regular expressions to detect if the user is changing any of the built-in xcodebuildArgs
- /* eslint-disable no-useless-escape */
- const buildFlagMatchers = {
- workspace: /^\-workspace\s*(.*)/,
- scheme: /^\-scheme\s*(.*)/,
- configuration: /^\-configuration\s*(.*)/,
- sdk: /^\-sdk\s*(.*)/,
- destination: /^\-destination\s*(.*)/,
- archivePath: /^\-archivePath\s*(.*)/,
- configuration_build_dir: /^(CONFIGURATION_BUILD_DIR=.*)/,
- shared_precomps_dir: /^(SHARED_PRECOMPS_DIR=.*)/
- };
- /* eslint-enable no-useless-escape */
-
- /**
- * Creates a project object (see projectFile.js/parseProjectFile) from
- * a project path and name
- *
- * @param {*} projectPath
- * @param {*} projectName
- */
- function createProjectObject (projectPath, projectName) {
- const locations = {
- root: projectPath,
- pbxproj: path.join(projectPath, `${projectName}.xcodeproj`, 'project.pbxproj')
- };
-
- return projectFile.parse(locations);
- }
-
- /**
- * Returns a promise that resolves to the default simulator target; the logic here
- * matches what `cordova emulate ios` does.
- *
- * The return object has two properties: `name` (the Xcode destination name),
- * `identifier` (the simctl identifier), and `simIdentifier` (essentially the cordova emulate target)
- *
- * @return {Promise}
- */
- function getDefaultSimulatorTarget () {
- return require('./listEmulatorBuildTargets').run()
- .then(emulators => {
- let targetEmulator;
- if (emulators.length > 0) {
- targetEmulator = emulators[0];
- }
- emulators.forEach(emulator => {
- if (emulator.name.indexOf('iPhone') === 0) {
- targetEmulator = emulator;
- }
- });
- return targetEmulator;
- });
- }
-
- module.exports.run = buildOpts => {
- let emulatorTarget = '';
- const projectPath = path.join(__dirname, '..', '..');
- let projectName = '';
-
- buildOpts = buildOpts || {};
-
- if (buildOpts.debug && buildOpts.release) {
- return Promise.reject(new CordovaError('Cannot specify "debug" and "release" options together.'));
- }
-
- if (buildOpts.device && buildOpts.emulator) {
- return Promise.reject(new CordovaError('Cannot specify "device" and "emulator" options together.'));
- }
-
- if (buildOpts.buildConfig) {
- if (!fs.existsSync(buildOpts.buildConfig)) {
- return Promise.reject(new CordovaError(`Build config file does not exist: ${buildOpts.buildConfig}`));
- }
- events.emit('log', `Reading build config file: ${path.resolve(buildOpts.buildConfig)}`);
- const contents = fs.readFileSync(buildOpts.buildConfig, 'utf-8');
- const buildConfig = JSON.parse(contents.replace(/^\ufeff/, '')); // Remove BOM
- if (buildConfig.ios) {
- const buildType = buildOpts.release ? 'release' : 'debug';
- const config = buildConfig.ios[buildType];
- if (config) {
- ['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile', 'developmentTeam', 'packageType', 'buildFlag', 'iCloudContainerEnvironment', 'automaticProvisioning'].forEach(
- key => {
- buildOpts[key] = buildOpts[key] || config[key];
- });
- }
- }
- }
-
- return require('./listDevices').run()
- .then(devices => {
- if (devices.length > 0 && !(buildOpts.emulator)) {
- // we also explicitly set device flag in options as we pass
- // those parameters to other api (build as an example)
- buildOpts.device = true;
- return check_reqs.check_ios_deploy();
- }
- }).then(() => {
- // CB-12287: Determine the device we should target when building for a simulator
- if (!buildOpts.device) {
- let newTarget = buildOpts.target || '';
-
- if (newTarget) {
- // only grab the device name, not the runtime specifier
- newTarget = newTarget.split(',')[0];
- }
- // a target was given to us, find the matching Xcode destination name
- const promise = require('./listEmulatorBuildTargets').targetForSimIdentifier(newTarget);
- return promise.then(theTarget => {
- if (!theTarget) {
- return getDefaultSimulatorTarget().then(defaultTarget => {
- emulatorTarget = defaultTarget.name;
- events.emit('warn', `No simulator found for "${newTarget}. Falling back to the default target.`);
- events.emit('log', `Building for "${emulatorTarget}" Simulator (${defaultTarget.identifier}, ${defaultTarget.simIdentifier}).`);
- return emulatorTarget;
- });
- } else {
- emulatorTarget = theTarget.name;
- events.emit('log', `Building for "${emulatorTarget}" Simulator (${theTarget.identifier}, ${theTarget.simIdentifier}).`);
- return emulatorTarget;
- }
- });
- }
- })
- .then(() => check_reqs.run())
- .then(() => findXCodeProjectIn(projectPath))
- .then(name => {
- projectName = name;
- let extraConfig = '';
- if (buildOpts.codeSignIdentity) {
- extraConfig += `CODE_SIGN_IDENTITY = ${buildOpts.codeSignIdentity}\n`;
- extraConfig += `CODE_SIGN_IDENTITY[sdk=iphoneos*] = ${buildOpts.codeSignIdentity}\n`;
- }
- if (buildOpts.codeSignResourceRules) {
- extraConfig += `CODE_SIGN_RESOURCE_RULES_PATH = ${buildOpts.codeSignResourceRules}\n`;
- }
- if (buildOpts.provisioningProfile) {
- extraConfig += `PROVISIONING_PROFILE = ${buildOpts.provisioningProfile}\n`;
- }
- if (buildOpts.developmentTeam) {
- extraConfig += `DEVELOPMENT_TEAM = ${buildOpts.developmentTeam}\n`;
- }
-
- function writeCodeSignStyle (value) {
- const project = createProjectObject(projectPath, projectName);
-
- events.emit('verbose', `Set CODE_SIGN_STYLE Build Property to ${value}.`);
- project.xcode.updateBuildProperty('CODE_SIGN_STYLE', value);
- events.emit('verbose', `Set ProvisioningStyle Target Attribute to ${value}.`);
- project.xcode.addTargetAttribute('ProvisioningStyle', value);
-
- project.write();
- }
-
- if (buildOpts.provisioningProfile) {
- events.emit('verbose', 'ProvisioningProfile build option set, changing project settings to Manual.');
- writeCodeSignStyle('Manual');
- } else if (buildOpts.automaticProvisioning) {
- events.emit('verbose', 'ProvisioningProfile build option NOT set, changing project settings to Automatic.');
- writeCodeSignStyle('Automatic');
- }
-
- return fs.writeFile(path.join(__dirname, '..', 'build-extras.xcconfig'), extraConfig, 'utf-8');
- }).then(() => {
- const configuration = buildOpts.release ? 'Release' : 'Debug';
-
- events.emit('log', `Building project: ${path.join(projectPath, `${projectName}.xcworkspace`)}`);
- events.emit('log', `\tConfiguration: ${configuration}`);
- events.emit('log', `\tPlatform: ${buildOpts.device ? 'device' : 'emulator'}`);
- events.emit('log', `\tTarget: ${emulatorTarget}`);
-
- const buildOutputDir = path.join(projectPath, 'build', (buildOpts.device ? 'device' : 'emulator'));
-
- // remove the build/device folder before building
- fs.removeSync(buildOutputDir);
-
- const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, buildOpts.device, buildOpts.buildFlag, emulatorTarget, buildOpts.automaticProvisioning);
- return spawn('xcodebuild', xcodebuildArgs, { cwd: projectPath, printCommand: true, stdio: 'inherit' });
- }).then(() => {
- if (!buildOpts.device || buildOpts.noSign) {
- return;
- }
-
- const project = createProjectObject(projectPath, projectName);
- const bundleIdentifier = project.getPackageName();
- const exportOptions = { compileBitcode: false, method: 'development' };
-
- if (buildOpts.packageType) {
- exportOptions.method = buildOpts.packageType;
- }
-
- if (buildOpts.iCloudContainerEnvironment) {
- exportOptions.iCloudContainerEnvironment = buildOpts.iCloudContainerEnvironment;
- }
-
- if (buildOpts.developmentTeam) {
- exportOptions.teamID = buildOpts.developmentTeam;
- }
-
- if (buildOpts.provisioningProfile && bundleIdentifier) {
- exportOptions.provisioningProfiles = { [bundleIdentifier]: String(buildOpts.provisioningProfile) };
- exportOptions.signingStyle = 'manual';
- }
-
- if (buildOpts.codeSignIdentity) {
- exportOptions.signingCertificate = buildOpts.codeSignIdentity;
- }
-
- const exportOptionsPlist = plist.build(exportOptions);
- const exportOptionsPath = path.join(projectPath, 'exportOptions.plist');
-
- const buildOutputDir = path.join(projectPath, 'build', 'device');
-
- function checkSystemRuby () {
- const ruby_cmd = which.sync('ruby', { nothrow: true });
-
- if (ruby_cmd !== '/usr/bin/ruby') {
- events.emit('warn', 'Non-system Ruby in use. This may cause packaging to fail.\n' +
- 'If you use RVM, please run `rvm use system`.\n' +
- 'If you use chruby, please run `chruby system`.');
- }
- }
-
- function packageArchive () {
- const xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath, buildOpts.automaticProvisioning);
- return spawn('xcodebuild', xcodearchiveArgs, { cwd: projectPath, printCommand: true, stdio: 'inherit' });
- }
-
- return fs.writeFile(exportOptionsPath, exportOptionsPlist, 'utf-8')
- .then(checkSystemRuby)
- .then(packageArchive);
- });
- };
-
- /**
- * Searches for first XCode project in specified folder
- * @param {String} projectPath Path where to search project
- * @return {Promise} Promise either fulfilled with project name or rejected
- */
- function findXCodeProjectIn (projectPath) {
- // 'Searching for Xcode project in ' + projectPath);
- const xcodeProjFiles = fs.readdirSync(projectPath).filter(name => path.extname(name) === '.xcodeproj');
-
- if (xcodeProjFiles.length === 0) {
- return Promise.reject(new CordovaError(`No Xcode project found in ${projectPath}`));
- }
- if (xcodeProjFiles.length > 1) {
- events.emit('warn', `Found multiple .xcodeproj directories in \n${projectPath}\nUsing first one`);
- }
-
- const projectName = path.basename(xcodeProjFiles[0], '.xcodeproj');
- return Promise.resolve(projectName);
- }
-
- module.exports.findXCodeProjectIn = findXCodeProjectIn;
-
- /**
- * Returns array of arguments for xcodebuild
- * @param {String} projectName Name of xcode project
- * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild
- * @param {String} configuration Configuration name: debug|release
- * @param {Boolean} isDevice Flag that specify target for package (device/emulator)
- * @param {Array} buildFlags
- * @param {String} emulatorTarget Target for emulator (rather than default)
- * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning
- * @return {Array} Array of arguments that could be passed directly to spawn method
- */
- function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, buildFlags, emulatorTarget, autoProvisioning) {
- let options;
- let buildActions;
- let settings;
- const customArgs = {};
- customArgs.otherFlags = [];
-
- if (buildFlags) {
- if (typeof buildFlags === 'string' || buildFlags instanceof String) {
- parseBuildFlag(buildFlags, customArgs);
- } else { // buildFlags is an Array of strings
- buildFlags.forEach(flag => {
- parseBuildFlag(flag, customArgs);
- });
- }
- }
-
- if (isDevice) {
- options = [
- '-workspace', customArgs.workspace || `${projectName}.xcworkspace`,
- '-scheme', customArgs.scheme || projectName,
- '-configuration', customArgs.configuration || configuration,
- '-destination', customArgs.destination || 'generic/platform=iOS',
- '-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')}`
- ];
- // 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) {
- customArgs.otherFlags = customArgs.otherFlags.concat(['-sdk', customArgs.sdk]);
- }
-
- if (autoProvisioning) {
- options = options.concat(['-allowProvisioningUpdates']);
- }
- } else { // emulator
- options = [
- '-workspace', customArgs.project || `${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')}`
- ];
- // 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) {
- customArgs.otherFlags = customArgs.otherFlags.concat(['-archivePath', customArgs.archivePath]);
- }
- }
-
- return options.concat(buildActions).concat(settings).concat(customArgs.otherFlags);
- }
-
- /**
- * Returns array of arguments for xcodebuild
- * @param {String} projectName Name of xcode project
- * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild
- * @param {String} outputPath Output directory to contain the IPA
- * @param {String} exportOptionsPath Path to the exportOptions.plist file
- * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning
- * @return {Array} Array of arguments that could be passed directly to spawn method
- */
- function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath, autoProvisioning) {
- return [
- '-exportArchive',
- '-archivePath', `${projectName}.xcarchive`,
- '-exportOptionsPlist', exportOptionsPath,
- '-exportPath', outputPath
- ].concat(autoProvisioning ? ['-allowProvisioningUpdates'] : []);
- }
-
- function parseBuildFlag (buildFlag, args) {
- let matched;
- for (const key in buildFlagMatchers) {
- const found = buildFlag.match(buildFlagMatchers[key]);
- if (found) {
- matched = true;
- // found[0] is the whole match, found[1] is the first match in parentheses.
- args[key] = found[1];
- events.emit('warn', util.format('Overriding xcodebuildArg: %s', buildFlag));
- }
- }
-
- if (!matched) {
- // If the flag starts with a '-' then it is an xcodebuild built-in option or a
- // user-defined setting. The regex makes sure that we don't split a user-defined
- // setting that is wrapped in quotes.
- /* eslint-disable no-useless-escape */
- if (buildFlag[0] === '-' && !buildFlag.match(/^.*=(\".*\")|(\'.*\')$/)) {
- args.otherFlags = args.otherFlags.concat(buildFlag.split(' '));
- events.emit('warn', util.format('Adding xcodebuildArg: %s', buildFlag.split(' ')));
- } else {
- args.otherFlags.push(buildFlag);
- events.emit('warn', util.format('Adding xcodebuildArg: %s', buildFlag));
- }
- }
- }
-
- // help/usage function
- module.exports.help = function help () {
- console.log('');
- console.log('Usage: build [--debug | --release] [--archs=\"<list of architectures...>\"]');
- console.log(' [--device | --simulator] [--codeSignIdentity=\"<identity>\"]');
- console.log(' [--codeSignResourceRules=\"<resourcerules path>\"]');
- console.log(' [--developmentTeam=\"<Team ID>\"]');
- console.log(' [--provisioningProfile=\"<provisioning profile>\"]');
- console.log(' --help : Displays this dialog.');
- console.log(' --debug : Builds project in debug mode. (Default)');
- console.log(' --release : Builds project in release mode.');
- console.log(' -r : Shortcut :: builds project in release mode.');
- /* eslint-enable no-useless-escape */
- // TODO: add support for building different archs
- // console.log(" --archs : Builds project binaries for specific chip architectures (`anycpu`, `arm`, `x86`, `x64`).");
- console.log(' --device, --simulator');
- console.log(' : Specifies, what type of project to build');
- console.log(' --codeSignIdentity : Type of signing identity used for code signing.');
- console.log(' --codeSignResourceRules : Path to ResourceRules.plist.');
- console.log(' --developmentTeam : New for Xcode 8. The development team (Team ID)');
- console.log(' to use for code signing.');
- console.log(' --provisioningProfile : UUID of the profile.');
- console.log(' --device --noSign : Builds project without application signing.');
- console.log('');
- console.log('examples:');
- console.log(' build ');
- console.log(' build --debug');
- console.log(' build --release');
- console.log(' build --codeSignIdentity="iPhone Distribution" --provisioningProfile="926c2bd6-8de9-4c2f-8407-1016d2d12954"');
- // TODO: add support for building different archs
- // console.log(" build --release --archs=\"armv7\"");
- console.log('');
- process.exit(0);
- };
|