@expo/build-tools 18.7.0 → 18.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builders/ios.js +15 -2
- package/dist/common/easBuildInternal.js +3 -27
- package/dist/context.js +3 -1
- package/dist/ios/fastlane.d.ts +4 -1
- package/dist/ios/fastlane.js +9 -1
- package/dist/ios/gymfile.d.ts +5 -2
- package/dist/ios/gymfile.js +5 -2
- package/dist/steps/easFunctions.js +6 -0
- package/dist/steps/functionGroups/build.js +4 -0
- package/dist/steps/functions/configureIosCredentials.js +9 -3
- package/dist/steps/functions/configureIosVersion.js +32 -14
- package/dist/steps/functions/deploy.d.ts +2 -0
- package/dist/steps/functions/deploy.js +136 -0
- package/dist/steps/functions/eagerBundle.js +1 -1
- package/dist/steps/functions/export.d.ts +2 -0
- package/dist/steps/functions/export.js +115 -0
- package/dist/steps/functions/generateGymfileFromTemplate.js +9 -0
- package/dist/steps/functions/installNodeModules.js +1 -1
- package/dist/steps/functions/parseXcactivitylog.d.ts +2 -0
- package/dist/steps/functions/parseXcactivitylog.js +49 -0
- package/dist/steps/functions/prebuild.js +1 -1
- package/dist/steps/functions/restoreBuildCache.js +1 -1
- package/dist/steps/functions/saveBuildCache.js +1 -1
- package/dist/steps/utils/ios/xcactivitylog.d.ts +57 -0
- package/dist/steps/utils/ios/xcactivitylog.js +278 -0
- package/dist/templates/GymfileArchive.d.ts +1 -1
- package/dist/templates/GymfileArchive.js +4 -0
- package/dist/templates/GymfileSimulator.d.ts +1 -1
- package/dist/templates/GymfileSimulator.js +3 -0
- package/dist/utils/easCli.d.ts +11 -0
- package/dist/utils/easCli.js +57 -0
- package/dist/utils/packageManager.d.ts +4 -1
- package/dist/utils/packageManager.js +13 -4
- package/package.json +4 -4
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createEasExportBuildFunction = createEasExportBuildFunction;
|
|
4
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
5
|
+
const steps_1 = require("@expo/steps");
|
|
6
|
+
const packageManager_1 = require("../../utils/packageManager");
|
|
7
|
+
const project_1 = require("../../utils/project");
|
|
8
|
+
function createEasExportBuildFunction() {
|
|
9
|
+
return new steps_1.BuildFunction({
|
|
10
|
+
namespace: 'eas',
|
|
11
|
+
id: 'export',
|
|
12
|
+
name: 'Export',
|
|
13
|
+
__metricsId: 'eas/export',
|
|
14
|
+
inputProviders: [
|
|
15
|
+
steps_1.BuildStepInput.createProvider({
|
|
16
|
+
id: 'output_dir',
|
|
17
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
18
|
+
required: false,
|
|
19
|
+
defaultValue: 'dist',
|
|
20
|
+
}),
|
|
21
|
+
steps_1.BuildStepInput.createProvider({
|
|
22
|
+
id: 'dev',
|
|
23
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
24
|
+
required: false,
|
|
25
|
+
}),
|
|
26
|
+
steps_1.BuildStepInput.createProvider({
|
|
27
|
+
id: 'minify',
|
|
28
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
31
|
+
steps_1.BuildStepInput.createProvider({
|
|
32
|
+
id: 'dump_assetmap',
|
|
33
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
steps_1.BuildStepInput.createProvider({
|
|
37
|
+
id: 'ssg',
|
|
38
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
39
|
+
required: false,
|
|
40
|
+
}),
|
|
41
|
+
steps_1.BuildStepInput.createProvider({
|
|
42
|
+
id: 'api_only',
|
|
43
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
44
|
+
required: false,
|
|
45
|
+
}),
|
|
46
|
+
steps_1.BuildStepInput.createProvider({
|
|
47
|
+
id: 'platform',
|
|
48
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
49
|
+
required: false,
|
|
50
|
+
defaultValue: 'web',
|
|
51
|
+
}),
|
|
52
|
+
],
|
|
53
|
+
outputProviders: [
|
|
54
|
+
steps_1.BuildStepOutput.createProvider({
|
|
55
|
+
id: 'export_dir',
|
|
56
|
+
required: true,
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
fn: async (stepsCtx, { inputs, outputs, env }) => {
|
|
60
|
+
const outputDir = inputs.output_dir.value;
|
|
61
|
+
const dev = inputs.dev.value;
|
|
62
|
+
const minify = inputs.minify.value;
|
|
63
|
+
const dumpAssetmap = inputs.dump_assetmap.value;
|
|
64
|
+
const ssg = inputs.ssg.value;
|
|
65
|
+
const apiOnly = inputs.api_only.value;
|
|
66
|
+
const platform = inputs.platform.value;
|
|
67
|
+
const packageManager = (0, packageManager_1.resolvePackageManager)(stepsCtx.workingDirectory, { env });
|
|
68
|
+
const exportCommand = getExportCommand({
|
|
69
|
+
outputDir,
|
|
70
|
+
dev,
|
|
71
|
+
minify,
|
|
72
|
+
dumpAssetmap,
|
|
73
|
+
ssg,
|
|
74
|
+
apiOnly,
|
|
75
|
+
platform,
|
|
76
|
+
});
|
|
77
|
+
stepsCtx.logger.info(`Running export command: expo ${exportCommand.join(' ')}`);
|
|
78
|
+
try {
|
|
79
|
+
await (0, project_1.runExpoCliCommand)({
|
|
80
|
+
packageManager,
|
|
81
|
+
args: exportCommand,
|
|
82
|
+
options: {
|
|
83
|
+
cwd: stepsCtx.workingDirectory,
|
|
84
|
+
env,
|
|
85
|
+
logger: stepsCtx.logger,
|
|
86
|
+
stdio: 'pipe',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
outputs.export_dir.set(outputDir);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new eas_build_job_1.UserError('EXPO_EXPORT_FAILED', `Export command failed: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function getExportCommand({ outputDir, dev, minify, dumpAssetmap, ssg, apiOnly, platform, }) {
|
|
98
|
+
const exportCommand = ['export', '--output-dir', outputDir, '--platform', platform];
|
|
99
|
+
if (dev) {
|
|
100
|
+
exportCommand.push('--dev');
|
|
101
|
+
}
|
|
102
|
+
if (minify === false) {
|
|
103
|
+
exportCommand.push('--no-minify');
|
|
104
|
+
}
|
|
105
|
+
if (dumpAssetmap) {
|
|
106
|
+
exportCommand.push('--dump-assetmap');
|
|
107
|
+
}
|
|
108
|
+
if (ssg === false) {
|
|
109
|
+
exportCommand.push('--no-ssg');
|
|
110
|
+
}
|
|
111
|
+
if (apiOnly) {
|
|
112
|
+
exportCommand.push('--api-only');
|
|
113
|
+
}
|
|
114
|
+
return exportCommand;
|
|
115
|
+
}
|
|
@@ -10,6 +10,7 @@ const steps_1 = require("@expo/steps");
|
|
|
10
10
|
const template_file_1 = require("@expo/template-file");
|
|
11
11
|
const assert_1 = __importDefault(require("assert"));
|
|
12
12
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
13
14
|
const path_1 = __importDefault(require("path"));
|
|
14
15
|
const credentials_1 = require("../utils/ios/credentials/credentials");
|
|
15
16
|
const manager_1 = __importDefault(require("../utils/ios/credentials/manager"));
|
|
@@ -38,6 +39,10 @@ const DEFAULT_CREDENTIALS_TEMPLATE = `
|
|
|
38
39
|
disable_xcpretty(true)
|
|
39
40
|
buildlog_path("<%- LOGS_DIRECTORY %>")
|
|
40
41
|
|
|
42
|
+
derived_data_path("<%- DERIVED_DATA_PATH %>")
|
|
43
|
+
result_bundle(true)
|
|
44
|
+
result_bundle_path("<%- RESULT_BUNDLE_PATH %>")
|
|
45
|
+
|
|
41
46
|
output_directory("<%- OUTPUT_DIRECTORY %>")
|
|
42
47
|
`;
|
|
43
48
|
const DEFAULT_SIMULATOR_TEMPLATE = `
|
|
@@ -56,6 +61,9 @@ const DEFAULT_SIMULATOR_TEMPLATE = `
|
|
|
56
61
|
|
|
57
62
|
disable_xcpretty(true)
|
|
58
63
|
buildlog_path("<%- LOGS_DIRECTORY %>")
|
|
64
|
+
|
|
65
|
+
result_bundle(true)
|
|
66
|
+
result_bundle_path("<%- RESULT_BUNDLE_PATH %>")
|
|
59
67
|
`;
|
|
60
68
|
function generateGymfileFromTemplateFunction() {
|
|
61
69
|
return new steps_1.BuildFunction({
|
|
@@ -158,6 +166,7 @@ function generateGymfileFromTemplateFunction() {
|
|
|
158
166
|
ICLOUD_CONTAINER_ENVIRONMENT,
|
|
159
167
|
SCHEME_SIMULATOR_DESTINATION: simulatorDestination,
|
|
160
168
|
DERIVED_DATA_PATH: './build',
|
|
169
|
+
RESULT_BUNDLE_PATH: path_1.default.join(os_1.default.tmpdir(), `result-bundle-${Date.now()}.xcresult`),
|
|
161
170
|
...(PROFILES ? { PROFILES } : {}),
|
|
162
171
|
...(credentials
|
|
163
172
|
? {
|
|
@@ -23,7 +23,7 @@ function createInstallNodeModulesBuildFunction() {
|
|
|
23
23
|
}
|
|
24
24
|
async function installNodeModules(stepCtx, env) {
|
|
25
25
|
const { logger } = stepCtx;
|
|
26
|
-
const packageManager = (0, packageManager_1.resolvePackageManager)(stepCtx.workingDirectory);
|
|
26
|
+
const packageManager = (0, packageManager_1.resolvePackageManager)(stepCtx.workingDirectory, { env });
|
|
27
27
|
const packagerRunDir = (0, packageManager_1.findPackagerRootDir)(stepCtx.workingDirectory);
|
|
28
28
|
if (packagerRunDir !== stepCtx.workingDirectory) {
|
|
29
29
|
const relativeReactNativeProjectDirectory = path_1.default.relative(stepCtx.global.projectTargetDirectory, stepCtx.workingDirectory);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseXcactivitylogFunction = parseXcactivitylogFunction;
|
|
7
|
+
const steps_1 = require("@expo/steps");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const xcactivitylog_1 = require("../utils/ios/xcactivitylog");
|
|
10
|
+
function parseXcactivitylogFunction() {
|
|
11
|
+
return new steps_1.BuildFunction({
|
|
12
|
+
namespace: 'eas',
|
|
13
|
+
id: 'parse_xcactivitylog',
|
|
14
|
+
name: 'Analyze build performance',
|
|
15
|
+
__metricsId: 'eas/parse_xcactivitylog',
|
|
16
|
+
supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
|
|
17
|
+
inputProviders: [
|
|
18
|
+
steps_1.BuildStepInput.createProvider({
|
|
19
|
+
id: 'derived_data_path',
|
|
20
|
+
required: false,
|
|
21
|
+
defaultValue: 'ios/build',
|
|
22
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
23
|
+
}),
|
|
24
|
+
steps_1.BuildStepInput.createProvider({
|
|
25
|
+
id: 'workspace_path',
|
|
26
|
+
required: false,
|
|
27
|
+
defaultValue: 'ios',
|
|
28
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
29
|
+
}),
|
|
30
|
+
steps_1.BuildStepInput.createProvider({
|
|
31
|
+
id: 'xclogparser_version',
|
|
32
|
+
required: false,
|
|
33
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
fn: async (stepCtx, { inputs, env }) => {
|
|
37
|
+
const derivedDataPath = inputs.derived_data_path.value;
|
|
38
|
+
const workspacePath = inputs.workspace_path.value;
|
|
39
|
+
const version = inputs.xclogparser_version.value;
|
|
40
|
+
await (0, xcactivitylog_1.parseAndReportXcactivitylog)({
|
|
41
|
+
derivedDataPath: path_1.default.resolve(stepCtx.workingDirectory, derivedDataPath),
|
|
42
|
+
workspacePath: path_1.default.resolve(stepCtx.workingDirectory, workspacePath),
|
|
43
|
+
xclogparserVersion: version,
|
|
44
|
+
logger: stepCtx.logger,
|
|
45
|
+
proxyBaseUrl: env.EAS_BUILD_COCOAPODS_CACHE_URL,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -35,7 +35,7 @@ function createPrebuildBuildFunction() {
|
|
|
35
35
|
fn: async (stepCtx, { inputs, env }) => {
|
|
36
36
|
const { logger } = stepCtx;
|
|
37
37
|
const appleTeamId = inputs.apple_team_id.value;
|
|
38
|
-
const packageManager = (0, packageManager_1.resolvePackageManager)(stepCtx.workingDirectory);
|
|
38
|
+
const packageManager = (0, packageManager_1.resolvePackageManager)(stepCtx.workingDirectory, { env });
|
|
39
39
|
const defaultPlatform = process.platform === 'darwin' ? 'ios' : 'android';
|
|
40
40
|
const job = stepCtx.global.staticContext.job;
|
|
41
41
|
const prebuildCommandArgs = getPrebuildCommandArgs({
|
|
@@ -154,7 +154,7 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
async function restoreGradleCacheAsync({ logger, workingDirectory, env, secrets, }) {
|
|
157
|
-
if (env.
|
|
157
|
+
if (env.EAS_GRADLE_CACHE !== '1') {
|
|
158
158
|
return;
|
|
159
159
|
}
|
|
160
160
|
try {
|
|
@@ -112,7 +112,7 @@ async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBe
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
async function saveGradleCacheAsync({ logger, workingDirectory, env, secrets, }) {
|
|
115
|
-
if (env.
|
|
115
|
+
if (env.EAS_GRADLE_CACHE !== '1') {
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
const gradleCachesPath = path_1.default.join(os_1.default.homedir(), '.gradle', 'caches');
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { bunyan } from '@expo/logger';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Download xclogparser, parse xcactivitylog from derived data, and log a
|
|
5
|
+
* compile metrics report. Never throws — all errors are logged at debug level.
|
|
6
|
+
*
|
|
7
|
+
* Can be called from both the step-based flow (BuildFunction) and the
|
|
8
|
+
* traditional builder flow (runBuildPhase).
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion, logger, proxyBaseUrl, }: {
|
|
11
|
+
derivedDataPath: string;
|
|
12
|
+
workspacePath: string;
|
|
13
|
+
xclogparserVersion?: string;
|
|
14
|
+
logger: bunyan;
|
|
15
|
+
proxyBaseUrl?: string;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
declare const XcactivitylogStepSchemaZ: z.ZodObject<{
|
|
18
|
+
title: z.ZodOptional<z.ZodString>;
|
|
19
|
+
detailStepType: z.ZodOptional<z.ZodString>;
|
|
20
|
+
signature: z.ZodOptional<z.ZodString>;
|
|
21
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
22
|
+
startTimestamp: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
endTimestamp: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
subSteps: z.ZodOptional<z.ZodArray<z.ZodObject</*elided*/ any, z.core.$strip>>>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
declare const XcactivitylogDataSchemaZ: z.ZodObject<{
|
|
27
|
+
schema: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
28
|
+
name: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, z.core.$strip>]>>;
|
|
30
|
+
subSteps: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
31
|
+
title: z.ZodOptional<z.ZodString>;
|
|
32
|
+
detailStepType: z.ZodOptional<z.ZodString>;
|
|
33
|
+
signature: z.ZodOptional<z.ZodString>;
|
|
34
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
startTimestamp: z.ZodOptional<z.ZodNumber>;
|
|
36
|
+
endTimestamp: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
subSteps: z.ZodOptional<z.ZodArray<z.ZodObject</*elided*/ any, z.core.$strip>>>;
|
|
38
|
+
}, z.core.$strip>>>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
type XcactivitylogStep = z.infer<typeof XcactivitylogStepSchemaZ>;
|
|
41
|
+
type XcactivitylogData = z.infer<typeof XcactivitylogDataSchemaZ>;
|
|
42
|
+
interface TargetMetric {
|
|
43
|
+
moduleName: string;
|
|
44
|
+
taskSeconds: number;
|
|
45
|
+
wallSpan: number;
|
|
46
|
+
activeWallTime: number;
|
|
47
|
+
}
|
|
48
|
+
export declare function isCompileStep(step: Partial<XcactivitylogStep>): boolean;
|
|
49
|
+
export declare function collectTopLevelCompileSteps(step: XcactivitylogStep, results?: XcactivitylogStep[]): XcactivitylogStep[];
|
|
50
|
+
export declare function buildTargetMetrics(data: XcactivitylogData, { minTaskSeconds }?: {
|
|
51
|
+
minTaskSeconds?: number;
|
|
52
|
+
}): {
|
|
53
|
+
results: TargetMetric[];
|
|
54
|
+
totalTaskSeconds: number;
|
|
55
|
+
};
|
|
56
|
+
export declare function formatReport(data: XcactivitylogData): string;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseAndReportXcactivitylog = parseAndReportXcactivitylog;
|
|
7
|
+
exports.isCompileStep = isCompileStep;
|
|
8
|
+
exports.collectTopLevelCompileSteps = collectTopLevelCompileSteps;
|
|
9
|
+
exports.buildTargetMetrics = buildTargetMetrics;
|
|
10
|
+
exports.formatReport = formatReport;
|
|
11
|
+
const downloader_1 = __importDefault(require("@expo/downloader"));
|
|
12
|
+
const results_1 = require("@expo/results");
|
|
13
|
+
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
14
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const zod_1 = require("zod");
|
|
18
|
+
const DEFAULT_XCLOGPARSER_VERSION = 'v0.2.46';
|
|
19
|
+
const XCLOGPARSER_DOWNLOAD_URL = 'https://storage.googleapis.com/turtle-v2/xclogparser';
|
|
20
|
+
const XCLOGPARSER_DOWNLOAD_TIMEOUT_MS = 20_000;
|
|
21
|
+
const XCLOGPARSER_OUTPUT_FILENAME = 'xcactivitylog.json';
|
|
22
|
+
/**
|
|
23
|
+
* Download xclogparser, parse xcactivitylog from derived data, and log a
|
|
24
|
+
* compile metrics report. Never throws — all errors are logged at debug level.
|
|
25
|
+
*
|
|
26
|
+
* Can be called from both the step-based flow (BuildFunction) and the
|
|
27
|
+
* traditional builder flow (runBuildPhase).
|
|
28
|
+
*/
|
|
29
|
+
async function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion = DEFAULT_XCLOGPARSER_VERSION, logger, proxyBaseUrl, }) {
|
|
30
|
+
let tempDir;
|
|
31
|
+
try {
|
|
32
|
+
tempDir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'xclogparser-'));
|
|
33
|
+
const xclogparserPath = await downloadXclogparser(tempDir, xclogparserVersion, logger, proxyBaseUrl);
|
|
34
|
+
const jsonOutputPath = await runXclogparser({
|
|
35
|
+
binaryPath: xclogparserPath,
|
|
36
|
+
derivedDataPath,
|
|
37
|
+
workspacePath,
|
|
38
|
+
outputDir: tempDir,
|
|
39
|
+
});
|
|
40
|
+
const data = XcactivitylogDataSchemaZ.parse(JSON.parse(await fs_extra_1.default.readFile(jsonOutputPath, 'utf8')));
|
|
41
|
+
logger.info(formatReport(data));
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
logger.debug({ err }, 'Failed to analyze build performance; continuing without a report');
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
if (tempDir) {
|
|
48
|
+
await (0, results_1.asyncResult)(fs_extra_1.default.rm(tempDir, { force: true, recursive: true }));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function downloadXclogparser(tempDir, version, logger, proxyBaseUrl) {
|
|
53
|
+
const zipName = getXclogparserZipName(version);
|
|
54
|
+
const zipPath = path_1.default.join(tempDir, zipName);
|
|
55
|
+
const directUrl = `${XCLOGPARSER_DOWNLOAD_URL}/${zipName}`;
|
|
56
|
+
const proxiedUrl = getProxiedDownloadUrl({ directUrl, proxyBaseUrl });
|
|
57
|
+
if (proxiedUrl) {
|
|
58
|
+
const proxiedDownloadResult = await (0, results_1.asyncResult)(downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: proxiedUrl }));
|
|
59
|
+
if (!proxiedDownloadResult.ok) {
|
|
60
|
+
logger.debug({ err: proxiedDownloadResult.reason }, 'Failed to prepare xclogparser via the proxy path; falling back to the direct URL');
|
|
61
|
+
await cleanupXclogparserArtifacts({ tempDir, zipPath });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return proxiedDownloadResult.value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return await downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: directUrl });
|
|
68
|
+
}
|
|
69
|
+
async function downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl, }) {
|
|
70
|
+
await (0, downloader_1.default)(sourceUrl, zipPath, { retry: 3, timeout: XCLOGPARSER_DOWNLOAD_TIMEOUT_MS });
|
|
71
|
+
return await unpackXclogparser({ tempDir, zipPath });
|
|
72
|
+
}
|
|
73
|
+
async function unpackXclogparser({ tempDir, zipPath, }) {
|
|
74
|
+
await (0, turtle_spawn_1.default)('unzip', ['-q', zipPath, '-d', tempDir], { stdio: 'pipe' });
|
|
75
|
+
const binaryPath = path_1.default.join(tempDir, 'xclogparser');
|
|
76
|
+
await fs_extra_1.default.chmod(binaryPath, 0o755);
|
|
77
|
+
return binaryPath;
|
|
78
|
+
}
|
|
79
|
+
async function cleanupXclogparserArtifacts({ tempDir, zipPath, }) {
|
|
80
|
+
await (0, results_1.asyncResult)(fs_extra_1.default.rm(zipPath, { force: true }));
|
|
81
|
+
await (0, results_1.asyncResult)(fs_extra_1.default.rm(path_1.default.join(tempDir, 'xclogparser'), { force: true }));
|
|
82
|
+
}
|
|
83
|
+
async function runXclogparser({ binaryPath, derivedDataPath, workspacePath, outputDir, }) {
|
|
84
|
+
const outputPath = path_1.default.join(outputDir, XCLOGPARSER_OUTPUT_FILENAME);
|
|
85
|
+
await (0, turtle_spawn_1.default)(binaryPath, [
|
|
86
|
+
'parse',
|
|
87
|
+
'--derived_data',
|
|
88
|
+
derivedDataPath,
|
|
89
|
+
'--workspace',
|
|
90
|
+
workspacePath,
|
|
91
|
+
'--reporter',
|
|
92
|
+
'json',
|
|
93
|
+
'--output',
|
|
94
|
+
outputPath,
|
|
95
|
+
], { stdio: 'pipe' });
|
|
96
|
+
return outputPath;
|
|
97
|
+
}
|
|
98
|
+
const XcactivitylogStepSchemaZ = zod_1.z.object({
|
|
99
|
+
title: zod_1.z.string().optional(),
|
|
100
|
+
detailStepType: zod_1.z.string().optional(),
|
|
101
|
+
signature: zod_1.z.string().optional(),
|
|
102
|
+
duration: zod_1.z.number().optional(),
|
|
103
|
+
startTimestamp: zod_1.z.number().optional(),
|
|
104
|
+
endTimestamp: zod_1.z.number().optional(),
|
|
105
|
+
get subSteps() {
|
|
106
|
+
return zod_1.z.array(XcactivitylogStepSchemaZ).optional();
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const XcactivitylogDataSchemaZ = zod_1.z.object({
|
|
110
|
+
schema: zod_1.z.union([zod_1.z.string(), zod_1.z.object({ name: zod_1.z.string().optional() })]).optional(),
|
|
111
|
+
subSteps: zod_1.z.array(XcactivitylogStepSchemaZ).optional(),
|
|
112
|
+
});
|
|
113
|
+
const COMPILE_DETAIL_TYPES = new Set(['cCompilation', 'compileAssetsCatalog', 'compileStoryboard']);
|
|
114
|
+
const COMPILE_SIGNATURE_PREFIXES = ['SwiftCompile ', 'SwiftGeneratePch '];
|
|
115
|
+
function getXclogparserZipName(version) {
|
|
116
|
+
return `XCLogParser-macOS-x86-64-arm64-${version}.zip`;
|
|
117
|
+
}
|
|
118
|
+
function getProxiedDownloadUrl({ directUrl, proxyBaseUrl, }) {
|
|
119
|
+
if (!proxyBaseUrl) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const parsedUrl = new URL(directUrl);
|
|
123
|
+
return directUrl.replace(`${parsedUrl.protocol}//${parsedUrl.host}`, `${proxyBaseUrl}/${parsedUrl.host}`);
|
|
124
|
+
}
|
|
125
|
+
function isCompileStep(step) {
|
|
126
|
+
const detailStepType = step.detailStepType ?? '';
|
|
127
|
+
const signature = step.signature ?? '';
|
|
128
|
+
if (COMPILE_DETAIL_TYPES.has(detailStepType)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return COMPILE_SIGNATURE_PREFIXES.some(prefix => signature.startsWith(prefix));
|
|
132
|
+
}
|
|
133
|
+
function collectTopLevelCompileSteps(step, results = []) {
|
|
134
|
+
for (const child of step.subSteps ?? []) {
|
|
135
|
+
if (isCompileStep(child)) {
|
|
136
|
+
results.push(child);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
collectTopLevelCompileSteps(child, results);
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
function intervalFromStep(step) {
|
|
144
|
+
if (typeof step.startTimestamp !== 'number' || typeof step.endTimestamp !== 'number') {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return { start: step.startTimestamp, end: step.endTimestamp };
|
|
148
|
+
}
|
|
149
|
+
function computeActiveWallTime(intervals) {
|
|
150
|
+
if (intervals.length === 0) {
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
const sorted = [...intervals].sort((a, b) => a.start - b.start);
|
|
154
|
+
let activeWallTime = 0;
|
|
155
|
+
let currentStart = sorted[0].start;
|
|
156
|
+
let currentEnd = sorted[0].end;
|
|
157
|
+
for (const interval of sorted.slice(1)) {
|
|
158
|
+
if (interval.start <= currentEnd) {
|
|
159
|
+
currentEnd = Math.max(currentEnd, interval.end);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
activeWallTime += currentEnd - currentStart;
|
|
163
|
+
currentStart = interval.start;
|
|
164
|
+
currentEnd = interval.end;
|
|
165
|
+
}
|
|
166
|
+
activeWallTime += currentEnd - currentStart;
|
|
167
|
+
return activeWallTime;
|
|
168
|
+
}
|
|
169
|
+
function buildTargetMetrics(data, { minTaskSeconds = 0.5 } = {}) {
|
|
170
|
+
const results = [];
|
|
171
|
+
for (const step of data.subSteps ?? []) {
|
|
172
|
+
const title = step.title ?? '';
|
|
173
|
+
if (!title.startsWith('Build target ')) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const moduleName = title.replace('Build target ', '');
|
|
177
|
+
const compileSteps = collectTopLevelCompileSteps(step);
|
|
178
|
+
const taskSeconds = compileSteps.reduce((sum, s) => sum + (s.duration ?? 0), 0);
|
|
179
|
+
if (taskSeconds < minTaskSeconds) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const intervals = compileSteps.map(intervalFromStep).filter((i) => i !== null);
|
|
183
|
+
const minStart = intervals.length > 0 ? Math.min(...intervals.map(i => i.start)) : 0;
|
|
184
|
+
const maxEnd = intervals.length > 0 ? Math.max(...intervals.map(i => i.end)) : 0;
|
|
185
|
+
const wallSpan = intervals.length > 0 ? maxEnd - minStart : 0;
|
|
186
|
+
const activeWallTime = computeActiveWallTime(intervals);
|
|
187
|
+
results.push({ moduleName, taskSeconds, wallSpan, activeWallTime });
|
|
188
|
+
}
|
|
189
|
+
results.sort((a, b) => {
|
|
190
|
+
if (b.taskSeconds !== a.taskSeconds) {
|
|
191
|
+
return b.taskSeconds - a.taskSeconds;
|
|
192
|
+
}
|
|
193
|
+
if (b.wallSpan !== a.wallSpan) {
|
|
194
|
+
return b.wallSpan - a.wallSpan;
|
|
195
|
+
}
|
|
196
|
+
return a.moduleName.localeCompare(b.moduleName);
|
|
197
|
+
});
|
|
198
|
+
const totalTaskSeconds = results.reduce((sum, r) => sum + r.taskSeconds, 0);
|
|
199
|
+
return { results, totalTaskSeconds };
|
|
200
|
+
}
|
|
201
|
+
function formatSeconds(value) {
|
|
202
|
+
return `${value.toFixed(1)}s`;
|
|
203
|
+
}
|
|
204
|
+
function formatReport(data) {
|
|
205
|
+
const { results, totalTaskSeconds } = buildTargetMetrics(data);
|
|
206
|
+
const nameWidth = Math.max(6, ...results.map(r => r.moduleName.length)) + 2;
|
|
207
|
+
const taskWidth = 10;
|
|
208
|
+
const pctWidth = 8;
|
|
209
|
+
const wallWidth = 10;
|
|
210
|
+
const barMaxWidth = 20;
|
|
211
|
+
const maxTaskSeconds = results[0]?.taskSeconds ?? 1;
|
|
212
|
+
const header = '┌─' +
|
|
213
|
+
'─'.repeat(nameWidth) +
|
|
214
|
+
'─┬────────────┬──────────┬────────────┬─' +
|
|
215
|
+
'─'.repeat(barMaxWidth) +
|
|
216
|
+
'─┐';
|
|
217
|
+
const divider = '├─' +
|
|
218
|
+
'─'.repeat(nameWidth) +
|
|
219
|
+
'─┼────────────┼──────────┼────────────┼─' +
|
|
220
|
+
'─'.repeat(barMaxWidth) +
|
|
221
|
+
'─┤';
|
|
222
|
+
const footer = '└─' +
|
|
223
|
+
'─'.repeat(nameWidth) +
|
|
224
|
+
'─┴────────────┴──────────┴────────────┴─' +
|
|
225
|
+
'─'.repeat(barMaxWidth) +
|
|
226
|
+
'─┘';
|
|
227
|
+
const lines = [];
|
|
228
|
+
lines.push('');
|
|
229
|
+
lines.push('Xcode Build — Compile Metrics by Module');
|
|
230
|
+
lines.push(`Schema: ${typeof data.schema === 'string' ? data.schema : (data.schema?.name ?? 'unknown')}`);
|
|
231
|
+
lines.push('% Task = share of total Compile Task Seconds');
|
|
232
|
+
lines.push('Wall = first compile start to last compile end');
|
|
233
|
+
lines.push('');
|
|
234
|
+
lines.push(header);
|
|
235
|
+
lines.push('│ ' +
|
|
236
|
+
'Module'.padEnd(nameWidth) +
|
|
237
|
+
' │ ' +
|
|
238
|
+
'Task'.padStart(taskWidth) +
|
|
239
|
+
' │ ' +
|
|
240
|
+
'% Task'.padStart(pctWidth) +
|
|
241
|
+
' │ ' +
|
|
242
|
+
'Wall'.padStart(wallWidth) +
|
|
243
|
+
' │ ' +
|
|
244
|
+
' '.repeat(barMaxWidth) +
|
|
245
|
+
' │');
|
|
246
|
+
lines.push(divider);
|
|
247
|
+
for (const result of results) {
|
|
248
|
+
const pct = totalTaskSeconds === 0 ? 0 : (result.taskSeconds / totalTaskSeconds) * 100;
|
|
249
|
+
const barLength = Math.round((result.taskSeconds / maxTaskSeconds) * barMaxWidth);
|
|
250
|
+
const bar = '█'.repeat(barLength) + '░'.repeat(barMaxWidth - barLength);
|
|
251
|
+
lines.push('│ ' +
|
|
252
|
+
result.moduleName.padEnd(nameWidth) +
|
|
253
|
+
' │ ' +
|
|
254
|
+
formatSeconds(result.taskSeconds).padStart(taskWidth) +
|
|
255
|
+
' │ ' +
|
|
256
|
+
`${pct.toFixed(1)}%`.padStart(pctWidth) +
|
|
257
|
+
' │ ' +
|
|
258
|
+
formatSeconds(result.wallSpan).padStart(wallWidth) +
|
|
259
|
+
' │ ' +
|
|
260
|
+
bar +
|
|
261
|
+
' │');
|
|
262
|
+
}
|
|
263
|
+
lines.push(divider);
|
|
264
|
+
lines.push('│ ' +
|
|
265
|
+
'TOTAL'.padEnd(nameWidth) +
|
|
266
|
+
' │ ' +
|
|
267
|
+
formatSeconds(totalTaskSeconds).padStart(taskWidth) +
|
|
268
|
+
' │ ' +
|
|
269
|
+
'100.0%'.padStart(pctWidth) +
|
|
270
|
+
' │ ' +
|
|
271
|
+
'n/a'.padStart(wallWidth) +
|
|
272
|
+
' │ ' +
|
|
273
|
+
' '.repeat(barMaxWidth) +
|
|
274
|
+
' │');
|
|
275
|
+
lines.push(footer);
|
|
276
|
+
lines.push('');
|
|
277
|
+
return lines.join('\n');
|
|
278
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const GymfileArchiveTemplate = "suppress_xcode_output(true)\nclean(<%- CLEAN %>)\n\nscheme(\"<%- SCHEME %>\")\n<% if (SCHEME_BUILD_CONFIGURATION) { %>\nconfiguration(\"<%- SCHEME_BUILD_CONFIGURATION %>\")\n<% } %>\n\nexport_options({\n method: \"<%- EXPORT_METHOD %>\",\n provisioningProfiles: {<% _.forEach(PROFILES, function(profile) { %>\n \"<%- profile.BUNDLE_ID %>\" => \"<%- profile.UUID %>\",<% }); %>\n }<% if (ICLOUD_CONTAINER_ENVIRONMENT) { %>,\n iCloudContainerEnvironment: \"<%- ICLOUD_CONTAINER_ENVIRONMENT %>\"\n<% } %>\n})\n\nexport_xcargs \"OTHER_CODE_SIGN_FLAGS=\\\"--keychain <%- KEYCHAIN_PATH %>\\\"\"\n\ndisable_xcpretty(true)\nbuildlog_path(\"<%- LOGS_DIRECTORY %>\")\n\noutput_directory(\"<%- OUTPUT_DIRECTORY %>\")\n";
|
|
1
|
+
export declare const GymfileArchiveTemplate = "suppress_xcode_output(true)\nclean(<%- CLEAN %>)\n\nscheme(\"<%- SCHEME %>\")\n<% if (SCHEME_BUILD_CONFIGURATION) { %>\nconfiguration(\"<%- SCHEME_BUILD_CONFIGURATION %>\")\n<% } %>\n\nexport_options({\n method: \"<%- EXPORT_METHOD %>\",\n provisioningProfiles: {<% _.forEach(PROFILES, function(profile) { %>\n \"<%- profile.BUNDLE_ID %>\" => \"<%- profile.UUID %>\",<% }); %>\n }<% if (ICLOUD_CONTAINER_ENVIRONMENT) { %>,\n iCloudContainerEnvironment: \"<%- ICLOUD_CONTAINER_ENVIRONMENT %>\"\n<% } %>\n})\n\nexport_xcargs \"OTHER_CODE_SIGN_FLAGS=\\\"--keychain <%- KEYCHAIN_PATH %>\\\"\"\n\ndisable_xcpretty(true)\nbuildlog_path(\"<%- LOGS_DIRECTORY %>\")\n\nderived_data_path(\"<%- DERIVED_DATA_PATH %>\")\nresult_bundle(true)\nresult_bundle_path(\"<%- RESULT_BUNDLE_PATH %>\")\n\noutput_directory(\"<%- OUTPUT_DIRECTORY %>\")\n";
|
|
@@ -23,5 +23,9 @@ export_xcargs "OTHER_CODE_SIGN_FLAGS=\\"--keychain <%- KEYCHAIN_PATH %>\\""
|
|
|
23
23
|
disable_xcpretty(true)
|
|
24
24
|
buildlog_path("<%- LOGS_DIRECTORY %>")
|
|
25
25
|
|
|
26
|
+
derived_data_path("<%- DERIVED_DATA_PATH %>")
|
|
27
|
+
result_bundle(true)
|
|
28
|
+
result_bundle_path("<%- RESULT_BUNDLE_PATH %>")
|
|
29
|
+
|
|
26
30
|
output_directory("<%- OUTPUT_DIRECTORY %>")
|
|
27
31
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const GymfileSimulatorTemplate = "suppress_xcode_output(true)\nclean(<%- CLEAN %>)\n\nscheme(\"<%- SCHEME %>\")\n<% if (SCHEME_BUILD_CONFIGURATION) { %>\nconfiguration(\"<%- SCHEME_BUILD_CONFIGURATION %>\")\n<% } %>\n\nderived_data_path(\"<%- DERIVED_DATA_PATH %>\")\nskip_package_ipa(true)\nskip_archive(true)\ndestination(\"<%- SCHEME_SIMULATOR_DESTINATION %>\")\n\ndisable_xcpretty(true)\nbuildlog_path(\"<%- LOGS_DIRECTORY %>\")\n";
|
|
1
|
+
export declare const GymfileSimulatorTemplate = "suppress_xcode_output(true)\nclean(<%- CLEAN %>)\n\nscheme(\"<%- SCHEME %>\")\n<% if (SCHEME_BUILD_CONFIGURATION) { %>\nconfiguration(\"<%- SCHEME_BUILD_CONFIGURATION %>\")\n<% } %>\n\nderived_data_path(\"<%- DERIVED_DATA_PATH %>\")\nskip_package_ipa(true)\nskip_archive(true)\ndestination(\"<%- SCHEME_SIMULATOR_DESTINATION %>\")\n\ndisable_xcpretty(true)\nbuildlog_path(\"<%- LOGS_DIRECTORY %>\")\n\nresult_bundle(true)\nresult_bundle_path(\"<%- RESULT_BUNDLE_PATH %>\")\n";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SpawnOptions, SpawnResult } from '@expo/turtle-spawn';
|
|
2
|
+
import { Env } from '@expo/eas-build-job';
|
|
3
|
+
export declare function resolveEasCommandPrefixAndEnvAsync(): Promise<{
|
|
4
|
+
cmd: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
extraEnv: Env;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function runEasCliCommand({ args, options, }: {
|
|
9
|
+
args: string[];
|
|
10
|
+
options: SpawnOptions;
|
|
11
|
+
}): Promise<SpawnResult>;
|