Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add build-ios command #1744

Merged
merged 15 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ React Native CLI comes with following commands:
- [`ram-bundle`](#ram-bundle)
- [`run-android`](#run-android)
- [`run-ios`](#run-ios)
- [`build-ios`](#build-ios)
- [`start`](#start)
- [`upgrade`](#upgrade)
- [`profile-hermes`](#profile-hermes)
Expand Down Expand Up @@ -389,6 +390,7 @@ react-native run-ios [options]

Builds your app and starts it on iOS simulator.


#### Options

#### `--simulator <simulator_name>`
Expand Down Expand Up @@ -444,6 +446,76 @@ Default: `process.env.RCT_METRO_PORT || 8081`

#### `--xcconfig <string>`

Explicitly set `xcconfig` to use in build.

#### `--buildFolder <string>`

Location for iOS build artifacts. Corresponds to Xcode's `-derivedDataPath`.

### `build-ios`

Usage:

```sh
react-native build-ios [options]
```

Builds IOS app.

#### Options
#### `--simulator <simulator_name>`

> default: iPhone 14

Explicitly set the simulator to use. Optionally include iOS version between parenthesis at the end to match an exact version, e.g. `"iPhone 6 (10.0)"`.

Notes: If selected simulator does not exist, cli will try to run fallback simulators in following order:

- `iPhone 14`
- `iPhone 13`
- `iPhone 12`
- `iPhone 11`

Notes: `simulator_name` must be a valid iOS simulator name. If in doubt, open your AwesomeApp/ios/AwesomeApp.xcodeproj folder on XCode and unroll the dropdown menu containing the simulator list. The dropdown menu is situated on the right hand side of the play button (top left corner).

Example: this will launch your project directly onto the iPhone XS Max simulator:

```sh
react-native run-ios --simulator "iPhone XS Max"
```

#### `--configuration <string>`

Explicitly set the scheme configuration to use default: 'Debug'.

#### `--scheme <string>`

Explicitly set Xcode scheme to use.

#### `--device [string]`

Explicitly set device to use by name. The value is not required if you have a single device connected.

#### `--udid <string>`

Explicitly set device to use by udid.

#### `--no-packager`

Do not launch packager while building.

#### `--verbose`

Do not use `xcbeautify` or `xcpretty` even if installed.

#### `--port <number>`

Runs packager on specified port.

Default: `process.env.RCT_METRO_PORT || 8081`

#### `--xcconfig <string>`

Explicitly pass `xcconfig` options from the command line.

#### `--buildFolder <string>`
Expand Down
164 changes: 164 additions & 0 deletions packages/cli-platform-ios/src/commands/buildIOS/buildProject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import child_process, {
ChildProcess,
SpawnOptionsWithoutStdio,
} from 'child_process';
import chalk from 'chalk';
import {IOSProjectInfo} from '@react-native-community/cli-types';
import {logger, CLIError} from '@react-native-community/cli-tools';
import {getLoader} from '@react-native-community/cli-tools';

export type BuildFlags = {
mode: string;
packager: boolean;
verbose: boolean;
xcconfig?: string;
buildFolder?: string;
port: number;
terminal: string | undefined;
};

export function buildProject(
xcodeProject: IOSProjectInfo,
udid: string | undefined,
scheme: string,
args: BuildFlags,
): Promise<string> {
return new Promise((resolve, reject) => {
const xcodebuildArgs = [
xcodeProject.isWorkspace ? '-workspace' : '-project',
xcodeProject.name,
...(args.xcconfig ? ['-xcconfig', args.xcconfig] : []),
...(args.buildFolder ? ['-derivedDataPath', args.buildFolder] : []),
'-configuration',
args.mode,
'-scheme',
scheme,
'-destination',
udid
? `id=${udid}`
: args.mode === 'Debug'
? 'generic/platform=iOS Simulator'
: 'generic/platform=iOS',
];
const loader = getLoader();
logger.info(
`Building ${chalk.dim(
`(using "xcodebuild ${xcodebuildArgs.join(' ')}")`,
)}`,
);
let xcodebuildOutputFormatter: ChildProcess | any;
if (!args.verbose) {
if (xcbeautifyAvailable()) {
xcodebuildOutputFormatter = child_process.spawn('xcbeautify', [], {
stdio: ['pipe', process.stdout, process.stderr],
});
} else if (xcprettyAvailable()) {
xcodebuildOutputFormatter = child_process.spawn('xcpretty', [], {
stdio: ['pipe', process.stdout, process.stderr],
});
}
}
const buildProcess = child_process.spawn(
'xcodebuild',
xcodebuildArgs,
getProcessOptions(args),
);
let buildOutput = '';
let errorOutput = '';
buildProcess.stdout.on('data', (data: Buffer) => {
const stringData = data.toString();
buildOutput += stringData;
if (xcodebuildOutputFormatter) {
xcodebuildOutputFormatter.stdin.write(data);
} else {
if (logger.isVerbose()) {
logger.debug(stringData);
} else {
loader.start(
`Building the app${'.'.repeat(buildOutput.length % 10)}`,
);
}
}
});

buildProcess.stderr.on('data', (data: Buffer) => {
errorOutput += data;
});
buildProcess.on('close', (code: number) => {
if (xcodebuildOutputFormatter) {
xcodebuildOutputFormatter.stdin.end();
} else {
loader.stop();
}
if (code !== 0) {
reject(
new CLIError(
`
Failed to build iOS project.

We ran "xcodebuild" command but it exited with error code ${code}. To debug build
logs further, consider building your app with Xcode.app, by opening
${xcodeProject.name}.
`,
xcodebuildOutputFormatter
? undefined
: buildOutput + '\n' + errorOutput,
),
);
return;
}
logger.success('Successfully built the app');
resolve(buildOutput);
});
});
}

function xcbeautifyAvailable() {
try {
child_process.execSync('xcbeautify --version', {
stdio: [0, 'pipe', 'ignore'],
});
} catch (error) {
return false;
}
return true;
}

function xcprettyAvailable() {
try {
child_process.execSync('xcpretty --version', {
stdio: [0, 'pipe', 'ignore'],
});
} catch (error) {
return false;
}
return true;
}

function getProcessOptions({
packager,
terminal,
port,
}: {
packager: boolean;
terminal: string | undefined;
port: number;
}): SpawnOptionsWithoutStdio {
if (packager) {
return {
env: {
...process.env,
RCT_TERMINAL: terminal,
RCT_METRO_PORT: port.toString(),
},
};
}

return {
env: {
...process.env,
RCT_TERMINAL: terminal,
RCT_NO_LAUNCH_PACKAGER: 'true',
},
};
}
Loading