Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

build.js 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. const path = require('path');
  20. const which = require('which');
  21. const {
  22. CordovaError,
  23. events,
  24. superspawn: { spawn }
  25. } = require('cordova-common');
  26. const fs = require('fs-extra');
  27. const plist = require('plist');
  28. const util = require('util');
  29. const check_reqs = require('./check_reqs');
  30. const projectFile = require('./projectFile');
  31. // These are regular expressions to detect if the user is changing any of the built-in xcodebuildArgs
  32. /* eslint-disable no-useless-escape */
  33. const buildFlagMatchers = {
  34. workspace: /^\-workspace\s*(.*)/,
  35. scheme: /^\-scheme\s*(.*)/,
  36. configuration: /^\-configuration\s*(.*)/,
  37. sdk: /^\-sdk\s*(.*)/,
  38. destination: /^\-destination\s*(.*)/,
  39. archivePath: /^\-archivePath\s*(.*)/,
  40. configuration_build_dir: /^(CONFIGURATION_BUILD_DIR=.*)/,
  41. shared_precomps_dir: /^(SHARED_PRECOMPS_DIR=.*)/
  42. };
  43. /* eslint-enable no-useless-escape */
  44. /**
  45. * Creates a project object (see projectFile.js/parseProjectFile) from
  46. * a project path and name
  47. *
  48. * @param {*} projectPath
  49. * @param {*} projectName
  50. */
  51. function createProjectObject (projectPath, projectName) {
  52. const locations = {
  53. root: projectPath,
  54. pbxproj: path.join(projectPath, `${projectName}.xcodeproj`, 'project.pbxproj')
  55. };
  56. return projectFile.parse(locations);
  57. }
  58. /**
  59. * Returns a promise that resolves to the default simulator target; the logic here
  60. * matches what `cordova emulate ios` does.
  61. *
  62. * The return object has two properties: `name` (the Xcode destination name),
  63. * `identifier` (the simctl identifier), and `simIdentifier` (essentially the cordova emulate target)
  64. *
  65. * @return {Promise}
  66. */
  67. function getDefaultSimulatorTarget () {
  68. return require('./listEmulatorBuildTargets').run()
  69. .then(emulators => {
  70. let targetEmulator;
  71. if (emulators.length > 0) {
  72. targetEmulator = emulators[0];
  73. }
  74. emulators.forEach(emulator => {
  75. if (emulator.name.indexOf('iPhone') === 0) {
  76. targetEmulator = emulator;
  77. }
  78. });
  79. return targetEmulator;
  80. });
  81. }
  82. module.exports.run = buildOpts => {
  83. let emulatorTarget = '';
  84. const projectPath = path.join(__dirname, '..', '..');
  85. let projectName = '';
  86. buildOpts = buildOpts || {};
  87. if (buildOpts.debug && buildOpts.release) {
  88. return Promise.reject(new CordovaError('Cannot specify "debug" and "release" options together.'));
  89. }
  90. if (buildOpts.device && buildOpts.emulator) {
  91. return Promise.reject(new CordovaError('Cannot specify "device" and "emulator" options together.'));
  92. }
  93. if (buildOpts.buildConfig) {
  94. if (!fs.existsSync(buildOpts.buildConfig)) {
  95. return Promise.reject(new CordovaError(`Build config file does not exist: ${buildOpts.buildConfig}`));
  96. }
  97. events.emit('log', `Reading build config file: ${path.resolve(buildOpts.buildConfig)}`);
  98. const contents = fs.readFileSync(buildOpts.buildConfig, 'utf-8');
  99. const buildConfig = JSON.parse(contents.replace(/^\ufeff/, '')); // Remove BOM
  100. if (buildConfig.ios) {
  101. const buildType = buildOpts.release ? 'release' : 'debug';
  102. const config = buildConfig.ios[buildType];
  103. if (config) {
  104. ['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile', 'developmentTeam', 'packageType', 'buildFlag', 'iCloudContainerEnvironment', 'automaticProvisioning'].forEach(
  105. key => {
  106. buildOpts[key] = buildOpts[key] || config[key];
  107. });
  108. }
  109. }
  110. }
  111. return require('./listDevices').run()
  112. .then(devices => {
  113. if (devices.length > 0 && !(buildOpts.emulator)) {
  114. // we also explicitly set device flag in options as we pass
  115. // those parameters to other api (build as an example)
  116. buildOpts.device = true;
  117. return check_reqs.check_ios_deploy();
  118. }
  119. }).then(() => {
  120. // CB-12287: Determine the device we should target when building for a simulator
  121. if (!buildOpts.device) {
  122. let newTarget = buildOpts.target || '';
  123. if (newTarget) {
  124. // only grab the device name, not the runtime specifier
  125. newTarget = newTarget.split(',')[0];
  126. }
  127. // a target was given to us, find the matching Xcode destination name
  128. const promise = require('./listEmulatorBuildTargets').targetForSimIdentifier(newTarget);
  129. return promise.then(theTarget => {
  130. if (!theTarget) {
  131. return getDefaultSimulatorTarget().then(defaultTarget => {
  132. emulatorTarget = defaultTarget.name;
  133. events.emit('warn', `No simulator found for "${newTarget}. Falling back to the default target.`);
  134. events.emit('log', `Building for "${emulatorTarget}" Simulator (${defaultTarget.identifier}, ${defaultTarget.simIdentifier}).`);
  135. return emulatorTarget;
  136. });
  137. } else {
  138. emulatorTarget = theTarget.name;
  139. events.emit('log', `Building for "${emulatorTarget}" Simulator (${theTarget.identifier}, ${theTarget.simIdentifier}).`);
  140. return emulatorTarget;
  141. }
  142. });
  143. }
  144. })
  145. .then(() => check_reqs.run())
  146. .then(() => findXCodeProjectIn(projectPath))
  147. .then(name => {
  148. projectName = name;
  149. let extraConfig = '';
  150. if (buildOpts.codeSignIdentity) {
  151. extraConfig += `CODE_SIGN_IDENTITY = ${buildOpts.codeSignIdentity}\n`;
  152. extraConfig += `CODE_SIGN_IDENTITY[sdk=iphoneos*] = ${buildOpts.codeSignIdentity}\n`;
  153. }
  154. if (buildOpts.codeSignResourceRules) {
  155. extraConfig += `CODE_SIGN_RESOURCE_RULES_PATH = ${buildOpts.codeSignResourceRules}\n`;
  156. }
  157. if (buildOpts.provisioningProfile) {
  158. extraConfig += `PROVISIONING_PROFILE = ${buildOpts.provisioningProfile}\n`;
  159. }
  160. if (buildOpts.developmentTeam) {
  161. extraConfig += `DEVELOPMENT_TEAM = ${buildOpts.developmentTeam}\n`;
  162. }
  163. function writeCodeSignStyle (value) {
  164. const project = createProjectObject(projectPath, projectName);
  165. events.emit('verbose', `Set CODE_SIGN_STYLE Build Property to ${value}.`);
  166. project.xcode.updateBuildProperty('CODE_SIGN_STYLE', value);
  167. events.emit('verbose', `Set ProvisioningStyle Target Attribute to ${value}.`);
  168. project.xcode.addTargetAttribute('ProvisioningStyle', value);
  169. project.write();
  170. }
  171. if (buildOpts.provisioningProfile) {
  172. events.emit('verbose', 'ProvisioningProfile build option set, changing project settings to Manual.');
  173. writeCodeSignStyle('Manual');
  174. } else if (buildOpts.automaticProvisioning) {
  175. events.emit('verbose', 'ProvisioningProfile build option NOT set, changing project settings to Automatic.');
  176. writeCodeSignStyle('Automatic');
  177. }
  178. return fs.writeFile(path.join(__dirname, '..', 'build-extras.xcconfig'), extraConfig, 'utf-8');
  179. }).then(() => {
  180. const configuration = buildOpts.release ? 'Release' : 'Debug';
  181. events.emit('log', `Building project: ${path.join(projectPath, `${projectName}.xcworkspace`)}`);
  182. events.emit('log', `\tConfiguration: ${configuration}`);
  183. events.emit('log', `\tPlatform: ${buildOpts.device ? 'device' : 'emulator'}`);
  184. events.emit('log', `\tTarget: ${emulatorTarget}`);
  185. const buildOutputDir = path.join(projectPath, 'build', (buildOpts.device ? 'device' : 'emulator'));
  186. // remove the build/device folder before building
  187. fs.removeSync(buildOutputDir);
  188. const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, buildOpts.device, buildOpts.buildFlag, emulatorTarget, buildOpts.automaticProvisioning);
  189. return spawn('xcodebuild', xcodebuildArgs, { cwd: projectPath, printCommand: true, stdio: 'inherit' });
  190. }).then(() => {
  191. if (!buildOpts.device || buildOpts.noSign) {
  192. return;
  193. }
  194. const project = createProjectObject(projectPath, projectName);
  195. const bundleIdentifier = project.getPackageName();
  196. const exportOptions = { compileBitcode: false, method: 'development' };
  197. if (buildOpts.packageType) {
  198. exportOptions.method = buildOpts.packageType;
  199. }
  200. if (buildOpts.iCloudContainerEnvironment) {
  201. exportOptions.iCloudContainerEnvironment = buildOpts.iCloudContainerEnvironment;
  202. }
  203. if (buildOpts.developmentTeam) {
  204. exportOptions.teamID = buildOpts.developmentTeam;
  205. }
  206. if (buildOpts.provisioningProfile && bundleIdentifier) {
  207. exportOptions.provisioningProfiles = { [bundleIdentifier]: String(buildOpts.provisioningProfile) };
  208. exportOptions.signingStyle = 'manual';
  209. }
  210. if (buildOpts.codeSignIdentity) {
  211. exportOptions.signingCertificate = buildOpts.codeSignIdentity;
  212. }
  213. const exportOptionsPlist = plist.build(exportOptions);
  214. const exportOptionsPath = path.join(projectPath, 'exportOptions.plist');
  215. const buildOutputDir = path.join(projectPath, 'build', 'device');
  216. function checkSystemRuby () {
  217. const ruby_cmd = which.sync('ruby', { nothrow: true });
  218. if (ruby_cmd !== '/usr/bin/ruby') {
  219. events.emit('warn', 'Non-system Ruby in use. This may cause packaging to fail.\n' +
  220. 'If you use RVM, please run `rvm use system`.\n' +
  221. 'If you use chruby, please run `chruby system`.');
  222. }
  223. }
  224. function packageArchive () {
  225. const xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath, buildOpts.automaticProvisioning);
  226. return spawn('xcodebuild', xcodearchiveArgs, { cwd: projectPath, printCommand: true, stdio: 'inherit' });
  227. }
  228. return fs.writeFile(exportOptionsPath, exportOptionsPlist, 'utf-8')
  229. .then(checkSystemRuby)
  230. .then(packageArchive);
  231. });
  232. };
  233. /**
  234. * Searches for first XCode project in specified folder
  235. * @param {String} projectPath Path where to search project
  236. * @return {Promise} Promise either fulfilled with project name or rejected
  237. */
  238. function findXCodeProjectIn (projectPath) {
  239. // 'Searching for Xcode project in ' + projectPath);
  240. const xcodeProjFiles = fs.readdirSync(projectPath).filter(name => path.extname(name) === '.xcodeproj');
  241. if (xcodeProjFiles.length === 0) {
  242. return Promise.reject(new CordovaError(`No Xcode project found in ${projectPath}`));
  243. }
  244. if (xcodeProjFiles.length > 1) {
  245. events.emit('warn', `Found multiple .xcodeproj directories in \n${projectPath}\nUsing first one`);
  246. }
  247. const projectName = path.basename(xcodeProjFiles[0], '.xcodeproj');
  248. return Promise.resolve(projectName);
  249. }
  250. module.exports.findXCodeProjectIn = findXCodeProjectIn;
  251. /**
  252. * Returns array of arguments for xcodebuild
  253. * @param {String} projectName Name of xcode project
  254. * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild
  255. * @param {String} configuration Configuration name: debug|release
  256. * @param {Boolean} isDevice Flag that specify target for package (device/emulator)
  257. * @param {Array} buildFlags
  258. * @param {String} emulatorTarget Target for emulator (rather than default)
  259. * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning
  260. * @return {Array} Array of arguments that could be passed directly to spawn method
  261. */
  262. function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, buildFlags, emulatorTarget, autoProvisioning) {
  263. let options;
  264. let buildActions;
  265. let settings;
  266. const customArgs = {};
  267. customArgs.otherFlags = [];
  268. if (buildFlags) {
  269. if (typeof buildFlags === 'string' || buildFlags instanceof String) {
  270. parseBuildFlag(buildFlags, customArgs);
  271. } else { // buildFlags is an Array of strings
  272. buildFlags.forEach(flag => {
  273. parseBuildFlag(flag, customArgs);
  274. });
  275. }
  276. }
  277. if (isDevice) {
  278. options = [
  279. '-workspace', customArgs.workspace || `${projectName}.xcworkspace`,
  280. '-scheme', customArgs.scheme || projectName,
  281. '-configuration', customArgs.configuration || configuration,
  282. '-destination', customArgs.destination || 'generic/platform=iOS',
  283. '-archivePath', customArgs.archivePath || `${projectName}.xcarchive`
  284. ];
  285. buildActions = ['archive'];
  286. settings = [
  287. customArgs.configuration_build_dir || `CONFIGURATION_BUILD_DIR=${path.join(projectPath, 'build', 'device')}`,
  288. customArgs.shared_precomps_dir || `SHARED_PRECOMPS_DIR=${path.join(projectPath, 'build', 'sharedpch')}`
  289. ];
  290. // Add other matched flags to otherFlags to let xcodebuild present an appropriate error.
  291. // This is preferable to just ignoring the flags that the user has passed in.
  292. if (customArgs.sdk) {
  293. customArgs.otherFlags = customArgs.otherFlags.concat(['-sdk', customArgs.sdk]);
  294. }
  295. if (autoProvisioning) {
  296. options = options.concat(['-allowProvisioningUpdates']);
  297. }
  298. } else { // emulator
  299. options = [
  300. '-workspace', customArgs.project || `${projectName}.xcworkspace`,
  301. '-scheme', customArgs.scheme || projectName,
  302. '-configuration', customArgs.configuration || configuration,
  303. '-sdk', customArgs.sdk || 'iphonesimulator',
  304. '-destination', customArgs.destination || `platform=iOS Simulator,name=${emulatorTarget}`
  305. ];
  306. buildActions = ['build'];
  307. settings = [
  308. customArgs.configuration_build_dir || `CONFIGURATION_BUILD_DIR=${path.join(projectPath, 'build', 'emulator')}`,
  309. customArgs.shared_precomps_dir || `SHARED_PRECOMPS_DIR=${path.join(projectPath, 'build', 'sharedpch')}`
  310. ];
  311. // Add other matched flags to otherFlags to let xcodebuild present an appropriate error.
  312. // This is preferable to just ignoring the flags that the user has passed in.
  313. if (customArgs.archivePath) {
  314. customArgs.otherFlags = customArgs.otherFlags.concat(['-archivePath', customArgs.archivePath]);
  315. }
  316. }
  317. return options.concat(buildActions).concat(settings).concat(customArgs.otherFlags);
  318. }
  319. /**
  320. * Returns array of arguments for xcodebuild
  321. * @param {String} projectName Name of xcode project
  322. * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild
  323. * @param {String} outputPath Output directory to contain the IPA
  324. * @param {String} exportOptionsPath Path to the exportOptions.plist file
  325. * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning
  326. * @return {Array} Array of arguments that could be passed directly to spawn method
  327. */
  328. function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath, autoProvisioning) {
  329. return [
  330. '-exportArchive',
  331. '-archivePath', `${projectName}.xcarchive`,
  332. '-exportOptionsPlist', exportOptionsPath,
  333. '-exportPath', outputPath
  334. ].concat(autoProvisioning ? ['-allowProvisioningUpdates'] : []);
  335. }
  336. function parseBuildFlag (buildFlag, args) {
  337. let matched;
  338. for (const key in buildFlagMatchers) {
  339. const found = buildFlag.match(buildFlagMatchers[key]);
  340. if (found) {
  341. matched = true;
  342. // found[0] is the whole match, found[1] is the first match in parentheses.
  343. args[key] = found[1];
  344. events.emit('warn', util.format('Overriding xcodebuildArg: %s', buildFlag));
  345. }
  346. }
  347. if (!matched) {
  348. // If the flag starts with a '-' then it is an xcodebuild built-in option or a
  349. // user-defined setting. The regex makes sure that we don't split a user-defined
  350. // setting that is wrapped in quotes.
  351. /* eslint-disable no-useless-escape */
  352. if (buildFlag[0] === '-' && !buildFlag.match(/^.*=(\".*\")|(\'.*\')$/)) {
  353. args.otherFlags = args.otherFlags.concat(buildFlag.split(' '));
  354. events.emit('warn', util.format('Adding xcodebuildArg: %s', buildFlag.split(' ')));
  355. } else {
  356. args.otherFlags.push(buildFlag);
  357. events.emit('warn', util.format('Adding xcodebuildArg: %s', buildFlag));
  358. }
  359. }
  360. }
  361. // help/usage function
  362. module.exports.help = function help () {
  363. console.log('');
  364. console.log('Usage: build [--debug | --release] [--archs=\"<list of architectures...>\"]');
  365. console.log(' [--device | --simulator] [--codeSignIdentity=\"<identity>\"]');
  366. console.log(' [--codeSignResourceRules=\"<resourcerules path>\"]');
  367. console.log(' [--developmentTeam=\"<Team ID>\"]');
  368. console.log(' [--provisioningProfile=\"<provisioning profile>\"]');
  369. console.log(' --help : Displays this dialog.');
  370. console.log(' --debug : Builds project in debug mode. (Default)');
  371. console.log(' --release : Builds project in release mode.');
  372. console.log(' -r : Shortcut :: builds project in release mode.');
  373. /* eslint-enable no-useless-escape */
  374. // TODO: add support for building different archs
  375. // console.log(" --archs : Builds project binaries for specific chip architectures (`anycpu`, `arm`, `x86`, `x64`).");
  376. console.log(' --device, --simulator');
  377. console.log(' : Specifies, what type of project to build');
  378. console.log(' --codeSignIdentity : Type of signing identity used for code signing.');
  379. console.log(' --codeSignResourceRules : Path to ResourceRules.plist.');
  380. console.log(' --developmentTeam : New for Xcode 8. The development team (Team ID)');
  381. console.log(' to use for code signing.');
  382. console.log(' --provisioningProfile : UUID of the profile.');
  383. console.log(' --device --noSign : Builds project without application signing.');
  384. console.log('');
  385. console.log('examples:');
  386. console.log(' build ');
  387. console.log(' build --debug');
  388. console.log(' build --release');
  389. console.log(' build --codeSignIdentity="iPhone Distribution" --provisioningProfile="926c2bd6-8de9-4c2f-8407-1016d2d12954"');
  390. // TODO: add support for building different archs
  391. // console.log(" build --release --archs=\"armv7\"");
  392. console.log('');
  393. process.exit(0);
  394. };