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

360 lines
12 KiB
Plaintext

/**
* @flow
*/
import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import rimraf from 'rimraf';
import Api from '../Api';
import {
isDirectory,
rimrafDontThrow,
spawnAsync,
spawnAsyncThrowError,
transformFileContentsAsync,
} from './ExponentTools';
import { renderPodfileAsync } from './IosPodsTools.js';
import * as IosPlist from './IosPlist';
import * as Utils from '../Utils';
import StandaloneContext from './StandaloneContext';
import * as Versions from '../Versions';
async function _getVersionedExpoKitConfigAsync(sdkVersion: string): any {
const versions = await Versions.versionsAsync();
let sdkVersionConfig = versions.sdkVersions[sdkVersion];
if (!sdkVersionConfig) {
if (process.env.EXPO_VIEW_DIR) {
sdkVersionConfig = {};
} else {
throw new Error(`Unsupported SDK version: ${sdkVersion}`);
}
}
const { iosVersion, iosExpoViewUrl } = sdkVersionConfig;
const iosClientVersion = iosVersion ? iosVersion : versions.iosVersion;
return {
iosClientVersion,
iosExpoViewUrl,
};
}
async function _getOrCreateTemplateDirectoryAsync(
context: StandaloneContext,
iosExpoViewUrl: string
) {
if (context.type === 'service') {
return path.join(context.data.expoSourcePath, '..');
} else if (context.type === 'user') {
let expoRootTemplateDirectory;
if (process.env.EXPO_VIEW_DIR) {
// Only for testing
expoRootTemplateDirectory = process.env.EXPO_VIEW_DIR;
} else {
// HEY: if you need other paths into the extracted archive, be sure and include them
// when the archive is generated in `ios/pipeline.js`
expoRootTemplateDirectory = path.join(context.data.projectPath, 'temp-ios-directory');
if (!isDirectory(expoRootTemplateDirectory)) {
mkdirp.sync(expoRootTemplateDirectory);
console.log('Downloading iOS code...');
await Api.downloadAsync(iosExpoViewUrl, expoRootTemplateDirectory, {
extract: true,
});
}
}
return expoRootTemplateDirectory;
}
}
async function _renameAndMoveProjectFilesAsync(
context: StandaloneContext,
projectDirectory: string,
projectName: string
) {
// remove .gitignore, as this actually pertains to internal expo template management
try {
const gitIgnorePath = path.join(projectDirectory, '.gitignore');
if (fs.existsSync(gitIgnorePath)) {
rimraf.sync(gitIgnorePath);
}
} catch (e) {}
const filesToTransform = [
path.join('exponent-view-template.xcodeproj', 'project.pbxproj'),
path.join('exponent-view-template.xcworkspace', 'contents.xcworkspacedata'),
path.join(
'exponent-view-template.xcodeproj',
'xcshareddata',
'xcschemes',
'exponent-view-template.xcscheme'
),
];
let bundleIdentifier;
if (context.type === 'user') {
const exp = context.data.exp;
bundleIdentifier = exp.ios && exp.ios.bundleIdentifier ? exp.ios.bundleIdentifier : null;
if (!bundleIdentifier) {
throw new Error(`Cannot configure an ExpoKit workspace with no iOS bundle identifier.`);
}
} else if (context.type === 'service') {
bundleIdentifier = 'host.exp.Exponent';
}
await Promise.all(
filesToTransform.map(fileName =>
transformFileContentsAsync(path.join(projectDirectory, fileName), fileString => {
return fileString
.replace(/com.getexponent.exponent-view-template/g, bundleIdentifier)
.replace(/exponent-view-template/g, projectName);
})
)
);
// order of this array matters
const filesToMove = [
'exponent-view-template',
path.join(
'exponent-view-template.xcodeproj',
'xcshareddata',
'xcschemes',
'exponent-view-template.xcscheme'
),
'exponent-view-template.xcodeproj',
'exponent-view-template.xcworkspace',
];
filesToMove.forEach(async fileName => {
let destFileName = path.join(path.dirname(fileName), `${projectName}${path.extname(fileName)}`);
await spawnAsyncThrowError('/bin/mv', [
path.join(projectDirectory, fileName),
path.join(projectDirectory, destFileName),
]);
});
return;
}
async function _configureVersionsPlistAsync(
configFilePath: string,
standaloneSdkVersion: string,
isMultipleVersion: boolean
) {
await IosPlist.modifyAsync(configFilePath, 'EXSDKVersions', versionConfig => {
if (isMultipleVersion) {
delete versionConfig.detachedNativeVersions;
// leave versionConfig.sdkVersions unchanged
// because the ExpoKit template already contains the list of supported versions.
} else {
versionConfig.sdkVersions = [standaloneSdkVersion];
versionConfig.detachedNativeVersions = {
shell: standaloneSdkVersion,
kernel: standaloneSdkVersion,
};
}
return versionConfig;
});
}
async function _configureBuildConstantsPlistAsync(
configFilePath: string,
context: StandaloneContext
) {
await IosPlist.modifyAsync(configFilePath, 'EXBuildConstants', constantsConfig => {
constantsConfig.STANDALONE_CONTEXT_TYPE = context.type;
return constantsConfig;
});
return;
}
async function _renderPodfileFromTemplateAsync(
context: StandaloneContext,
expoRootTemplateDirectory: string,
sdkVersion: string,
iosClientVersion: string
) {
const { iosProjectDirectory, projectName } = getPaths(context);
let podfileTemplateFilename;
let podfileSubstitutions: any = {
TARGET_NAME: projectName,
};
let reactNativeDependencyPath;
if (context.type === 'user') {
reactNativeDependencyPath = path.join(context.data.projectPath, 'node_modules', 'react-native');
podfileSubstitutions.EXPOKIT_TAG = `ios/${iosClientVersion}`;
podfileTemplateFilename = 'ExpoKit-Podfile';
} else if (context.type === 'service') {
reactNativeDependencyPath = path.join(
expoRootTemplateDirectory,
'..',
'react-native-lab',
'react-native'
);
podfileSubstitutions.EXPOKIT_PATH = path.relative(
iosProjectDirectory,
expoRootTemplateDirectory
);
podfileSubstitutions.VERSIONED_REACT_NATIVE_PATH = path.relative(
iosProjectDirectory,
path.join(expoRootTemplateDirectory, 'ios', 'versioned-react-native')
);
podfileTemplateFilename = 'ExpoKit-Podfile-multiple-versions';
} else {
throw new Error(`Unsupported context type: ${context.type}`);
}
podfileSubstitutions.REACT_NATIVE_PATH = path.relative(
iosProjectDirectory,
reactNativeDependencyPath
);
// env flags for testing
if (process.env.EXPOKIT_TAG_IOS) {
console.log(`EXPOKIT_TAG_IOS: Using custom ExpoKit iOS tag...`);
podfileSubstitutions.EXPOKIT_TAG = process.env.EXPOKIT_TAG_IOS;
} else if (process.env.EXPO_VIEW_DIR) {
console.log('EXPO_VIEW_DIR: Using local ExpoKit source for iOS...');
podfileSubstitutions.EXPOKIT_PATH = path.relative(
iosProjectDirectory,
process.env.EXPO_VIEW_DIR
);
}
const templatePodfilePath = path.join(
expoRootTemplateDirectory,
'template-files',
'ios',
podfileTemplateFilename
);
await renderPodfileAsync(
templatePodfilePath,
path.join(iosProjectDirectory, 'Podfile'),
podfileSubstitutions,
sdkVersion
);
}
async function createDetachedAsync(context: StandaloneContext) {
let isMultipleVersion, standaloneSdkVersion;
if (context.type === 'user') {
standaloneSdkVersion = context.config.sdkVersion;
isMultipleVersion = false;
} else if (context.type === 'service') {
const { version } = await Versions.newestSdkVersionAsync();
standaloneSdkVersion = version;
isMultipleVersion = true;
}
const { iosProjectDirectory, projectName, supportingDirectory } = getPaths(context);
console.log(`Creating ExpoKit workspace at ${iosProjectDirectory}...`);
const { iosClientVersion, iosExpoViewUrl } = await _getVersionedExpoKitConfigAsync(
standaloneSdkVersion
);
const expoRootTemplateDirectory = await _getOrCreateTemplateDirectoryAsync(
context,
iosExpoViewUrl
);
// copy template workspace
console.log('Moving iOS project files...');
console.log('Attempting to create project directory...');
console.log(`project dir: ${iosProjectDirectory}`);
mkdirp.sync(iosProjectDirectory);
console.log('Created project directory! Copying files:');
await Utils.ncpAsync(
path.join(expoRootTemplateDirectory, 'exponent-view-template', 'ios'),
iosProjectDirectory
);
console.log('Naming iOS project...');
await _renameAndMoveProjectFilesAsync(context, iosProjectDirectory, projectName);
console.log('Configuring iOS dependencies...');
// this configuration must happen prior to build time because it affects which
// native versions of RN we depend on.
await _configureVersionsPlistAsync(supportingDirectory, standaloneSdkVersion, isMultipleVersion);
await _configureBuildConstantsPlistAsync(supportingDirectory, context);
await _renderPodfileFromTemplateAsync(
context,
expoRootTemplateDirectory,
standaloneSdkVersion,
iosClientVersion
);
if (!process.env.EXPO_VIEW_DIR) {
if (context.type === 'user') {
rimrafDontThrow(expoRootTemplateDirectory);
}
await IosPlist.cleanBackupAsync(supportingDirectory, 'EXSDKVersions', false);
}
return;
}
function addDetachedConfigToExp(exp: any, context: StandaloneContext): any {
if (context.type !== 'user') {
console.warn(`Tried to modify exp for a non-user StandaloneContext, ignoring`);
return;
}
if (!exp) {
exp = {};
}
const { supportingDirectory } = getPaths(context);
exp.ios.publishBundlePath = path.relative(
context.data.projectPath,
path.join(supportingDirectory, 'shell-app.bundle')
);
exp.ios.publishManifestPath = path.relative(
context.data.projectPath,
path.join(supportingDirectory, 'shell-app-manifest.json')
);
return exp;
}
/**
* paths returned:
* iosProjectDirectory - root directory of an (uncompiled) xcworkspace and obj-c source tree
* projectName - xcworkspace project name normalized from context.config
* supportingDirectory - location of Info.plist, xib files, etc. during configuration.
* for an unbuilt app this is underneath iosProjectDirectory. for a compiled app it's just
* a path to the flat xcarchive.
* intermediatesDirectory - temporary spot to write whatever files are needed during the
* detach/build process but can be discarded afterward.
*/
function getPaths(context: StandaloneContext) {
let iosProjectDirectory;
let projectName;
let supportingDirectory;
let intermediatesDirectory;
if (context.isAnonymous()) {
projectName = 'ExpoKitApp';
} else if (context.config && context.config.name) {
let projectNameLabel = context.config.name;
projectName = projectNameLabel.replace(/[^a-z0-9_\-]/gi, '-').toLowerCase();
} else {
throw new Error('Cannot configure an Expo project with no name.');
}
if (context.type === 'user') {
iosProjectDirectory = path.join(context.data.projectPath, 'ios');
supportingDirectory = path.join(iosProjectDirectory, projectName, 'Supporting');
} else if (context.type === 'service') {
iosProjectDirectory = context.build.ios.workspaceSourcePath;
if (context.data.archivePath) {
// compiled archive has a flat NSBundle
supportingDirectory = context.data.archivePath;
} else {
supportingDirectory = path.join(iosProjectDirectory, projectName, 'Supporting');
}
} else {
throw new Error(`Unsupported StandaloneContext type: ${context.type}`);
}
// sandbox intermediates directory by workspace so that concurrently operating
// contexts do not interfere with one another.
intermediatesDirectory = path.join(iosProjectDirectory, 'ExpoKitIntermediates');
return {
intermediatesDirectory,
iosProjectDirectory,
projectName,
supportingDirectory,
};
}
export { addDetachedConfigToExp, createDetachedAsync, getPaths };