-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathXCBuildConfiguration.ts
More file actions
272 lines (243 loc) · 10.4 KB
/
XCBuildConfiguration.ts
File metadata and controls
272 lines (243 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
import path from "node:path";
import os from "node:os";
import fs from "node:fs";
import plist from "@expo/plist";
import type { XCConfigurationList } from "./XCConfigurationList";
import type { PBXNativeTarget } from "./PBXNativeTarget";
import { uniqueBy } from "./utils/array";
import * as json from "../json/types";
import { AbstractObject } from "./AbstractObject";
import type { SansIsa } from "./utils/util.types";
import type { XcodeProject } from "./XcodeProject";
import type { PBXFileReference } from "./PBXFileReference";
import { resolveXcodeBuildSetting } from "./utils/resolveBuildSettings";
import * as xcconfig from "../xcconfig";
import type { XCConfig, XCConfigFlattenOptions } from "../xcconfig";
const debug = require("debug")(
"xcode:XCBuildConfiguration"
) as typeof console.log;
export type XCBuildConfigurationModel =
json.XCBuildConfiguration<PBXFileReference>;
export class XCBuildConfiguration extends AbstractObject<XCBuildConfigurationModel> {
static isa = json.ISA.XCBuildConfiguration as const;
static is(object: any): object is XCBuildConfiguration {
return object.isa === XCBuildConfiguration.isa;
}
static create(
project: XcodeProject,
opts: SansIsa<XCBuildConfigurationModel>
) {
return project.createModel<XCBuildConfigurationModel>({
isa: json.ISA.XCBuildConfiguration,
...opts,
}) as XCBuildConfiguration;
}
/** @returns the resolved absolute file path for the `INFOPLIST_FILE` build setting if it exists. `null` if the setting does not exist. */
private resolveFilePath(key: keyof json.BuildSettings): string | null {
const fileRef = this.resolveBuildSetting(key);
if (fileRef == null || typeof fileRef !== "string") {
return null;
}
const root = this.getXcodeProject().getProjectRoot();
// TODO: Maybe interpolate
// TODO: Maybe add root projectRoot, currently this is always `""` in my fixtures.
return path.join(root, fileRef);
}
/** @returns the resolved absolute file path for the `INFOPLIST_FILE` build setting if it exists. `null` if the setting does not exist. */
getEntitlementsFilePath(): string | null {
return this.resolveFilePath("CODE_SIGN_ENTITLEMENTS")!;
}
getEntitlements() {
const filePath = this.getEntitlementsFilePath();
if (!filePath) return null;
return plist.parse(fs.readFileSync(filePath, "utf8"));
}
/** @returns the resolved absolute file path for the `INFOPLIST_FILE` build setting if it exists. `null` if the setting does not exist. */
getInfoPlistFilePath(): string | null {
return this.resolveFilePath("INFOPLIST_FILE")!;
}
getInfoPlist() {
const filePath = this.getInfoPlistFilePath();
if (!filePath) return null;
return plist.parse(fs.readFileSync(filePath, "utf8"));
}
/** @returns a list of targets which refer to this build configuration. */
getTargetReferrers(): PBXNativeTarget[] {
const lists = this.getReferrers().filter(
(ref) => ref.isa === json.ISA.XCConfigurationList
) as XCConfigurationList[];
const targets = lists
.map((list) => {
return list
.getReferrers()
.filter(
(ref) => ref.isa === json.ISA.PBXNativeTarget
) as PBXNativeTarget[];
})
.flat();
return uniqueBy(targets, (item) => item.uuid);
}
/**
* Build settings can include environment variables (often defined by `xcodebuild`) and additional commands to mutate the value, e.g. `$(FOO:lower)` -> `process.env.FOO.toLowerCase()`
*
* @returns a resolved build setting with all commands and references interpolated out. */
resolveBuildSetting<TSetting extends keyof json.BuildSettings>(
buildSetting: TSetting
): json.BuildSettings[TSetting] {
const resolver = (sub: string): string => {
if (!(sub in this.props.buildSettings)) {
if (process.env[sub] != null) {
debug('Using environment variable substitution for "%s"', sub);
return process.env[sub]!;
} else if (
// If the build settings aren't available then it means this process it being run outside of xcodebuild (likely)
// so we'll fallback on defaults from a random HTML file I found on the internet.
// https://opensource.apple.com/source/pb_makefiles/pb_makefiles-1005/platform-variables.make.auto.html
sub in DEF_APPLE_BUILD_VARIABLES
) {
debug(
'Dangerously using estimated default Apple build variable substitution for "%s"',
sub
);
return DEF_APPLE_BUILD_VARIABLES[sub];
}
// If the common value `TARGET_NAME` was used and we got here (no environment variable was found) then we'll just guess.
if (sub === "TARGET_NAME") {
const parentTarget = this.getTargetReferrers();
if (parentTarget.length > 0) {
if (parentTarget.length > 1) {
console.warn(
`[XCBuildConfiguration][${
this.props.name
}]: Multiple targets found for build setting "${buildSetting}". Using first target "${
parentTarget[0].props.name
}". Possible targets: ${parentTarget
.map((target) => target.getDisplayName())
.join(", ")}`
);
}
return parentTarget[0].props.name;
} else {
console.warn(
`[XCBuildConfiguration][${this.props.name}]: Issue resolving special build setting "${buildSetting}". Substitute value "${sub}" not found in build settings, environment variables, or from a referring PBXNativeTarget.`
);
}
} else {
console.warn(
`[XCBuildConfiguration][${this.props.name}]: Issue resolving build setting "${buildSetting}". Substitute value "${sub}" not found in build settings.`
);
}
}
if (Array.isArray(sub)) {
console.warn(
`[XCBuildConfiguration][${this.props.name}]: Issue resolving build setting "${buildSetting}". Substitute value "${sub}" is of type array––it's not clear how this should be resolved in a string.`
);
}
return this.props.buildSettings[sub];
};
const setting = this.props.buildSettings[buildSetting];
if (typeof setting === "string") {
return resolveXcodeBuildSetting(setting, resolver);
}
if (Array.isArray(setting)) {
// @ts-expect-error
return setting.map((s) => {
return resolveXcodeBuildSetting(s, resolver);
});
}
return setting;
}
protected getObjectProps() {
return {
baseConfigurationReference: String,
};
}
/**
* Get the absolute file path of the base configuration xcconfig file.
* @returns The absolute path or null if no base configuration is set.
*/
getBaseConfigurationFilePath(): string | null {
const fileRef = this.props.baseConfigurationReference;
if (!fileRef) return null;
const filePath = fileRef.props.path;
if (!filePath) return null;
const root = this.getXcodeProject().getProjectRoot();
// Handle different source tree types
const sourceTree = fileRef.props.sourceTree;
if (sourceTree === "<group>" || sourceTree === "SOURCE_ROOT") {
return path.join(root, filePath);
}
if (sourceTree === "<absolute>" || path.isAbsolute(filePath)) {
return filePath;
}
return path.join(root, filePath);
}
/**
* Parse and return the base configuration xcconfig file.
* @returns Parsed XCConfig or null if no base configuration is set or file doesn't exist.
*/
getBaseConfiguration(): XCConfig | null {
const filePath = this.getBaseConfigurationFilePath();
if (!filePath || !fs.existsSync(filePath)) return null;
try {
return xcconfig.parseFile(filePath);
} catch (error) {
debug("Failed to parse base configuration: %s", error);
return null;
}
}
/**
* Get flattened build settings from the base configuration xcconfig file.
* Settings are merged from included files with current file taking precedence.
*
* @param options - Optional filtering by sdk/arch/config
* @returns Flattened key-value map of build settings, or empty object if no base config.
*/
getBaseConfigurationSettings(
options?: XCConfigFlattenOptions
): Record<string, string> {
const config = this.getBaseConfiguration();
if (!config) return {};
return xcconfig.flattenBuildSettings(config, options);
}
}
// https://opensource.apple.com/source/pb_makefiles/pb_makefiles-1005/platform-variables.make.auto.html
const DEF_APPLE_BUILD_VARIABLES: Record<string, string> = {
HOME: os.homedir(),
SYSTEM_APPS_DIR: "/Applications",
SYSTEM_ADMIN_APPS_DIR: "/Applications/Utilities",
SYSTEM_DEMOS_DIR: "/Applications/Extras",
SYSTEM_DEVELOPER_DIR: "/Applications/Xcode.app/Contents/Developer",
SYSTEM_DEVELOPER_APPS_DIR: "/Applications/Xcode.app/Contents/Applications",
SYSTEM_DEVELOPER_JAVA_TOOLS_DIR:
"/Applications/Xcode.app/Contents/Applications/Java Tools",
SYSTEM_DEVELOPER_PERFORMANCE_TOOLS_DIR:
"/Applications/Xcode.app/Contents/Applications/Performance Tools",
SYSTEM_DEVELOPER_GRAPHICS_TOOLS_DIR:
"/Applications/Xcode.app/Contents/Applications/Graphics Tools",
SYSTEM_DEVELOPER_UTILITIES_DIR:
"/Applications/Xcode.app/Contents/Applications/Utilities",
SYSTEM_DEVELOPER_DEMOS_DIR:
"/Applications/Xcode.app/Contents/Developer/Utilities/Built Examples",
SYSTEM_DEVELOPER_DOC_DIR:
"/Applications/Xcode.app/Contents/Developer/ADC Reference Library",
SYSTEM_DEVELOPER_TOOLS_DOC_DIR:
"/Applications/Xcode.app/Contents/Developer/ADC Reference Library/documentation/DeveloperTools",
SYSTEM_DEVELOPER_RELEASENOTES_DIR:
"/Applications/Xcode.app/Contents/Developer/ADC Reference Library/releasenotes",
SYSTEM_DEVELOPER_TOOLS_RELEASENOTES_DIR:
"/Applications/Xcode.app/Contents/Developer/ADC Reference Library/releasenotes/DeveloperTools",
SYSTEM_LIBRARY_DIR: "/System/Library",
SYSTEM_CORE_SERVICES_DIR: "/System/Library/CoreServices",
SYSTEM_DOCUMENTATION_DIR: "/Library/Documentation",
LOCAL_ADMIN_APPS_DIR: "/Applications/Utilities",
LOCAL_APPS_DIR: "/Applications",
LOCAL_DEVELOPER_DIR: "/Library/Developer",
LOCAL_LIBRARY_DIR: "/Library",
USER_APPS_DIR: "$(HOME)/Applications",
USER_LIBRARY_DIR: "$(HOME)/Library",
MAN_PAGE_DIRECTORIES: "/usr/share/man",
SYSTEM_LIBRARY_EXECUTABLES_DIR: "",
SYSTEM_DEVELOPER_EXECUTABLES_DIR: "",
LOCAL_DEVELOPER_EXECUTABLES_DIR: "",
};