Skip to content

Instantly share code, notes, and snippets.

@tabrindle
Created July 27, 2017 14:54
Show Gist options
  • Save tabrindle/d3ac6c264431ee77319733c5307e5a61 to your computer and use it in GitHub Desktop.
Save tabrindle/d3ac6c264431ee77319733c5307e5a61 to your computer and use it in GitHub Desktop.
add xcpretty optionally to ios build in react native
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const child_process = require('child_process');
const fs = require('fs');
const path = require('path');
const findXcodeProject = require('./findXcodeProject');
const parseIOSDevicesList = require('./parseIOSDevicesList');
const findMatchingSimulator = require('./findMatchingSimulator');
const getBuildPath = function(configuration = 'Debug', appName, isDevice) {
return `build/Build/Products/${configuration}-${isDevice ? 'iphoneos' : 'iphonesimulator'}/${appName}.app`;
};
const xcprettyAvailable = function() {
try {
child_process.execSync('xcpretty --version', {
stdio: [ 0, 'pipe', 'ignore', ]
});
} catch (error) {
console.log(error)
return false;
}
return true;
};
function runIOS(argv, config, args) {
process.chdir(args.projectPath);
const xcodeProject = findXcodeProject(fs.readdirSync('.'));
if (!xcodeProject) {
throw new Error('Could not find Xcode project files in ios folder');
}
const inferredSchemeName = path.basename(xcodeProject.name, path.extname(xcodeProject.name));
const scheme = args.scheme || inferredSchemeName;
console.log(`Found Xcode ${xcodeProject.isWorkspace ? 'workspace' : 'project'} ${xcodeProject.name}`);
const devices = parseIOSDevicesList(
child_process.execFileSync('xcrun', ['instruments', '-s'], {encoding: 'utf8'})
);
if (args.device) {
const selectedDevice = matchingDevice(devices, args.device);
if (selectedDevice){
return runOnDevice(selectedDevice, scheme, xcodeProject, args.configuration, args.packager);
} else {
if (devices){
console.log('Could not find device with the name: "' + args.device + '".');
console.log('Choose one of the following:');
printFoundDevices(devices);
} else {
console.log('No iOS devices connected.');
}
}
} else if (args.udid) {
return runOnDeviceByUdid(args, scheme, xcodeProject, devices);
} else {
return runOnSimulator(xcodeProject, args, inferredSchemeName, scheme);
}
}
function runOnDeviceByUdid(args, scheme, xcodeProject, devices) {
const selectedDevice = matchingDeviceByUdid(devices, args.udid);
if (selectedDevice){
return runOnDevice(selectedDevice, scheme, xcodeProject, args.configuration, args.packager);
} else {
if (devices){
console.log('Could not find device with the udid: "' + args.udid + '".');
console.log('Choose one of the following:');
printFoundDevices(devices);
} else {
console.log('No iOS devices connected.');
}
}
}
function runOnSimulator(xcodeProject, args, inferredSchemeName, scheme){
return new Promise((resolve) => {
try {
var simulators = JSON.parse(
child_process.execFileSync('xcrun', ['simctl', 'list', '--json', 'devices'], {encoding: 'utf8'})
);
} catch (e) {
throw new Error('Could not parse the simulator list output');
}
const selectedSimulator = findMatchingSimulator(simulators, args.simulator);
if (!selectedSimulator) {
throw new Error(`Could not find ${args.simulator} simulator`);
}
const simulatorFullName = formattedDeviceName(selectedSimulator);
console.log(`Launching ${simulatorFullName}...`);
try {
child_process.spawnSync('xcrun', ['instruments', '-w', selectedSimulator.udid]);
} catch (e) {
// instruments always fail with 255 because it expects more arguments,
// but we want it to only launch the simulator
}
resolve(selectedSimulator.udid)
})
.then((udid) => buildProject(xcodeProject, udid, scheme, args.configuration, args.packager))
.then((appName) => {
if (!appName) {
appName = inferredSchemeName;
}
let appPath = getBuildPath(args.configuration, appName);
console.log(`Installing ${appPath}`);
child_process.spawnSync('xcrun', ['simctl', 'install', 'booted', appPath], {stdio: 'inherit'});
const bundleID = child_process.execFileSync(
'/usr/libexec/PlistBuddy',
['-c', 'Print:CFBundleIdentifier', path.join(appPath, 'Info.plist')],
{encoding: 'utf8'}
).trim();
console.log(`Launching ${bundleID}`);
child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'});
})
}
function runOnDevice(selectedDevice, scheme, xcodeProject, configuration, launchPackager) {
return buildProject(xcodeProject, selectedDevice.udid, scheme, configuration, launchPackager)
.then((appName) => {
if (!appName) {
appName = scheme;
}
const iosDeployInstallArgs = [
'--bundle', getBuildPath(configuration, appName, true),
'--id' , selectedDevice.udid,
'--justlaunch'
];
console.log(`installing and launching your app on ${selectedDevice.name}...`);
const iosDeployOutput = child_process.spawnSync('ios-deploy', iosDeployInstallArgs, {encoding: 'utf8'});
if (iosDeployOutput.error) {
console.log('');
console.log('** INSTALLATION FAILED **');
console.log('Make sure you have ios-deploy installed globally.');
console.log('(e.g "npm install -g ios-deploy")');
} else {
console.log('** INSTALLATION SUCCEEDED **');
}
});
}
function buildProject(xcodeProject, udid, scheme, configuration = 'Debug', launchPackager = false) {
return new Promise((resolve,reject) =>
{
var xcodebuildArgs = [
xcodeProject.isWorkspace ? '-workspace' : '-project', xcodeProject.name,
'-configuration', configuration,
'-scheme', scheme,
'-destination', `id=${udid}`,
'-derivedDataPath', 'build',
];
console.log(`Building using "xcodebuild ${xcodebuildArgs.join(' ')}"`);
const xcpretty = xcprettyAvailable();
if (xcpretty) {
var xcprettyProcess = child_process.spawn('xcpretty', [], { stdio: ['pipe', process.stdout, process.stderr] })
}
const buildProcess = child_process.spawn('xcodebuild', xcodebuildArgs, getProcessOptions(launchPackager));
let buildOutput = "";
buildProcess.stdout.on('data', function(data) {
buildOutput += data.toString();
if (xcpretty) {
xcprettyProcess.stdin.write(data);
} else {
console.log(data.toString());
}
});
buildProcess.stderr.on('data', function(data) {
console.error(data.toString());
});
buildProcess.on('close', function(code) {
if (xcpretty) {
xcprettyProcess.stdin.end();
}
//FULL_PRODUCT_NAME is the actual file name of the app, which actually comes from the Product Name in the build config, which does not necessary match a scheme name, example output line: export FULL_PRODUCT_NAME="Super App Dev.app"
let productNameMatch = /export FULL_PRODUCT_NAME="?(.+).app"?$/.exec(buildOutput);
if (productNameMatch && productNameMatch.length && productNameMatch.length > 1) {
return resolve(productNameMatch[1]);//0 is the full match, 1 is the app name
}
return buildProcess.error? reject(error) : resolve();
});
});
}
function matchingDevice(devices, deviceName) {
if (deviceName === true && devices.length === 1)
{
console.log(`Using first available device ${devices[0].name} due to lack of name supplied.`)
return devices[0];
}
for (let i = devices.length - 1; i >= 0; i--) {
if (devices[i].name === deviceName || formattedDeviceName(devices[i]) === deviceName) {
return devices[i];
}
}
}
function matchingDeviceByUdid(devices, udid) {
for (let i = devices.length - 1; i >= 0; i--) {
if (devices[i].udid === udid) {
return devices[i];
}
}
}
function formattedDeviceName(simulator) {
return `${simulator.name} (${simulator.version})`;
}
function printFoundDevices(devices){
for (let i = devices.length - 1; i >= 0; i--) {
console.log(devices[i].name + ' Udid: ' + devices[i].udid);
}
}
function getProcessOptions(launchPackager) {
if (launchPackager) {
return {};
}
return {
env: Object.assign({}, process.env, { RCT_NO_LAUNCH_PACKAGER: true }),
};
}
module.exports = {
name: 'run-ios',
description: 'builds your app and starts it on iOS simulator',
func: runIOS,
examples: [
{
desc: 'Run on a different simulator, e.g. iPhone 5',
cmd: 'react-native run-ios --simulator "iPhone 5"',
},
{
desc: 'Pass a non-standard location of iOS directory',
cmd: 'react-native run-ios --project-path "./app/ios"',
},
{
desc: "Run on a connected device, e.g. Max's iPhone",
cmd: 'react-native run-ios --device "Max\'s iPhone"',
},
],
options: [{
command: '--simulator [string]',
description: 'Explicitly set simulator to use',
default: 'iPhone 6',
} , {
command: '--configuration [string]',
description: 'Explicitly set the scheme configuration to use',
} , {
command: '--scheme [string]',
description: 'Explicitly set Xcode scheme to use',
}, {
command: '--project-path [string]',
description: 'Path relative to project root where the Xcode project '
+ '(.xcodeproj) lives. The default is \'ios\'.',
default: 'ios',
}, {
command: '--device [string]',
description: 'Explicitly set device to use by name. The value is not required if you have a single device connected.',
}, {
command: '--udid [string]',
description: 'Explicitly set device to use by udid',
}, {
command: '--no-packager',
description: 'Do not launch packager while building',
}, {
command: '--pretty-output',
description: 'Optionally use xcpretty if installed',
}],
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment