GT2/GT2-iOS/node_modules/xdl/build/detach/IosShellApp.js.flow

278 lines
9.3 KiB
Plaintext

// Copyright 2015-present 650 Industries. All rights reserved.
'use strict';
import fs from 'fs-extra';
import path from 'path';
import rimraf from 'rimraf';
import { getManifestAsync, spawnAsync, spawnAsyncThrowError } from './ExponentTools';
import * as IosNSBundle from './IosNSBundle';
import * as IosWorkspace from './IosWorkspace';
import StandaloneBuildFlags from './StandaloneBuildFlags';
import StandaloneContext from './StandaloneContext';
function _validateCLIArgs(args) {
args.type = args.type || 'archive';
args.configuration = args.configuration || 'Release';
args.verbose = args.verbose || false;
switch (args.type) {
case 'simulator': {
if (args.configuration !== 'Debug' && args.configuration !== 'Release') {
throw new Error(`Unsupported build configuration ${args.configuration}`);
}
break;
}
case 'archive': {
if (args.configuration !== 'Release') {
throw new Error('Release is the only supported configuration when archiving');
}
break;
}
default: {
throw new Error(`Unsupported build type ${args.type}`);
}
}
switch (args.action) {
case 'configure': {
if (!args.url) {
throw new Error('Must run with `--url MANIFEST_URL`');
}
if (!args.sdkVersion) {
throw new Error('Must run with `--sdkVersion SDK_VERSION`');
}
if (!args.archivePath) {
throw new Error(
'Need to provide --archivePath <path to existing archive for configuration>'
);
}
break;
}
case 'build': {
break;
}
default: {
throw new Error(`Unsupported build action ${args.action}`);
}
}
return args;
}
/**
* Build the iOS workspace at the given path.
* @return the path to the resulting build artifact
*/
async function _buildAsync(
projectName,
workspacePath,
configuration,
type,
relativeBuildDestination,
verbose
) {
let buildCmd, pathToArtifact;
const buildDest = `${relativeBuildDestination}-${type}`;
if (type === 'simulator') {
buildCmd = `xcodebuild -workspace ${projectName}.xcworkspace -scheme ${projectName} -sdk iphonesimulator -configuration ${configuration} -derivedDataPath ${buildDest} CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ARCHS="i386 x86_64" ONLY_ACTIVE_ARCH=NO | xcpretty`;
pathToArtifact = path.join(
buildDest,
'Build',
'Products',
`${configuration}-iphonesimulator`,
`${projectName}.app`
);
} else if (type === 'archive') {
buildCmd = `xcodebuild -workspace ${projectName}.xcworkspace -scheme ${projectName} -sdk iphoneos -destination generic/platform=iOS -configuration ${configuration} archive -derivedDataPath ${buildDest} -archivePath ${buildDest}/${projectName}.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty`;
pathToArtifact = path.join(buildDest, `${projectName}.xcarchive`);
} else {
throw new Error(`Unsupported build type: ${type}`);
}
console.log(`Building iOS workspace at ${workspacePath} to ${buildDest}:\n`);
console.log(buildCmd);
if (!verbose) {
console.log(
'\nxcodebuild is running. Logging errors only. To see full output, use --verbose 1...'
);
}
await spawnAsyncThrowError(buildCmd, null, {
// only stderr
stdio: verbose ? 'inherit' : ['ignore', 'ignore', 'inherit'],
cwd: workspacePath,
shell: true,
});
return path.resolve(workspacePath, pathToArtifact);
}
async function _podInstallAsync(workspacePath, isRepoUpdateEnabled) {
// ensure pods are clean
const pathsToClean = [path.join(workspacePath, 'Pods'), path.join(workspacePath, 'Podfile.lock')];
pathsToClean.forEach(path => {
if (fs.existsSync(path)) {
rimraf.sync(path);
}
});
// install
let cocoapodsArgs = ['install'];
if (isRepoUpdateEnabled) {
cocoapodsArgs.push('--repo-update');
}
console.log('Installing iOS workspace dependencies...');
console.log(`pod ${cocoapodsArgs.join(' ')}`);
await spawnAsyncThrowError('pod', cocoapodsArgs, {
stdio: 'inherit',
cwd: workspacePath,
});
}
async function _createStandaloneContextAsync(args) {
// right now we only ever build a single detached workspace for service contexts.
// TODO: support multiple different pod configurations, assemble a cache of those builds.
const expoSourcePath = '../ios';
const workspaceSourcePath = path.join(
expoSourcePath,
'..',
'shellAppWorkspaces',
'ios',
'default'
);
let { privateConfigFile } = args;
let privateConfig;
if (privateConfigFile) {
let privateConfigContents = await fs.readFile(privateConfigFile, 'utf8');
privateConfig = JSON.parse(privateConfigContents);
}
let manifest;
if (args.action === 'configure') {
const { url, sdkVersion, releaseChannel } = args;
manifest = await getManifestAsync(url, {
'Exponent-SDK-Version': sdkVersion,
'Exponent-Platform': 'ios',
'Expo-Release-Channel': releaseChannel ? releaseChannel : 'default',
});
}
const buildFlags = StandaloneBuildFlags.createIos(args.configuration, {
workspaceSourcePath,
appleTeamId: args.appleTeamId,
});
const context = StandaloneContext.createServiceContext(
expoSourcePath,
args.archivePath,
manifest,
privateConfig,
buildFlags,
args.url,
args.releaseChannel
);
return context;
}
/**
* possible args:
* @param url manifest url for shell experience
* @param sdkVersion sdk to use when requesting the manifest
* @param releaseChannel channel to pull manifests from, default is 'default'
* @param archivePath path to existing NSBundle to configure
* @param privateConfigFile path to a private config file containing, e.g., private api keys
* @param appleTeamId Apple Developer's account Team ID
* @param output specify the output path of the configured archive (ie) /tmp/my-app-archive-build.xcarchive or /tmp/my-app-ios-build.tar.gz
*/
async function _configureAndCopyShellAppArchiveAsync(args) {
const { output, type } = args;
const context = await _createStandaloneContextAsync(args);
const { projectName } = IosWorkspace.getPaths(context);
await IosNSBundle.configureAsync(context);
if (output) {
// TODO: un-hard-code ExpoKitApp.app
const archiveName = projectName.replace(/[^0-9a-z_\-\.]/gi, '_');
const appReleasePath = path.resolve(path.join(`${context.data.archivePath}`, '..'));
if (type === 'simulator') {
await spawnAsync(
`mv ExpoKitApp.app ${archiveName}.app && tar -czvf ${output} ${archiveName}.app`,
null,
{
stdio: 'inherit',
cwd: appReleasePath,
shell: true,
}
);
} else if (type === 'archive') {
await spawnAsync('/bin/mv', [`ExpoKitApp.xcarchive`, output], {
stdio: 'inherit',
cwd: `${context.data.archivePath}/../../../..`,
});
}
}
}
async function _createShellAppWorkspaceAsync(context, skipRepoUpdate) {
if (fs.existsSync(context.build.ios.workspaceSourcePath)) {
console.log(`Removing existing workspace at ${context.build.ios.workspaceSourcePath}...`);
try {
rimraf.sync(context.build.ios.workspaceSourcePath);
} catch (_) {}
}
await IosWorkspace.createDetachedAsync(context);
await _podInstallAsync(context.build.ios.workspaceSourcePath, !skipRepoUpdate);
}
/**
* possible args:
* @param configuration StandaloneBuildConfiguration (Debug or Release)
* @param verbose show all xcodebuild output (default false)
* @param reuseWorkspace if true, when building, assume a detached workspace already exists rather than creating a new one.
* @param skipRepoUpdate if true, when building, omit `--repo-update` cocoapods flag.
*/
async function _buildAndCopyShellAppArtifactAsync(args) {
const context = await _createStandaloneContextAsync(args);
const { verbose, type, reuseWorkspace } = args;
const { projectName } = IosWorkspace.getPaths(context);
if (!reuseWorkspace) {
await _createShellAppWorkspaceAsync(context, args.skipRepoUpdate);
}
const pathToArtifact = await _buildAsync(
projectName,
context.build.ios.workspaceSourcePath,
context.build.configuration,
type,
path.relative(context.build.ios.workspaceSourcePath, '../shellAppBase'),
verbose
);
const artifactDestPath = path.join('../shellAppBase-builds', type, context.build.configuration);
console.log(`\nFinished building, copying artifact to ${path.resolve(artifactDestPath)}...`);
if (fs.existsSync(artifactDestPath)) {
await spawnAsyncThrowError('/bin/rm', ['-rf', artifactDestPath]);
}
console.log(`mkdir -p ${artifactDestPath}`);
await spawnAsyncThrowError('/bin/mkdir', ['-p', artifactDestPath]);
console.log(`cp -R ${pathToArtifact} ${artifactDestPath}`);
await spawnAsyncThrowError('/bin/cp', ['-R', pathToArtifact, artifactDestPath]);
}
/**
* possible args in addition to action-specific args:
* @param action
* build - build a binary
* configure - don't build anything, just configure the files in an existing NSBundle
* @param type type of artifact to build or configure (simulator or archive)
*/
async function createIOSShellAppAsync(args) {
args = _validateCLIArgs(args);
if (args.action === 'build') {
await _buildAndCopyShellAppArtifactAsync(args);
} else if (args.action === 'configure') {
await _configureAndCopyShellAppArchiveAsync(args);
}
}
export { createIOSShellAppAsync };