Skip to content

Commit 0a265a5

Browse files
committed
refactor: move installing and launching app logic to separate function
1 parent acdcb2b commit 0a265a5

File tree

5 files changed

+208
-140
lines changed

5 files changed

+208
-140
lines changed
Lines changed: 12 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,13 @@
1-
import child_process from 'child_process';
2-
import {IOSProjectInfo} from '@react-native-community/cli-types';
3-
import {CLIError, logger} from '@react-native-community/cli-tools';
4-
import chalk from 'chalk';
1+
import {CLIError} from '@react-native-community/cli-tools';
2+
import path from 'path';
53

64
export async function getBuildPath(
7-
xcodeProject: IOSProjectInfo,
8-
mode: string,
9-
buildOutput: string,
10-
scheme: string,
11-
target: string | undefined,
5+
buildSettings: any,
126
isCatalyst: boolean = false,
137
) {
14-
const buildSettings = child_process.execFileSync(
15-
'xcodebuild',
16-
[
17-
xcodeProject.isWorkspace ? '-workspace' : '-project',
18-
xcodeProject.name,
19-
'-scheme',
20-
scheme,
21-
'-sdk',
22-
getPlatformName(buildOutput),
23-
'-configuration',
24-
mode,
25-
'-showBuildSettings',
26-
'-json',
27-
],
28-
{encoding: 'utf8'},
29-
);
30-
31-
const {targetBuildDir, executableFolderPath} = await getTargetPaths(
32-
buildSettings,
33-
scheme,
34-
target,
35-
);
8+
const targetBuildDir = buildSettings.TARGET_BUILD_DIR;
9+
const executableFolderPath = buildSettings.EXECUTABLE_FOLDER_PATH;
10+
const fullProductName = buildSettings.FULL_PRODUCT_NAME;
3611

3712
if (!targetBuildDir) {
3813
throw new CLIError('Failed to get the target build directory.');
@@ -42,63 +17,13 @@ export async function getBuildPath(
4217
throw new CLIError('Failed to get the app name.');
4318
}
4419

45-
return `${targetBuildDir}${
46-
isCatalyst ? '-maccatalyst' : ''
47-
}/${executableFolderPath}`;
48-
}
49-
50-
async function getTargetPaths(
51-
buildSettings: string,
52-
scheme: string,
53-
target: string | undefined,
54-
) {
55-
const settings = JSON.parse(buildSettings);
56-
57-
const targets = settings.map(
58-
({target: settingsTarget}: any) => settingsTarget,
59-
);
60-
61-
let selectedTarget = targets[0];
62-
63-
if (target) {
64-
if (!targets.includes(target)) {
65-
logger.info(
66-
`Target ${chalk.bold(target)} not found for scheme ${chalk.bold(
67-
scheme,
68-
)}, automatically selected target ${chalk.bold(selectedTarget)}`,
69-
);
70-
} else {
71-
selectedTarget = target;
72-
}
73-
}
74-
75-
// Find app in all building settings - look for WRAPPER_EXTENSION: 'app',
76-
77-
const targetIndex = targets.indexOf(selectedTarget);
78-
79-
const wrapperExtension =
80-
settings[targetIndex].buildSettings.WRAPPER_EXTENSION;
81-
82-
if (wrapperExtension === 'app') {
83-
return {
84-
targetBuildDir: settings[targetIndex].buildSettings.TARGET_BUILD_DIR,
85-
executableFolderPath:
86-
settings[targetIndex].buildSettings.EXECUTABLE_FOLDER_PATH,
87-
};
20+
if (!fullProductName) {
21+
throw new CLIError('Failed to get product name.');
8822
}
8923

90-
return {};
91-
}
92-
93-
function getPlatformName(buildOutput: string) {
94-
// Xcode can sometimes escape `=` with a backslash or put the value in quotes
95-
const platformNameMatch = /export PLATFORM_NAME\\?="?(\w+)"?$/m.exec(
96-
buildOutput,
97-
);
98-
if (!platformNameMatch) {
99-
throw new CLIError(
100-
'Couldn\'t find "PLATFORM_NAME" variable in xcodebuild output. Please report this issue and run your project with Xcode instead.',
101-
);
24+
if (isCatalyst) {
25+
return path.join(targetBuildDir, '-maccatalyst', executableFolderPath);
26+
} else {
27+
return path.join(targetBuildDir, executableFolderPath);
10228
}
103-
return platformNameMatch[1];
10429
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {CLIError, logger} from '@react-native-community/cli-tools';
2+
import {IOSProjectInfo} from '@react-native-community/cli-types';
3+
import chalk from 'chalk';
4+
import child_process from 'child_process';
5+
6+
export async function getBuildSettings(
7+
xcodeProject: IOSProjectInfo,
8+
mode: string,
9+
buildOutput: string,
10+
scheme: string,
11+
target?: string,
12+
) {
13+
const buildSettings = child_process.execFileSync(
14+
'xcodebuild',
15+
[
16+
xcodeProject.isWorkspace ? '-workspace' : '-project',
17+
xcodeProject.name,
18+
'-scheme',
19+
scheme,
20+
'-sdk',
21+
getPlatformName(buildOutput),
22+
'-configuration',
23+
mode,
24+
'-showBuildSettings',
25+
'-json',
26+
],
27+
{encoding: 'utf8'},
28+
);
29+
30+
const settings = JSON.parse(buildSettings);
31+
32+
const targets = settings.map(
33+
({target: settingsTarget}: any) => settingsTarget,
34+
);
35+
36+
let selectedTarget = targets[0];
37+
38+
if (target) {
39+
if (!targets.includes(target)) {
40+
logger.info(
41+
`Target ${chalk.bold(target)} not found for scheme ${chalk.bold(
42+
scheme,
43+
)}, automatically selected target ${chalk.bold(selectedTarget)}`,
44+
);
45+
} else {
46+
selectedTarget = target;
47+
}
48+
}
49+
50+
// Find app in all building settings - look for WRAPPER_EXTENSION: 'app',
51+
const targetIndex = targets.indexOf(selectedTarget);
52+
const targetSettings = settings[targetIndex].buildSettings;
53+
54+
const wrapperExtension = targetSettings.WRAPPER_EXTENSION;
55+
56+
if (wrapperExtension === 'app') {
57+
return settings[targetIndex].buildSettings;
58+
}
59+
60+
return null;
61+
}
62+
63+
function getPlatformName(buildOutput: string) {
64+
// Xcode can sometimes escape `=` with a backslash or put the value in quotes
65+
const platformNameMatch = /export PLATFORM_NAME\\?="?(\w+)"?$/m.exec(
66+
buildOutput,
67+
);
68+
if (!platformNameMatch) {
69+
throw new CLIError(
70+
'Couldn\'t find "PLATFORM_NAME" variable in xcodebuild output. Please report this issue and run your project with Xcode instead.',
71+
);
72+
}
73+
return platformNameMatch[1];
74+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import child_process from 'child_process';
2+
import {CLIError, logger} from '@react-native-community/cli-tools';
3+
import {IOSProjectInfo} from '@react-native-community/cli-types';
4+
import chalk from 'chalk';
5+
import {getBuildPath} from './getBuildPath';
6+
import {getBuildSettings} from './getBuildSettings';
7+
import path from 'path';
8+
9+
function handleLaunchResult(
10+
success: boolean,
11+
errorMessage: string,
12+
errorDetails = '',
13+
) {
14+
if (success) {
15+
logger.success('Successfully launched the app');
16+
} else {
17+
logger.error(errorMessage, errorDetails);
18+
}
19+
}
20+
21+
type Options = {
22+
buildOutput: any;
23+
xcodeProject: IOSProjectInfo;
24+
mode: string;
25+
scheme: string;
26+
target?: string;
27+
udid: string;
28+
binaryPath?: string;
29+
};
30+
31+
export default async function installApp({
32+
buildOutput,
33+
xcodeProject,
34+
mode,
35+
scheme,
36+
target,
37+
udid,
38+
binaryPath,
39+
}: Options) {
40+
let appPath = binaryPath;
41+
42+
const buildSettings = await getBuildSettings(
43+
xcodeProject,
44+
mode,
45+
buildOutput,
46+
scheme,
47+
target,
48+
);
49+
50+
if (!appPath) {
51+
appPath = await getBuildPath(buildSettings);
52+
}
53+
54+
const targetBuildDir = buildSettings.TARGET_BUILD_DIR;
55+
const infoPlistPath = buildSettings.INFOPLIST_PATH;
56+
57+
if (!infoPlistPath) {
58+
throw new CLIError('Failed to find Info.plist');
59+
}
60+
61+
if (!targetBuildDir) {
62+
throw new CLIError('Failed to get target build directory.');
63+
}
64+
65+
logger.info(`Installing "${chalk.bold(appPath)}`);
66+
67+
if (udid && appPath) {
68+
child_process.spawnSync('xcrun', ['simctl', 'install', udid, appPath], {
69+
stdio: 'inherit',
70+
});
71+
}
72+
73+
const bundleID = child_process
74+
.execFileSync(
75+
'/usr/libexec/PlistBuddy',
76+
[
77+
'-c',
78+
'Print:CFBundleIdentifier',
79+
path.join(targetBuildDir, infoPlistPath),
80+
],
81+
{encoding: 'utf8'},
82+
)
83+
.trim();
84+
85+
console.log(bundleID);
86+
87+
logger.info(`Launching "${chalk.bold(bundleID)}"`);
88+
89+
let result = child_process.spawnSync('xcrun', [
90+
'simctl',
91+
'launch',
92+
udid,
93+
bundleID,
94+
]);
95+
96+
handleLaunchResult(
97+
result.status === 0,
98+
'Failed to launch the app on simulator',
99+
result.stderr.toString(),
100+
);
101+
}

packages/cli-platform-apple/src/commands/runCommand/runOnDevice.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import chalk from 'chalk';
66
import {buildProject} from '../buildCommand/buildProject';
77
import {getBuildPath} from './getBuildPath';
88
import {FlagsT} from './createRun';
9+
import {getBuildSettings} from './getBuildSettings';
910

1011
export async function runOnDevice(
1112
selectedDevice: Device,
@@ -45,14 +46,14 @@ export async function runOnDevice(
4546
args,
4647
);
4748

48-
const appPath = await getBuildPath(
49+
const buildSettings = await getBuildSettings(
4950
xcodeProject,
5051
mode,
5152
buildOutput,
5253
scheme,
53-
args.target,
54-
true,
5554
);
55+
56+
const appPath = await getBuildPath(buildSettings, true);
5657
const appProcess = child_process.spawn(`${appPath}/${scheme}`, [], {
5758
detached: true,
5859
stdio: 'ignore',
@@ -70,13 +71,14 @@ export async function runOnDevice(
7071
args,
7172
);
7273

73-
appPath = await getBuildPath(
74+
const buildSettings = await getBuildSettings(
7475
xcodeProject,
7576
mode,
7677
buildOutput,
7778
scheme,
78-
args.target,
7979
);
80+
81+
appPath = await getBuildPath(buildSettings);
8082
} else {
8183
appPath = args.binaryPath;
8284
}

0 commit comments

Comments
 (0)