Use xcpretty for iOS build output if installed
Summary: iOS/xcodebuild logging output sucks. It's completely unreadable because of how xcodebuild takes arguments. Fortunately a ruby gem has existed to fix this for years: xcpretty. You simply pipe xcodebuilds output to xcpretty and it cleans it up, showing relevant info in a more readable format. This PR implements xcpretty output. It checks to see if xcpretty is in PATH, then uses it by default if it is, and can be disabled using a --verbose cli flag. - Create a project from master - run `react-native run-ios` - Confirm output looks like this: ```tabrindle-mbp:shop-rn tabrindle$ make Scanning 662 folders for symlinks in /Users/tabrindle/Developer/react-native/node_modules (5ms) Found Xcode project Shop.xcodeproj We couldn't boot your defined simulator due to an already booted simulator. We are limited to one simulator launched at a time. Launching iPhone 6 (iOS 10.3)... Building using "xcodebuild -project Shop.xcodeproj -configuration Debug -scheme Shop -destination id=0CD9ABF3-A8E4-43D6-A52B-F437FF0F45A0 -derivedDataPath build" ▸ Building React/yoga [Debug] ▸ Check Dependencies ▸ Building React/double-conversion [Debug] ▸ Check Dependencies ▸ Running script 'Install Third Party' ▸ Building React/third-party [Debug] ▸ Check Dependencies ▸ Building React/jschelpers [Debug] ▸ Check Dependencies ▸ Building React/cxxreact [Debug] ▸ Check Dependencies ▸ Building React/React [Debug] ▸ Check Dependencies ▸ Running script 'Start Packager' ▸ Running script 'Include RCTJSCProfiler' ▸ Building RCTSettings/RCTSettings [Debug] ▸ Check Dependencies ▸ Building BVLinearGradient/BVLinearGradient [Debug] ▸ Check Dependencies ▸ Building RCTActionSheet/RCTActionSheet [Debug] ▸ Check Dependencies ▸ Building RCTLinking/RCTLinking [Debug] ▸ Check Dependencies ▸ Building RCTWebSocket/fishhook [Debug] ▸ Check Dependencies ▸ Building RCTWebSocket/RCTWebSocket [Debug] ▸ Check Dependencies ▸ Building RCTText/RCTText [Debug] ▸ Check Dependencies ▸ Building RCTNetwork/RCTNetwork [Debug] ▸ Check Dependencies ▸ Building RCTAnimation/RCTAnimation [Debug] ▸ Check Dependencies ▸ Building RCTGeolocation/RCTGeolocation [Debug] ▸ Check Dependencies ▸ Building ReactNativeNavigation/ReactNativeNavigation [Debug] ▸ Check Dependencies ▸ Building RNI18n/RNI18n [Debug] ▸ Check Dependencies ▸ Building RCTImage/RCTImage [Debug] ▸ Check Dependencies ▸ Building RNVectorIcons/RNVectorIcons [Debug] ▸ Check Dependencies ▸ Building RCTVibration/RCTVibration [Debug] ▸ Check Dependencies ▸ Building Shop/Shop [Debug] ▸ Check Dependencies ▸ Running script 'Bundle React Native code and images' ▸ Building Shop/ShopTests [Debug] ▸ Check Dependencies ▸ Build Succeeded Installing build/Build/Products/Debug-iphonesimulator/Shop.app Launching com.marketamerica.MAMobile com.marketamerica.MAMobile: 43497``` - run `react-native run-ios --verbose` - confirm output looks as normal Nothing else should be affected. This is simply a developer experience change. If xcpretty is not already installed, nothing will change at all. Many of us using fastlane have been spoiled by this for years. Closes https://github.com/facebook/react-native/pull/15607 Differential Revision: D5740684 Pulled By: shergin fbshipit-source-id: 09ef21414b8b65be92595b19502b843fa943403d
This commit is contained in:
parent
dcac87330c
commit
cb8a116d05
|
@ -18,6 +18,16 @@ const findMatchingSimulator = require('./findMatchingSimulator');
|
||||||
const getBuildPath = function(configuration = 'Debug', appName, isDevice) {
|
const getBuildPath = function(configuration = 'Debug', appName, isDevice) {
|
||||||
return `build/Build/Products/${configuration}-${isDevice ? 'iphoneos' : 'iphonesimulator'}/${appName}.app`;
|
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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
function runIOS(argv, config, args) {
|
function runIOS(argv, config, args) {
|
||||||
if (!fs.existsSync(args.projectPath)) {
|
if (!fs.existsSync(args.projectPath)) {
|
||||||
|
@ -48,7 +58,7 @@ function runIOS(argv, config, args) {
|
||||||
if (args.device) {
|
if (args.device) {
|
||||||
const selectedDevice = matchingDevice(devices, args.device);
|
const selectedDevice = matchingDevice(devices, args.device);
|
||||||
if (selectedDevice) {
|
if (selectedDevice) {
|
||||||
return runOnDevice(selectedDevice, scheme, xcodeProject, args.configuration, args.packager);
|
return runOnDevice(selectedDevice, scheme, xcodeProject, args.configuration, args.packager, args.verbose);
|
||||||
} else {
|
} else {
|
||||||
if (devices && devices.length > 0) {
|
if (devices && devices.length > 0) {
|
||||||
console.log('Could not find device with the name: "' + args.device + '".');
|
console.log('Could not find device with the name: "' + args.device + '".');
|
||||||
|
@ -68,7 +78,7 @@ function runIOS(argv, config, args) {
|
||||||
function runOnDeviceByUdid(args, scheme, xcodeProject, devices) {
|
function runOnDeviceByUdid(args, scheme, xcodeProject, devices) {
|
||||||
const selectedDevice = matchingDeviceByUdid(devices, args.udid);
|
const selectedDevice = matchingDeviceByUdid(devices, args.udid);
|
||||||
if (selectedDevice) {
|
if (selectedDevice) {
|
||||||
return runOnDevice(selectedDevice, scheme, xcodeProject, args.configuration, args.packager);
|
return runOnDevice(selectedDevice, scheme, xcodeProject, args.configuration, args.packager, args.verbose);
|
||||||
} else {
|
} else {
|
||||||
if (devices && devices.length > 0) {
|
if (devices && devices.length > 0) {
|
||||||
console.log('Could not find device with the udid: "' + args.udid + '".');
|
console.log('Could not find device with the udid: "' + args.udid + '".');
|
||||||
|
@ -103,9 +113,9 @@ function runOnSimulator(xcodeProject, args, scheme) {
|
||||||
// instruments always fail with 255 because it expects more arguments,
|
// instruments always fail with 255 because it expects more arguments,
|
||||||
// but we want it to only launch the simulator
|
// but we want it to only launch the simulator
|
||||||
}
|
}
|
||||||
resolve(selectedSimulator.udid)
|
resolve(selectedSimulator.udid);
|
||||||
})
|
})
|
||||||
.then((udid) => buildProject(xcodeProject, udid, scheme, args.configuration, args.packager))
|
.then((udid) => buildProject(xcodeProject, udid, scheme, args.configuration, args.packager, args.verbose))
|
||||||
.then((appName) => {
|
.then((appName) => {
|
||||||
if (!appName) {
|
if (!appName) {
|
||||||
appName = scheme;
|
appName = scheme;
|
||||||
|
@ -122,11 +132,11 @@ function runOnSimulator(xcodeProject, args, scheme) {
|
||||||
|
|
||||||
console.log(`Launching ${bundleID}`);
|
console.log(`Launching ${bundleID}`);
|
||||||
child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'});
|
child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function runOnDevice(selectedDevice, scheme, xcodeProject, configuration, launchPackager) {
|
function runOnDevice(selectedDevice, scheme, xcodeProject, configuration, launchPackager, verbose) {
|
||||||
return buildProject(xcodeProject, selectedDevice.udid, scheme, configuration, launchPackager)
|
return buildProject(xcodeProject, selectedDevice.udid, scheme, configuration, launchPackager, verbose)
|
||||||
.then((appName) => {
|
.then((appName) => {
|
||||||
if (!appName) {
|
if (!appName) {
|
||||||
appName = scheme;
|
appName = scheme;
|
||||||
|
@ -149,7 +159,7 @@ function runOnDevice(selectedDevice, scheme, xcodeProject, configuration, launch
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildProject(xcodeProject, udid, scheme, configuration = 'Debug', launchPackager = false) {
|
function buildProject(xcodeProject, udid, scheme, configuration = 'Debug', launchPackager = false, verbose) {
|
||||||
return new Promise((resolve,reject) =>
|
return new Promise((resolve,reject) =>
|
||||||
{
|
{
|
||||||
var xcodebuildArgs = [
|
var xcodebuildArgs = [
|
||||||
|
@ -160,22 +170,33 @@ function buildProject(xcodeProject, udid, scheme, configuration = 'Debug', launc
|
||||||
'-derivedDataPath', 'build',
|
'-derivedDataPath', 'build',
|
||||||
];
|
];
|
||||||
console.log(`Building using "xcodebuild ${xcodebuildArgs.join(' ')}"`);
|
console.log(`Building using "xcodebuild ${xcodebuildArgs.join(' ')}"`);
|
||||||
|
let xcpretty;
|
||||||
|
if (!verbose) {
|
||||||
|
xcpretty = xcprettyAvailable() && child_process.spawn('xcpretty', [], { stdio: ['pipe', process.stdout, process.stderr] });
|
||||||
|
}
|
||||||
const buildProcess = child_process.spawn('xcodebuild', xcodebuildArgs, getProcessOptions(launchPackager));
|
const buildProcess = child_process.spawn('xcodebuild', xcodebuildArgs, getProcessOptions(launchPackager));
|
||||||
let buildOutput = "";
|
let buildOutput = '';
|
||||||
buildProcess.stdout.on('data', function(data) {
|
buildProcess.stdout.on('data', function(data) {
|
||||||
console.log(data.toString());
|
|
||||||
buildOutput += data.toString();
|
buildOutput += data.toString();
|
||||||
|
if (xcpretty) {
|
||||||
|
xcpretty.stdin.write(data);
|
||||||
|
} else {
|
||||||
|
console.log(data.toString());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
buildProcess.stderr.on('data', function(data) {
|
buildProcess.stderr.on('data', function(data) {
|
||||||
console.error(data.toString());
|
console.error(data.toString());
|
||||||
});
|
});
|
||||||
buildProcess.on('close', function(code) {
|
buildProcess.on('close', function(code) {
|
||||||
|
if (xcpretty) {
|
||||||
|
xcpretty.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"
|
//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"?$/m.exec(buildOutput);
|
let productNameMatch = /export FULL_PRODUCT_NAME="?(.+).app"?$/m.exec(buildOutput);
|
||||||
if (productNameMatch && productNameMatch.length && productNameMatch.length > 1) {
|
if (productNameMatch && productNameMatch.length && productNameMatch.length > 1) {
|
||||||
return resolve(productNameMatch[1]);//0 is the full match, 1 is the app name
|
return resolve(productNameMatch[1]);//0 is the full match, 1 is the app name
|
||||||
}
|
}
|
||||||
return buildProcess.error? reject(buildProcess.error) : resolve();
|
return buildProcess.error ? reject(buildProcess.error) : resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -183,7 +204,7 @@ function buildProject(xcodeProject, udid, scheme, configuration = 'Debug', launc
|
||||||
function matchingDevice(devices, deviceName) {
|
function matchingDevice(devices, deviceName) {
|
||||||
if (deviceName === true && devices.length === 1)
|
if (deviceName === true && devices.length === 1)
|
||||||
{
|
{
|
||||||
console.log(`Using first available device ${devices[0].name} due to lack of name supplied.`)
|
console.log(`Using first available device ${devices[0].name} due to lack of name supplied.`);
|
||||||
return devices[0];
|
return devices[0];
|
||||||
}
|
}
|
||||||
for (let i = devices.length - 1; i >= 0; i--) {
|
for (let i = devices.length - 1; i >= 0; i--) {
|
||||||
|
@ -263,5 +284,8 @@ module.exports = {
|
||||||
}, {
|
}, {
|
||||||
command: '--no-packager',
|
command: '--no-packager',
|
||||||
description: 'Do not launch packager while building',
|
description: 'Do not launch packager while building',
|
||||||
|
}, {
|
||||||
|
command: '--verbose',
|
||||||
|
description: 'Do not use xcpretty even if installed',
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue