microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.0.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/ios/plistBuddy.ts

237lines · 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({
31 nodeChildProcess = new Node.ChildProcess(),
32 } = {}) {
33 this.nodeChildProcess = nodeChildProcess;
34 }
35
36 public getBundleId(iosProjectRoot: string, projectRoot: string, simulator: boolean = true, configuration: string = "Debug", productName?: string, scheme?: string): Promise<string> {
37 return ProjectVersionHelper.getReactNativeVersions(projectRoot)
38 .then((rnVersions) => {
39 let productsFolder;
40 if (semver.gte(rnVersions.reactNativeVersion, "0.59.0")) {
41 if (!scheme) {
42 // If no scheme were provided via runOptions.scheme or via runArguments then try to get scheme using the way RN CLI does.
43 scheme = this.getInferredScheme(iosProjectRoot, projectRoot, rnVersions.reactNativeVersion);
44 }
45 productsFolder = path.join(iosProjectRoot, "build", scheme, "Build", "Products");
46 } else {
47 productsFolder = path.join(iosProjectRoot, "build", "Build", "Products");
48 }
49 const sdkType = simulator ? "iphonesimulator" : "iphoneos";
50 let configurationFolder = path.join(productsFolder, `${configuration}-${sdkType}`);
51 let executable = "";
52 if (productName) {
53 executable = `${productName}.app`;
54 if (!fs.existsSync(path.join(configurationFolder, executable))) {
55 const configurationData = this.getConfigurationData(
56 projectRoot,
57 rnVersions.reactNativeVersion,
58 iosProjectRoot,
59 configuration,
60 scheme,
61 sdkType,
62 configurationFolder
63 );
64 configurationFolder = configurationData.configurationFolder;
65 }
66 } else {
67 const executableList = this.findExecutable(configurationFolder);
68 if (!executableList.length) {
69 const configurationData = this.getConfigurationData(
70 projectRoot,
71 rnVersions.reactNativeVersion,
72 iosProjectRoot,
73 configuration,
74 scheme,
75 sdkType,
76 configurationFolder
77 );
78
79 configurationFolder = configurationData.configurationFolder;
80 executableList.push(configurationData.fullProductName);
81 } else if (executableList.length > 1) {
82 throw ErrorHelper.getInternalError(InternalErrorCode.IOSFoundMoreThanOneExecutablesCleanupBuildFolder, configurationFolder);
83 }
84 executable = `${executableList[0]}`;
85 }
86
87 const infoPlistPath = path.join(configurationFolder, executable, "Info.plist");
88 return this.invokePlistBuddy("Print:CFBundleIdentifier", infoPlistPath);
89 });
90 }
91
92 public setPlistProperty(plistFile: string, property: string, value: string): Promise<void> {
93 // Attempt to set the value, and if it fails due to the key not existing attempt to create the key
94 return this.invokePlistBuddy(`Set ${property} ${value}`, plistFile).catch(() =>
95 this.invokePlistBuddy(`Add ${property} string ${value}`, plistFile)
96 ).then(() => { });
97 }
98
99 public setPlistBooleanProperty(plistFile: string, property: string, value: boolean): Promise<void> {
100 // Attempt to set the value, and if it fails due to the key not existing attempt to create the key
101 return this.invokePlistBuddy(`Set ${property} ${value}`, plistFile)
102 .catch(() =>
103 this.invokePlistBuddy(`Add ${property} bool ${value}`, plistFile)
104 )
105 .then(() => { });
106 }
107
108 public deletePlistProperty(plistFile: string, property: string): Promise<void> {
109 return this.invokePlistBuddy(`Delete ${property}`, plistFile).then(() => { });
110 }
111
112 public readPlistProperty(plistFile: string, property: string): Promise<string> {
113 return this.invokePlistBuddy(`Print ${property}`, plistFile);
114 }
115
116 public getBuildPathAndProductName(
117 iosProjectRoot: string,
118 projectWorkspaceConfigName: string,
119 configuration: string,
120 scheme: string,
121 sdkType: string
122 ): ConfigurationData {
123 const buildSettings = this.nodeChildProcess.execFileSync(
124 "xcodebuild",
125 [
126 "-workspace",
127 projectWorkspaceConfigName,
128 "-scheme",
129 scheme,
130 "-sdk",
131 sdkType,
132 "-configuration",
133 configuration,
134 "-showBuildSettings",
135 ],
136 {
137 encoding: "utf8",
138 cwd: iosProjectRoot,
139 }
140 );
141
142 const targetBuildDir = this.fetchParameterFromBuildSettings(<string>buildSettings, this.TARGET_BUILD_DIR_SEARCH_KEY);
143 const fullProductName = this.fetchParameterFromBuildSettings(<string>buildSettings, this.FULL_PRODUCT_NAME_SEARCH_KEY);
144
145 if (!targetBuildDir) {
146 throw new Error("Failed to get the target build directory.");
147 }
148 if (!fullProductName) {
149 throw new Error("Failed to get full product name.");
150 }
151
152 return {
153 fullProductName,
154 configurationFolder: targetBuildDir,
155 };
156 }
157
158 public getInferredScheme(iosProjectRoot: string, projectRoot: string, rnVersion: string) {
159 const projectWorkspaceConfigName = this.getProjectWorkspaceConfigName(iosProjectRoot, projectRoot, rnVersion);
160 return getFileNameWithoutExtension(projectWorkspaceConfigName);
161 }
162
163 public getProjectWorkspaceConfigName(iosProjectRoot: string, projectRoot: string, rnVersion: string): string {
164 // Portion of code was taken from https://github.com/react-native-community/cli/blob/master/packages/platform-ios/src/commands/runIOS/index.js
165 // and modified a little bit
166 /**
167 * Copyright (c) Facebook, Inc. and its affiliates.
168 *
169 * This source code is licensed under the MIT license found in the
170 * LICENSE file in the root directory of this source tree.
171 *
172 * @flow
173 * @format
174 */
175 let iOSCliFolderName: string;
176 if (semver.gte(rnVersion, "0.60.0")) {
177 iOSCliFolderName = "cli-platform-ios";
178 } else {
179 iOSCliFolderName = "cli";
180 }
181 const findXcodeProject = customRequire(path.join(projectRoot, `node_modules/@react-native-community/${iOSCliFolderName}/build/commands/runIOS/findXcodeProject`)).default;
182 const xcodeProject = findXcodeProject(fs.readdirSync(iosProjectRoot));
183 if (!xcodeProject) {
184 throw new Error(
185 `Could not find Xcode project files in "${iosProjectRoot}" folder`
186 );
187 }
188
189 return xcodeProject.name;
190 }
191
192 public getConfigurationData(
193 projectRoot: string,
194 reactNativeVersion: string,
195 iosProjectRoot: string,
196 configuration: string,
197 scheme: string | undefined,
198 sdkType: string,
199 oldConfigurationFolder: string
200 ): ConfigurationData {
201 if (!scheme) {
202 throw ErrorHelper.getInternalError(InternalErrorCode.IOSCouldNotFoundExecutableInFolder, oldConfigurationFolder);
203 }
204 const projectWorkspaceConfigName = this.getProjectWorkspaceConfigName(iosProjectRoot, projectRoot, reactNativeVersion);
205 return this.getBuildPathAndProductName(
206 iosProjectRoot,
207 projectWorkspaceConfigName,
208 configuration,
209 scheme,
210 sdkType
211 );
212 }
213
214 /**
215 * @param {string} buildSettings
216 * @param {string} parameterName
217 * @returns {string | null}
218 */
219 public fetchParameterFromBuildSettings(buildSettings: string, parameterName: string) {
220 const targetBuildMatch = new RegExp(`${parameterName} = (.+)$`, "m").exec(buildSettings);
221 return targetBuildMatch && targetBuildMatch[1]
222 ? targetBuildMatch[1].trim()
223 : null;
224 }
225
226 private findExecutable(folder: string): string[] {
227 return glob.sync("*.app", {
228 cwd: folder,
229 });
230 }
231
232 private invokePlistBuddy(command: string, plistFile: string): Promise<string> {
233 return this.nodeChildProcess.exec(`${PlistBuddy.plistBuddyExecutable} -c '${command}' '${plistFile}'`).then(res => res.outcome.then((result: string) => {
234 return result.toString().trim();
235 }));
236 }
237}
238