microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.3.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/ios/plistBuddy.ts

283lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as path from "path";
5import * as glob from "glob";
6import * as fs from "fs";
7import * as semver from "semver";
8
9import { Node } from "../../common/node/node";
10import { ChildProcess } from "../../common/node/childProcess";
11import { ErrorHelper } from "../../common/error/errorHelper";
12import { InternalErrorCode } from "../../common/error/internalErrorCode";
13import { ProjectVersionHelper } from "../../common/projectVersionHelper";
14import { getFileNameWithoutExtension } from "../../common/utils";
15import customRequire from "../../common/customRequire";
16
17export interface ConfigurationData {
18 fullProductName: string;
19 configurationFolder: string;
20}
21
22export class PlistBuddy {
23 private static plistBuddyExecutable = "/usr/libexec/PlistBuddy";
24
25 private readonly TARGET_BUILD_DIR_SEARCH_KEY = "TARGET_BUILD_DIR";
26 private readonly FULL_PRODUCT_NAME_SEARCH_KEY = "FULL_PRODUCT_NAME";
27
28 private nodeChildProcess: ChildProcess;
29
30 constructor({ nodeChildProcess = new Node.ChildProcess() } = {}) {
31 this.nodeChildProcess = nodeChildProcess;
32 }
33
34 public getBundleId(
35 iosProjectRoot: string,
36 projectRoot: string,
37 simulator: boolean = true,
38 configuration: string = "Debug",
39 productName?: string,
40 scheme?: string,
41 ): Promise<string> {
42 return ProjectVersionHelper.getReactNativeVersions(projectRoot).then(rnVersions => {
43 let productsFolder;
44 if (semver.gte(rnVersions.reactNativeVersion, "0.59.0")) {
45 if (!scheme) {
46 // If no scheme were provided via runOptions.scheme or via runArguments then try to get scheme using the way RN CLI does.
47 scheme = this.getInferredScheme(
48 iosProjectRoot,
49 projectRoot,
50 rnVersions.reactNativeVersion,
51 );
52 }
53 productsFolder = path.join(iosProjectRoot, "build", scheme, "Build", "Products");
54 } else {
55 productsFolder = path.join(iosProjectRoot, "build", "Build", "Products");
56 }
57 const sdkType = simulator ? "iphonesimulator" : "iphoneos";
58 let configurationFolder = path.join(productsFolder, `${configuration}-${sdkType}`);
59 let executable = "";
60 if (productName) {
61 executable = `${productName}.app`;
62 if (!fs.existsSync(path.join(configurationFolder, executable))) {
63 const configurationData = this.getConfigurationData(
64 projectRoot,
65 rnVersions.reactNativeVersion,
66 iosProjectRoot,
67 configuration,
68 scheme,
69 sdkType,
70 configurationFolder,
71 );
72 configurationFolder = configurationData.configurationFolder;
73 }
74 } else {
75 const executableList = this.findExecutable(configurationFolder);
76 if (!executableList.length) {
77 const configurationData = this.getConfigurationData(
78 projectRoot,
79 rnVersions.reactNativeVersion,
80 iosProjectRoot,
81 configuration,
82 scheme,
83 sdkType,
84 configurationFolder,
85 );
86
87 configurationFolder = configurationData.configurationFolder;
88 executableList.push(configurationData.fullProductName);
89 } else if (executableList.length > 1) {
90 throw ErrorHelper.getInternalError(
91 InternalErrorCode.IOSFoundMoreThanOneExecutablesCleanupBuildFolder,
92 configurationFolder,
93 );
94 }
95 executable = `${executableList[0]}`;
96 }
97
98 const infoPlistPath = path.join(configurationFolder, executable, "Info.plist");
99 return this.invokePlistBuddy("Print:CFBundleIdentifier", infoPlistPath);
100 });
101 }
102
103 public setPlistProperty(plistFile: string, property: string, value: string): Promise<void> {
104 // Attempt to set the value, and if it fails due to the key not existing attempt to create the key
105 return this.invokePlistBuddy(`Set ${property} ${value}`, plistFile)
106 .catch(() => this.invokePlistBuddy(`Add ${property} string ${value}`, plistFile))
107 .then(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
108 }
109
110 public setPlistBooleanProperty(
111 plistFile: string,
112 property: string,
113 value: boolean,
114 ): Promise<void> {
115 // Attempt to set the value, and if it fails due to the key not existing attempt to create the key
116 return this.invokePlistBuddy(`Set ${property} ${value}`, plistFile)
117 .catch(() => this.invokePlistBuddy(`Add ${property} bool ${value}`, plistFile))
118 .then(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
119 }
120
121 public deletePlistProperty(plistFile: string, property: string): Promise<void> {
122 return this.invokePlistBuddy(`Delete ${property}`, plistFile).then(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
123 }
124
125 public readPlistProperty(plistFile: string, property: string): Promise<string> {
126 return this.invokePlistBuddy(`Print ${property}`, plistFile);
127 }
128
129 public getBuildPathAndProductName(
130 iosProjectRoot: string,
131 projectWorkspaceConfigName: string,
132 configuration: string,
133 scheme: string,
134 sdkType: string,
135 ): ConfigurationData {
136 const buildSettings = this.nodeChildProcess.execFileSync(
137 "xcodebuild",
138 [
139 "-workspace",
140 projectWorkspaceConfigName,
141 "-scheme",
142 scheme,
143 "-sdk",
144 sdkType,
145 "-configuration",
146 configuration,
147 "-showBuildSettings",
148 ],
149 {
150 encoding: "utf8",
151 cwd: iosProjectRoot,
152 },
153 );
154
155 const targetBuildDir = this.fetchParameterFromBuildSettings(
156 <string>buildSettings,
157 this.TARGET_BUILD_DIR_SEARCH_KEY,
158 );
159 const fullProductName = this.fetchParameterFromBuildSettings(
160 <string>buildSettings,
161 this.FULL_PRODUCT_NAME_SEARCH_KEY,
162 );
163
164 if (!targetBuildDir) {
165 throw new Error("Failed to get the target build directory.");
166 }
167 if (!fullProductName) {
168 throw new Error("Failed to get full product name.");
169 }
170
171 return {
172 fullProductName,
173 configurationFolder: targetBuildDir,
174 };
175 }
176
177 public getInferredScheme(
178 iosProjectRoot: string,
179 projectRoot: string,
180 rnVersion: string,
181 ): string {
182 const projectWorkspaceConfigName = this.getProjectWorkspaceConfigName(
183 iosProjectRoot,
184 projectRoot,
185 rnVersion,
186 );
187 return getFileNameWithoutExtension(projectWorkspaceConfigName);
188 }
189
190 public getProjectWorkspaceConfigName(
191 iosProjectRoot: string,
192 projectRoot: string,
193 rnVersion: string,
194 ): string {
195 // Portion of code was taken from https://github.com/react-native-community/cli/blob/master/packages/platform-ios/src/commands/runIOS/index.js
196 // and modified a little bit
197 /**
198 * Copyright (c) Facebook, Inc. and its affiliates.
199 *
200 * This source code is licensed under the MIT license found in the
201 * LICENSE file in the root directory of this source tree.
202 *
203 * @flow
204 * @format
205 */
206 let iOSCliFolderName: string;
207 if (semver.gte(rnVersion, "0.60.0")) {
208 iOSCliFolderName = "cli-platform-ios";
209 } else {
210 iOSCliFolderName = "cli";
211 }
212 const findXcodeProject = customRequire(
213 path.join(
214 projectRoot,
215 `node_modules/@react-native-community/${iOSCliFolderName}/build/commands/runIOS/findXcodeProject`,
216 ),
217 ).default;
218 const xcodeProject = findXcodeProject(fs.readdirSync(iosProjectRoot));
219 if (!xcodeProject) {
220 throw new Error(`Could not find Xcode project files in "${iosProjectRoot}" folder`);
221 }
222
223 return xcodeProject.name;
224 }
225
226 public getConfigurationData(
227 projectRoot: string,
228 reactNativeVersion: string,
229 iosProjectRoot: string,
230 configuration: string,
231 scheme: string | undefined,
232 sdkType: string,
233 oldConfigurationFolder: string,
234 ): ConfigurationData {
235 if (!scheme) {
236 throw ErrorHelper.getInternalError(
237 InternalErrorCode.IOSCouldNotFoundExecutableInFolder,
238 oldConfigurationFolder,
239 );
240 }
241 const projectWorkspaceConfigName = this.getProjectWorkspaceConfigName(
242 iosProjectRoot,
243 projectRoot,
244 reactNativeVersion,
245 );
246 return this.getBuildPathAndProductName(
247 iosProjectRoot,
248 projectWorkspaceConfigName,
249 configuration,
250 scheme,
251 sdkType,
252 );
253 }
254
255 /**
256 * @param {string} buildSettings
257 * @param {string} parameterName
258 * @returns {string | null}
259 */
260 public fetchParameterFromBuildSettings(
261 buildSettings: string,
262 parameterName: string,
263 ): string | null {
264 const targetBuildMatch = new RegExp(`${parameterName} = (.+)$`, "m").exec(buildSettings);
265 return targetBuildMatch && targetBuildMatch[1] ? targetBuildMatch[1].trim() : null;
266 }
267
268 private findExecutable(folder: string): string[] {
269 return glob.sync("*.app", {
270 cwd: folder,
271 });
272 }
273
274 private invokePlistBuddy(command: string, plistFile: string): Promise<string> {
275 return this.nodeChildProcess
276 .exec(`${PlistBuddy.plistBuddyExecutable} -c '${command}' '${plistFile}'`)
277 .then(res =>
278 res.outcome.then((result: string) => {
279 return result.toString().trim();
280 }),
281 );
282 }
283}
284