@expo/build-tools 18.13.1 → 19.0.2
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/android/gradle.js +7 -2
- package/dist/android/gradleProfile.d.ts +7 -0
- package/dist/android/gradleProfile.js +192 -0
- package/dist/builders/ios.js +15 -5
- package/dist/context.js +8 -0
- package/dist/datadog.d.ts +11 -0
- package/dist/datadog.js +41 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -1
- package/dist/steps/functions/parseXcactivitylog.js +1 -0
- package/dist/steps/utils/android/gradle.js +6 -2
- package/dist/steps/utils/ios/xcactivitylog.d.ts +3 -1
- package/dist/steps/utils/ios/xcactivitylog.js +46 -14
- package/package.json +8 -8
package/dist/android/gradle.js
CHANGED
|
@@ -24,7 +24,7 @@ async function runGradleCommand(ctx, { logger, gradleCommand, androidDir, extraE
|
|
|
24
24
|
logger.info(`Running 'gradlew ${gradleCommand}' in ${androidDir}`);
|
|
25
25
|
await fs_extra_1.default.chmod(path_1.default.join(androidDir, 'gradlew'), 0o755);
|
|
26
26
|
const verboseFlag = ctx.env['EAS_VERBOSE'] === '1' ? '--info' : '';
|
|
27
|
-
const spawnPromise = (0, turtle_spawn_1.default)('bash', ['-c', `./gradlew ${gradleCommand} ${verboseFlag}`], {
|
|
27
|
+
const spawnPromise = (0, turtle_spawn_1.default)('bash', ['-c', `./gradlew ${gradleCommand} --profile ${verboseFlag}`], {
|
|
28
28
|
cwd: androidDir,
|
|
29
29
|
logger,
|
|
30
30
|
lineTransformer: (line) => {
|
|
@@ -35,7 +35,12 @@ async function runGradleCommand(ctx, { logger, gradleCommand, androidDir, extraE
|
|
|
35
35
|
return line;
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
env: {
|
|
38
|
+
env: {
|
|
39
|
+
...ctx.env,
|
|
40
|
+
...extraEnv,
|
|
41
|
+
...resolveVersionOverridesEnvs(ctx),
|
|
42
|
+
LC_ALL: 'C.UTF-8',
|
|
43
|
+
},
|
|
39
44
|
});
|
|
40
45
|
if (ctx.env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux') {
|
|
41
46
|
adjustOOMScore(spawnPromise, logger);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface GradleProfileTask {
|
|
2
|
+
path: string;
|
|
3
|
+
durationMs: number;
|
|
4
|
+
result: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseGradleProfile(androidDir: string): Promise<GradleProfileTask[]>;
|
|
7
|
+
export declare function formatGradleProfileReport(tasks: GradleProfileTask[]): string;
|
|
@@ -0,0 +1,192 @@
|
|
|
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.parseGradleProfile = parseGradleProfile;
|
|
7
|
+
exports.formatGradleProfileReport = formatGradleProfileReport;
|
|
8
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
async function parseGradleProfile(androidDir) {
|
|
12
|
+
const profileDir = path_1.default.join(androidDir, 'build', 'reports', 'profile');
|
|
13
|
+
// Gradle profile may not exist for all builds (e.g. custom, repack
|
|
14
|
+
// jobs). We should short circuit and not unnecessarily log.
|
|
15
|
+
if (!(await fs_extra_1.default.pathExists(profileDir))) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const files = await fs_extra_1.default.readdir(profileDir);
|
|
19
|
+
const htmlFile = files
|
|
20
|
+
.filter(f => f.startsWith('profile-') && f.endsWith('.html'))
|
|
21
|
+
.sort()
|
|
22
|
+
.pop();
|
|
23
|
+
if (!htmlFile) {
|
|
24
|
+
throw new Error(`No Gradle profile HTML found in ${profileDir}`);
|
|
25
|
+
}
|
|
26
|
+
const html = await fs_extra_1.default.readFile(path_1.default.join(profileDir, htmlFile), 'utf8');
|
|
27
|
+
// Locate the <h2>Task Execution</h2> heading and extract its <table>
|
|
28
|
+
const headingMatch = html.match(/<h2[^>]*>\s*Task Execution\s*<\/h2>/i);
|
|
29
|
+
if (!headingMatch || headingMatch.index === undefined) {
|
|
30
|
+
throw new Error('Could not find Task Execution section in Gradle profile');
|
|
31
|
+
}
|
|
32
|
+
const sectionStart = headingMatch.index;
|
|
33
|
+
const tableStart = html.indexOf('<table', sectionStart);
|
|
34
|
+
const tableEnd = html.indexOf('</table>', tableStart);
|
|
35
|
+
if (tableStart === -1 || tableEnd === -1) {
|
|
36
|
+
throw new Error('Could not find task execution table in Gradle profile');
|
|
37
|
+
}
|
|
38
|
+
const tableHtml = html.slice(tableStart, tableEnd + '</table>'.length);
|
|
39
|
+
const parser = new fast_xml_parser_1.XMLParser({
|
|
40
|
+
ignoreAttributes: true,
|
|
41
|
+
isArray: name => name === 'tr' || name === 'td',
|
|
42
|
+
trimValues: true,
|
|
43
|
+
});
|
|
44
|
+
const parsed = parser.parse(tableHtml);
|
|
45
|
+
const rows = parsed?.table?.tbody?.tr ?? parsed?.table?.tr ?? [];
|
|
46
|
+
const tasks = [];
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
const cells = row?.td;
|
|
49
|
+
if (!cells || cells.length < 2) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const taskPath = String(cells[0] ?? '').trim();
|
|
53
|
+
const durationStr = String(cells[1] ?? '').trim();
|
|
54
|
+
const result = String(cells[2] ?? '').trim() || 'executed';
|
|
55
|
+
if (!taskPath || !durationStr) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const durationMs = parseDurationToMs(durationStr);
|
|
59
|
+
tasks.push({ path: taskPath, durationMs, result: result.toLowerCase() });
|
|
60
|
+
}
|
|
61
|
+
return tasks;
|
|
62
|
+
}
|
|
63
|
+
function parseDurationToMs(duration) {
|
|
64
|
+
// Gradle profile durations can be like "1.234s", "0.045s", "12.5s"
|
|
65
|
+
const secondsMatch = duration.match(/^([\d.]+)s$/);
|
|
66
|
+
if (secondsMatch) {
|
|
67
|
+
return Math.round(parseFloat(secondsMatch[1]) * 1000);
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
function formatSeconds(ms) {
|
|
72
|
+
const s = ms / 1000;
|
|
73
|
+
if (s < 0.1) {
|
|
74
|
+
return `${ms}ms`;
|
|
75
|
+
}
|
|
76
|
+
return `${s.toFixed(1)}s`;
|
|
77
|
+
}
|
|
78
|
+
function formatGradleProfileReport(tasks) {
|
|
79
|
+
// Filter out tasks under 1 second
|
|
80
|
+
const significantTasks = tasks.filter(t => t.durationMs >= 1000);
|
|
81
|
+
// Separate module totals from individual tasks
|
|
82
|
+
const moduleTotals = significantTasks.filter(t => t.result === '(total)');
|
|
83
|
+
const individualTasks = significantTasks.filter(t => t.result !== '(total)');
|
|
84
|
+
// Group individual tasks by their module prefix
|
|
85
|
+
const moduleChildren = new Map();
|
|
86
|
+
const orphanTasks = [];
|
|
87
|
+
for (const task of individualTasks) {
|
|
88
|
+
const parent = moduleTotals.find(m => task.path.startsWith(m.path + ':'));
|
|
89
|
+
if (parent) {
|
|
90
|
+
const children = moduleChildren.get(parent.path) ?? [];
|
|
91
|
+
children.push(task);
|
|
92
|
+
moduleChildren.set(parent.path, children);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
orphanTasks.push(task);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Sort module totals by duration, sort children within each module
|
|
99
|
+
const sortedModules = [...moduleTotals].sort((a, b) => b.durationMs - a.durationMs);
|
|
100
|
+
for (const children of moduleChildren.values()) {
|
|
101
|
+
children.sort((a, b) => b.durationMs - a.durationMs);
|
|
102
|
+
}
|
|
103
|
+
orphanTasks.sort((a, b) => b.durationMs - a.durationMs);
|
|
104
|
+
// Build display rows: [displayName, task]
|
|
105
|
+
const rows = [];
|
|
106
|
+
for (const mod of sortedModules) {
|
|
107
|
+
rows.push({ displayName: mod.path, task: mod });
|
|
108
|
+
const children = moduleChildren.get(mod.path) ?? [];
|
|
109
|
+
for (let i = 0; i < children.length; i++) {
|
|
110
|
+
const isLast = i === children.length - 1;
|
|
111
|
+
const prefix = isLast ? ' └─ ' : ' ├─ ';
|
|
112
|
+
const shortName = children[i].path.slice(mod.path.length + 1);
|
|
113
|
+
rows.push({ displayName: prefix + shortName, task: children[i] });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const task of orphanTasks) {
|
|
117
|
+
rows.push({ displayName: task.path, task });
|
|
118
|
+
}
|
|
119
|
+
// Compute totals from individual tasks only (avoid double-counting)
|
|
120
|
+
const totalMs = individualTasks.reduce((sum, t) => sum + t.durationMs, 0);
|
|
121
|
+
const maxMs = totalMs || 1;
|
|
122
|
+
const nameWidth = Math.max(4, ...rows.map(r => r.displayName.length)) + 2;
|
|
123
|
+
const barMaxWidth = 20;
|
|
124
|
+
const header = '┌─' +
|
|
125
|
+
'─'.repeat(nameWidth) +
|
|
126
|
+
'─┬────────────┬──────────┬────────────┬─' +
|
|
127
|
+
'─'.repeat(barMaxWidth) +
|
|
128
|
+
'─┐';
|
|
129
|
+
const divider = '├─' +
|
|
130
|
+
'─'.repeat(nameWidth) +
|
|
131
|
+
'─┼────────────┼──────────┼────────────┼─' +
|
|
132
|
+
'─'.repeat(barMaxWidth) +
|
|
133
|
+
'─┤';
|
|
134
|
+
const footer = '└─' +
|
|
135
|
+
'─'.repeat(nameWidth) +
|
|
136
|
+
'─┴────────────┴──────────┴────────────┴─' +
|
|
137
|
+
'─'.repeat(barMaxWidth) +
|
|
138
|
+
'─┘';
|
|
139
|
+
const taskCount = individualTasks.length;
|
|
140
|
+
const cachedCount = individualTasks.filter(t => t.result !== 'executed').length;
|
|
141
|
+
const lines = [];
|
|
142
|
+
lines.push('Gradle Build — Task Execution Profile');
|
|
143
|
+
const cachedSuffix = cachedCount > 0 ? ` (${cachedCount} cached/up-to-date)` : '';
|
|
144
|
+
lines.push(`${taskCount} tasks${cachedSuffix}, total task time: ${formatSeconds(totalMs)}`);
|
|
145
|
+
lines.push('% Time = share of total task execution time');
|
|
146
|
+
lines.push('');
|
|
147
|
+
lines.push(header);
|
|
148
|
+
lines.push('│ ' +
|
|
149
|
+
'Task'.padEnd(nameWidth) +
|
|
150
|
+
' │ ' +
|
|
151
|
+
'Duration'.padStart(10) +
|
|
152
|
+
' │ ' +
|
|
153
|
+
'% Time'.padStart(8) +
|
|
154
|
+
' │ ' +
|
|
155
|
+
'Result'.padEnd(10) +
|
|
156
|
+
' │ ' +
|
|
157
|
+
' '.repeat(barMaxWidth) +
|
|
158
|
+
' │');
|
|
159
|
+
lines.push(divider);
|
|
160
|
+
for (const row of rows) {
|
|
161
|
+
const pct = totalMs === 0 ? 0 : (row.task.durationMs / totalMs) * 100;
|
|
162
|
+
const barLength = Math.round((row.task.durationMs / maxMs) * barMaxWidth);
|
|
163
|
+
const bar = '█'.repeat(barLength) + '░'.repeat(barMaxWidth - barLength);
|
|
164
|
+
const result = row.task.result === '(total)' ? 'total' : row.task.result;
|
|
165
|
+
lines.push('│ ' +
|
|
166
|
+
row.displayName.padEnd(nameWidth) +
|
|
167
|
+
' │ ' +
|
|
168
|
+
formatSeconds(row.task.durationMs).padStart(10) +
|
|
169
|
+
' │ ' +
|
|
170
|
+
`${pct.toFixed(1)}%`.padStart(8) +
|
|
171
|
+
' │ ' +
|
|
172
|
+
result.padEnd(10) +
|
|
173
|
+
' │ ' +
|
|
174
|
+
bar +
|
|
175
|
+
' │');
|
|
176
|
+
}
|
|
177
|
+
lines.push(divider);
|
|
178
|
+
lines.push('│ ' +
|
|
179
|
+
'TOTAL'.padEnd(nameWidth) +
|
|
180
|
+
' │ ' +
|
|
181
|
+
formatSeconds(totalMs).padStart(10) +
|
|
182
|
+
' │ ' +
|
|
183
|
+
'100.0%'.padStart(8) +
|
|
184
|
+
' │ ' +
|
|
185
|
+
' '.repeat(10) +
|
|
186
|
+
' │ ' +
|
|
187
|
+
' '.repeat(barMaxWidth) +
|
|
188
|
+
' │');
|
|
189
|
+
lines.push(footer);
|
|
190
|
+
lines.push('');
|
|
191
|
+
return lines.join('\n');
|
|
192
|
+
}
|
package/dist/builders/ios.js
CHANGED
|
@@ -20,6 +20,7 @@ const fastlane_1 = require("../ios/fastlane");
|
|
|
20
20
|
const pod_1 = require("../ios/pod");
|
|
21
21
|
const resign_1 = require("../ios/resign");
|
|
22
22
|
const resolve_1 = require("../ios/resolve");
|
|
23
|
+
const sentry_1 = require("../sentry");
|
|
23
24
|
const xcactivitylog_1 = require("../steps/utils/ios/xcactivitylog");
|
|
24
25
|
const restoreBuildCache_1 = require("../steps/functions/restoreBuildCache");
|
|
25
26
|
const saveBuildCache_1 = require("../steps/functions/saveBuildCache");
|
|
@@ -158,17 +159,26 @@ async function buildAsync(ctx) {
|
|
|
158
159
|
await credentialsManager.cleanUp();
|
|
159
160
|
});
|
|
160
161
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
await ctx.runBuildPhase(eas_build_job_1.BuildPhase.PARSE_XCACTIVITYLOG, async () => {
|
|
163
|
+
if (ctx.isLocal) {
|
|
164
|
+
ctx.logger.info('Local builds skip build performance analysis.');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const { derivedDataPath, workspacePath } = (0, nullthrows_1.default)(fastlaneResult);
|
|
164
169
|
await (0, xcactivitylog_1.parseAndReportXcactivitylog)({
|
|
165
170
|
derivedDataPath,
|
|
166
171
|
workspacePath,
|
|
167
172
|
logger: ctx.logger,
|
|
168
173
|
proxyBaseUrl: ctx.env.EAS_BUILD_COCOAPODS_CACHE_URL,
|
|
174
|
+
env: ctx.env,
|
|
169
175
|
});
|
|
170
|
-
}
|
|
171
|
-
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
sentry_1.Sentry.capture('Failed to parse xcactivitylog', err);
|
|
179
|
+
ctx.markBuildPhaseSkipped();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
172
182
|
await ctx.runBuildPhase(eas_build_job_1.BuildPhase.PRE_UPLOAD_ARTIFACTS_HOOK, async () => {
|
|
173
183
|
await (0, hooks_1.runHookIfPresent)(ctx, hooks_1.Hook.PRE_UPLOAD_ARTIFACTS);
|
|
174
184
|
});
|
package/dist/context.js
CHANGED
|
@@ -10,6 +10,7 @@ const core_1 = require("@urql/core");
|
|
|
10
10
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const detectError_1 = require("./buildErrors/detectError");
|
|
13
|
+
const datadog_1 = require("./datadog");
|
|
13
14
|
const appConfig_1 = require("./utils/appConfig");
|
|
14
15
|
const environmentSecrets_1 = require("./utils/environmentSecrets");
|
|
15
16
|
const packageManager_1 = require("./utils/packageManager");
|
|
@@ -235,6 +236,13 @@ class BuildContext {
|
|
|
235
236
|
}
|
|
236
237
|
await this.collectAndUpdateEnvVariablesAsync();
|
|
237
238
|
this.reportBuildPhaseStats?.({ buildPhase: this.buildPhase, result, durationMs });
|
|
239
|
+
if (this.job.platform) {
|
|
240
|
+
datadog_1.Datadog.distribution('eas.build.phase_duration', durationMs, {
|
|
241
|
+
build_phase: this.buildPhase.toLowerCase(),
|
|
242
|
+
platform: this.job.platform,
|
|
243
|
+
result,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
238
246
|
if (!doNotMarkEnd) {
|
|
239
247
|
this.logger.info({ marker: eas_build_job_1.LogMarker.END_PHASE, result, durationMs }, `End phase: ${this.buildPhase}`);
|
|
240
248
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type DatadogSetupOptions = {
|
|
2
|
+
expoApiV2BaseUrl: string;
|
|
3
|
+
turtleBuildId: string;
|
|
4
|
+
robotAccessToken: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const Datadog: {
|
|
7
|
+
setup(opts: DatadogSetupOptions | null): void;
|
|
8
|
+
distribution(name: string, value: number, tags?: Record<string, string>): void;
|
|
9
|
+
flushAsync(): Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
package/dist/datadog.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Datadog = void 0;
|
|
4
|
+
const sentry_1 = require("./sentry");
|
|
5
|
+
const turtleFetch_1 = require("./utils/turtleFetch");
|
|
6
|
+
let setupOptions = null;
|
|
7
|
+
let pendingMetricUploads = [];
|
|
8
|
+
exports.Datadog = {
|
|
9
|
+
setup(opts) {
|
|
10
|
+
setupOptions = opts;
|
|
11
|
+
},
|
|
12
|
+
distribution(name, value, tags = {}) {
|
|
13
|
+
if (!setupOptions) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const { expoApiV2BaseUrl, turtleBuildId, robotAccessToken } = setupOptions;
|
|
17
|
+
const metrics = [
|
|
18
|
+
{
|
|
19
|
+
name,
|
|
20
|
+
type: 'distribution',
|
|
21
|
+
value,
|
|
22
|
+
tags,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
const uploadPromise = (0, turtleFetch_1.turtleFetch)(new URL(`turtle-builds/${turtleBuildId}/metrics`, expoApiV2BaseUrl).toString(), 'POST', {
|
|
26
|
+
json: { metrics },
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${robotAccessToken}`,
|
|
29
|
+
},
|
|
30
|
+
retries: 2,
|
|
31
|
+
}).catch(err => {
|
|
32
|
+
sentry_1.Sentry.capture('Failed to report turtle build metric', err, {
|
|
33
|
+
extras: { metrics },
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
pendingMetricUploads.push(uploadPromise);
|
|
37
|
+
},
|
|
38
|
+
async flushAsync() {
|
|
39
|
+
await Promise.allSettled(pendingMetricUploads);
|
|
40
|
+
},
|
|
41
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,8 @@ export { ArtifactToUpload, Artifacts, BuildContext, BuildContextOptions, CacheMa
|
|
|
6
6
|
export { PackageManager } from './utils/packageManager';
|
|
7
7
|
export { findAndUploadXcodeBuildLogsAsync } from './ios/xcodeBuildLogs';
|
|
8
8
|
export { Hook, runHookIfPresent } from './utils/hooks';
|
|
9
|
+
export { parseGradleProfile, formatGradleProfileReport } from './android/gradleProfile';
|
|
10
|
+
export type { GradleProfileTask } from './android/gradleProfile';
|
|
9
11
|
export * from './generic';
|
|
12
|
+
export { Datadog } from './datadog';
|
|
10
13
|
export { Sentry } from './sentry';
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.Sentry = exports.runHookIfPresent = exports.Hook = exports.findAndUploadXcodeBuildLogsAsync = exports.PackageManager = exports.SkipNativeBuildError = exports.BuildContext = exports.GCSLoggerStream = exports.GCS = exports.Builders = void 0;
|
|
42
|
+
exports.Sentry = exports.Datadog = exports.formatGradleProfileReport = exports.parseGradleProfile = exports.runHookIfPresent = exports.Hook = exports.findAndUploadXcodeBuildLogsAsync = exports.PackageManager = exports.SkipNativeBuildError = exports.BuildContext = exports.GCSLoggerStream = exports.GCS = exports.Builders = void 0;
|
|
43
43
|
const Builders = __importStar(require("./builders"));
|
|
44
44
|
exports.Builders = Builders;
|
|
45
45
|
const LoggerStream_1 = __importDefault(require("./gcs/LoggerStream"));
|
|
@@ -56,6 +56,11 @@ Object.defineProperty(exports, "findAndUploadXcodeBuildLogsAsync", { enumerable:
|
|
|
56
56
|
var hooks_1 = require("./utils/hooks");
|
|
57
57
|
Object.defineProperty(exports, "Hook", { enumerable: true, get: function () { return hooks_1.Hook; } });
|
|
58
58
|
Object.defineProperty(exports, "runHookIfPresent", { enumerable: true, get: function () { return hooks_1.runHookIfPresent; } });
|
|
59
|
+
var gradleProfile_1 = require("./android/gradleProfile");
|
|
60
|
+
Object.defineProperty(exports, "parseGradleProfile", { enumerable: true, get: function () { return gradleProfile_1.parseGradleProfile; } });
|
|
61
|
+
Object.defineProperty(exports, "formatGradleProfileReport", { enumerable: true, get: function () { return gradleProfile_1.formatGradleProfileReport; } });
|
|
59
62
|
__exportStar(require("./generic"), exports);
|
|
63
|
+
var datadog_1 = require("./datadog");
|
|
64
|
+
Object.defineProperty(exports, "Datadog", { enumerable: true, get: function () { return datadog_1.Datadog; } });
|
|
60
65
|
var sentry_1 = require("./sentry");
|
|
61
66
|
Object.defineProperty(exports, "Sentry", { enumerable: true, get: function () { return sentry_1.Sentry; } });
|
|
@@ -14,7 +14,7 @@ async function runGradleCommand({ logger, gradleCommand, androidDir, env, extraE
|
|
|
14
14
|
const verboseFlag = env['EAS_VERBOSE'] === '1' ? '--info' : '';
|
|
15
15
|
logger.info(`Running 'gradlew ${gradleCommand} ${verboseFlag}' in ${androidDir}`);
|
|
16
16
|
await fs_extra_1.default.chmod(path_1.default.join(androidDir, 'gradlew'), 0o755);
|
|
17
|
-
const spawnPromise = (0, turtle_spawn_1.default)('bash', ['-c', `./gradlew ${gradleCommand} ${verboseFlag}`], {
|
|
17
|
+
const spawnPromise = (0, turtle_spawn_1.default)('bash', ['-c', `./gradlew ${gradleCommand} --profile ${verboseFlag}`], {
|
|
18
18
|
cwd: androidDir,
|
|
19
19
|
logger,
|
|
20
20
|
lineTransformer: (line) => {
|
|
@@ -25,7 +25,11 @@ async function runGradleCommand({ logger, gradleCommand, androidDir, env, extraE
|
|
|
25
25
|
return line;
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
-
env: {
|
|
28
|
+
env: {
|
|
29
|
+
...env,
|
|
30
|
+
...extraEnv,
|
|
31
|
+
LC_ALL: 'C.UTF-8',
|
|
32
|
+
},
|
|
29
33
|
});
|
|
30
34
|
if (env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux') {
|
|
31
35
|
adjustOOMScore(spawnPromise, logger);
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { bunyan } from '@expo/logger';
|
|
2
|
+
import { BuildStepEnv } from '@expo/steps';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
/**
|
|
4
5
|
* Never throws — best-effort observability that does not affect build status.
|
|
5
6
|
* Failures route to Sentry via `Sentry.capture` for engineering triage;
|
|
6
7
|
* users see only a generic skip message.
|
|
7
8
|
*/
|
|
8
|
-
export declare function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion, logger, proxyBaseUrl, }: {
|
|
9
|
+
export declare function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion, logger, proxyBaseUrl, env, }: {
|
|
9
10
|
derivedDataPath: string;
|
|
10
11
|
workspacePath: string;
|
|
11
12
|
xclogparserVersion?: string;
|
|
12
13
|
logger: bunyan;
|
|
13
14
|
proxyBaseUrl?: string;
|
|
15
|
+
env: BuildStepEnv;
|
|
14
16
|
}): Promise<void>;
|
|
15
17
|
declare const XcactivitylogStepSchemaZ: z.ZodObject<{
|
|
16
18
|
title: z.ZodOptional<z.ZodString>;
|
|
@@ -25,19 +25,40 @@ const XCLOGPARSER_OUTPUT_FILENAME = 'xcactivitylog.json';
|
|
|
25
25
|
* Failures route to Sentry via `Sentry.capture` for engineering triage;
|
|
26
26
|
* users see only a generic skip message.
|
|
27
27
|
*/
|
|
28
|
-
async function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion
|
|
28
|
+
async function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion, logger, proxyBaseUrl, env, }) {
|
|
29
29
|
let tempDir;
|
|
30
30
|
let phase = 'creating_temp_directory';
|
|
31
31
|
try {
|
|
32
32
|
tempDir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'xclogparser-'));
|
|
33
|
-
phase = '
|
|
34
|
-
const
|
|
33
|
+
phase = 'resolving_xclogparser';
|
|
34
|
+
const preinstalledVersion = await detectPreinstalledXclogparserVersion(env);
|
|
35
|
+
let binaryPath;
|
|
36
|
+
let resolvedVersion;
|
|
37
|
+
if (preinstalledVersion &&
|
|
38
|
+
(!xclogparserVersion || preinstalledVersion === xclogparserVersion)) {
|
|
39
|
+
binaryPath = 'xclogparser';
|
|
40
|
+
resolvedVersion = preinstalledVersion;
|
|
41
|
+
logger.info(`Using preinstalled xclogparser ${resolvedVersion}.`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
phase = 'downloading_xclogparser';
|
|
45
|
+
resolvedVersion = xclogparserVersion ?? DEFAULT_XCLOGPARSER_VERSION;
|
|
46
|
+
binaryPath = await downloadXclogparser({
|
|
47
|
+
tempDir,
|
|
48
|
+
version: resolvedVersion,
|
|
49
|
+
logger,
|
|
50
|
+
proxyBaseUrl,
|
|
51
|
+
env,
|
|
52
|
+
});
|
|
53
|
+
logger.info(`Using downloaded xclogparser ${resolvedVersion}.`);
|
|
54
|
+
}
|
|
35
55
|
phase = 'running_xclogparser';
|
|
36
56
|
const jsonOutputPath = await runXclogparser({
|
|
37
|
-
binaryPath
|
|
57
|
+
binaryPath,
|
|
38
58
|
derivedDataPath,
|
|
39
59
|
workspacePath,
|
|
40
60
|
outputDir: tempDir,
|
|
61
|
+
env,
|
|
41
62
|
});
|
|
42
63
|
phase = 'parsing_xclogparser_output';
|
|
43
64
|
const data = XcactivitylogDataSchemaZ.parse(JSON.parse(await fs_extra_1.default.readFile(jsonOutputPath, 'utf8')));
|
|
@@ -54,13 +75,21 @@ async function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xcl
|
|
|
54
75
|
}
|
|
55
76
|
}
|
|
56
77
|
}
|
|
57
|
-
async function
|
|
78
|
+
async function detectPreinstalledXclogparserVersion(env) {
|
|
79
|
+
const result = await (0, results_1.asyncResult)((0, turtle_spawn_1.default)('xclogparser', ['version'], { stdio: 'pipe', env }));
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const match = result.value.stdout.match(/(\d+\.\d+\.\d+)/);
|
|
84
|
+
return match ? `v${match[1]}` : null;
|
|
85
|
+
}
|
|
86
|
+
async function downloadXclogparser({ tempDir, version, logger, proxyBaseUrl, env, }) {
|
|
58
87
|
const zipName = getXclogparserZipName(version);
|
|
59
88
|
const zipPath = path_1.default.join(tempDir, zipName);
|
|
60
89
|
const directUrl = `${XCLOGPARSER_DOWNLOAD_URL}/${zipName}`;
|
|
61
90
|
const proxiedUrl = getProxiedDownloadUrl({ directUrl, proxyBaseUrl });
|
|
62
91
|
if (proxiedUrl) {
|
|
63
|
-
const proxiedDownloadResult = await (0, results_1.asyncResult)(downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: proxiedUrl }));
|
|
92
|
+
const proxiedDownloadResult = await (0, results_1.asyncResult)(downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: proxiedUrl, env }));
|
|
64
93
|
if (!proxiedDownloadResult.ok) {
|
|
65
94
|
logger.debug({ err: proxiedDownloadResult.reason }, 'Failed to prepare xclogparser via the proxy path; falling back to the direct URL');
|
|
66
95
|
await cleanupXclogparserArtifacts({ tempDir, zipPath });
|
|
@@ -69,14 +98,14 @@ async function downloadXclogparser(tempDir, version, logger, proxyBaseUrl) {
|
|
|
69
98
|
return proxiedDownloadResult.value;
|
|
70
99
|
}
|
|
71
100
|
}
|
|
72
|
-
return await downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: directUrl });
|
|
101
|
+
return await downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: directUrl, env });
|
|
73
102
|
}
|
|
74
|
-
async function downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl, }) {
|
|
103
|
+
async function downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl, env, }) {
|
|
75
104
|
await (0, downloader_1.default)(sourceUrl, zipPath, { retry: 3, timeout: XCLOGPARSER_DOWNLOAD_TIMEOUT_MS });
|
|
76
|
-
return await unpackXclogparser({ tempDir, zipPath });
|
|
105
|
+
return await unpackXclogparser({ tempDir, zipPath, env });
|
|
77
106
|
}
|
|
78
|
-
async function unpackXclogparser({ tempDir, zipPath, }) {
|
|
79
|
-
await (0, turtle_spawn_1.default)('unzip', ['-q', zipPath, '-d', tempDir], { stdio: 'pipe' });
|
|
107
|
+
async function unpackXclogparser({ tempDir, zipPath, env, }) {
|
|
108
|
+
await (0, turtle_spawn_1.default)('unzip', ['-q', zipPath, '-d', tempDir], { stdio: 'pipe', env });
|
|
80
109
|
const binaryPath = path_1.default.join(tempDir, 'xclogparser');
|
|
81
110
|
await fs_extra_1.default.chmod(binaryPath, 0o755);
|
|
82
111
|
return binaryPath;
|
|
@@ -85,7 +114,7 @@ async function cleanupXclogparserArtifacts({ tempDir, zipPath, }) {
|
|
|
85
114
|
await (0, results_1.asyncResult)(fs_extra_1.default.rm(zipPath, { force: true }));
|
|
86
115
|
await (0, results_1.asyncResult)(fs_extra_1.default.rm(path_1.default.join(tempDir, 'xclogparser'), { force: true }));
|
|
87
116
|
}
|
|
88
|
-
async function runXclogparser({ binaryPath, derivedDataPath, workspacePath, outputDir, }) {
|
|
117
|
+
async function runXclogparser({ binaryPath, derivedDataPath, workspacePath, outputDir, env, }) {
|
|
89
118
|
const outputPath = path_1.default.join(outputDir, XCLOGPARSER_OUTPUT_FILENAME);
|
|
90
119
|
await (0, turtle_spawn_1.default)(binaryPath, [
|
|
91
120
|
'parse',
|
|
@@ -95,9 +124,12 @@ async function runXclogparser({ binaryPath, derivedDataPath, workspacePath, outp
|
|
|
95
124
|
workspacePath,
|
|
96
125
|
'--reporter',
|
|
97
126
|
'json',
|
|
127
|
+
'--omit_warnings',
|
|
128
|
+
'--omit_notes',
|
|
129
|
+
'--trunc_large_issues',
|
|
98
130
|
'--output',
|
|
99
131
|
outputPath,
|
|
100
|
-
], { stdio: 'pipe' });
|
|
132
|
+
], { stdio: 'pipe', env });
|
|
101
133
|
return outputPath;
|
|
102
134
|
}
|
|
103
135
|
const XcactivitylogStepSchemaZ = zod_1.z.object({
|
|
@@ -274,7 +306,7 @@ function formatReport(data) {
|
|
|
274
306
|
' │ ' +
|
|
275
307
|
'100.0%'.padStart(pctWidth) +
|
|
276
308
|
' │ ' +
|
|
277
|
-
'
|
|
309
|
+
' '.repeat(wallWidth) +
|
|
278
310
|
' │ ' +
|
|
279
311
|
' '.repeat(barMaxWidth) +
|
|
280
312
|
' │');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/build-tools",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.2",
|
|
4
4
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Expo <support@expo.io>",
|
|
@@ -37,17 +37,17 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@expo/config": "55.0.10",
|
|
39
39
|
"@expo/config-plugins": "55.0.7",
|
|
40
|
-
"@expo/downloader": "
|
|
41
|
-
"@expo/eas-build-job": "
|
|
40
|
+
"@expo/downloader": "19.0.0",
|
|
41
|
+
"@expo/eas-build-job": "19.0.0",
|
|
42
42
|
"@expo/env": "^0.4.0",
|
|
43
|
-
"@expo/logger": "
|
|
43
|
+
"@expo/logger": "19.0.0",
|
|
44
44
|
"@expo/package-manager": "1.9.10",
|
|
45
45
|
"@expo/plist": "^0.2.0",
|
|
46
46
|
"@expo/results": "^1.0.0",
|
|
47
47
|
"@expo/spawn-async": "1.7.2",
|
|
48
|
-
"@expo/steps": "
|
|
49
|
-
"@expo/template-file": "
|
|
50
|
-
"@expo/turtle-spawn": "
|
|
48
|
+
"@expo/steps": "19.0.0",
|
|
49
|
+
"@expo/template-file": "19.0.0",
|
|
50
|
+
"@expo/turtle-spawn": "19.0.0",
|
|
51
51
|
"@expo/xcpretty": "^4.3.1",
|
|
52
52
|
"@google-cloud/storage": "^7.11.2",
|
|
53
53
|
"@sentry/node": "7.77.0",
|
|
@@ -99,5 +99,5 @@
|
|
|
99
99
|
"typescript": "^5.5.4",
|
|
100
100
|
"uuid": "^9.0.1"
|
|
101
101
|
},
|
|
102
|
-
"gitHead": "
|
|
102
|
+
"gitHead": "b64f25b36dd01aec10a03a503ab35e9f9fb5be41"
|
|
103
103
|
}
|