@expo/build-tools 21.7.0 → 21.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/logging/HttpLogStream.js +2 -0
- package/dist/steps/functions/maestroTests.js +36 -0
- package/dist/steps/functions/startAndroidEmulator.js +14 -1
- package/dist/steps/utils/deviceRunSessionEvents.js +1 -1
- package/dist/templates/EasBuildConfigureVersionGradle.d.ts +1 -1
- package/dist/templates/EasBuildConfigureVersionGradle.js +18 -1
- package/dist/utils/AndroidEmulatorUtils.d.ts +3 -1
- package/dist/utils/AndroidEmulatorUtils.js +23 -2
- package/package.json +3 -3
|
@@ -8,6 +8,7 @@ const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
|
8
8
|
const stream_1 = require("stream");
|
|
9
9
|
const retry_1 = require("../utils/retry");
|
|
10
10
|
const MAX_BATCH_BYTES = 200_000;
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
11
12
|
/**
|
|
12
13
|
* A bunyan-compatible writable stream for sending logs over HTTP.
|
|
13
14
|
*/
|
|
@@ -120,6 +121,7 @@ class HttpLogStream extends stream_1.Writable {
|
|
|
120
121
|
'Content-Type': 'application/x-ndjson',
|
|
121
122
|
},
|
|
122
123
|
body: logs.map(log => log.serialized).join('\n'),
|
|
124
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
123
125
|
});
|
|
124
126
|
if (!response.ok) {
|
|
125
127
|
const responseText = await (0, results_1.asyncResult)(response.text());
|
|
@@ -18,6 +18,7 @@ const retry_1 = require("../../utils/retry");
|
|
|
18
18
|
const FlowPathSchema = zod_1.z.array(zod_1.z.string().min(1)).min(1);
|
|
19
19
|
const RetriesSchema = zod_1.z.number().int().min(0).default(0);
|
|
20
20
|
const ShardsSchema = zod_1.z.number().int().min(1).optional();
|
|
21
|
+
const AndroidConnectionModeSchema = zod_1.z.enum(['adb', 'dadb']).default('adb');
|
|
21
22
|
function parseInput(schema, value, message) {
|
|
22
23
|
const result = schema.safeParse(value);
|
|
23
24
|
if (!result.success) {
|
|
@@ -109,6 +110,11 @@ function createMaestroTestsBuildFunction(ctx) {
|
|
|
109
110
|
required: false,
|
|
110
111
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
111
112
|
}),
|
|
113
|
+
steps_1.BuildStepInput.createProvider({
|
|
114
|
+
id: 'android_connection_mode',
|
|
115
|
+
required: false,
|
|
116
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
117
|
+
}),
|
|
112
118
|
],
|
|
113
119
|
outputProviders: [
|
|
114
120
|
steps_1.BuildStepOutput.createProvider({ id: 'junit_report_directory', required: true }),
|
|
@@ -153,6 +159,9 @@ function createMaestroTestsBuildFunction(ctx) {
|
|
|
153
159
|
const flowPaths = parseInput(FlowPathSchema, inputs.flow_path.value, 'flow_path must be a non-empty array of non-empty strings.');
|
|
154
160
|
const retries = parseInput(RetriesSchema, inputs.retries.value, 'retries must be a non-negative integer.');
|
|
155
161
|
const shards = parseInput(ShardsSchema, inputs.shards.value, 'shards must be a positive integer.');
|
|
162
|
+
const androidConnectionMode = parseInput(AndroidConnectionModeSchema, inputs.android_connection_mode.value ||
|
|
163
|
+
env.EAS_MAESTRO_ANDROID_CONNECTION_MODE ||
|
|
164
|
+
undefined, 'android_connection_mode and EAS_MAESTRO_ANDROID_CONNECTION_MODE must be either "adb" or "dadb".');
|
|
156
165
|
const retryFailedOnly = inputs.retry_failed_only.value;
|
|
157
166
|
try {
|
|
158
167
|
await promises_1.default.mkdir(junitReportDirectory, { recursive: true });
|
|
@@ -176,6 +185,33 @@ function createMaestroTestsBuildFunction(ctx) {
|
|
|
176
185
|
let lastAttemptExitCode = null;
|
|
177
186
|
const harvested = [];
|
|
178
187
|
const totalAttempts = retries + 1;
|
|
188
|
+
if (platform === 'android' && androidConnectionMode === 'dadb') {
|
|
189
|
+
try {
|
|
190
|
+
const adbOverrideDirectoryPath = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'maestro-tests-adb-override-'));
|
|
191
|
+
await promises_1.default.writeFile(path_1.default.join(adbOverrideDirectoryPath, 'adb'), '#!/bin/sh\nexit 1\n', {
|
|
192
|
+
mode: 0o755,
|
|
193
|
+
});
|
|
194
|
+
// DADB starts an ADB server when it can find an adb binary. Stop the existing
|
|
195
|
+
// server first, then make only the Maestro process find the failing shim.
|
|
196
|
+
try {
|
|
197
|
+
await (0, turtle_spawn_1.default)('adb', ['kill-server'], { env: { ...spawnEnv }, logger, signal });
|
|
198
|
+
logger.info('Using a direct DADB connection for Android Maestro tests after stopping the ADB server.');
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
logger.warn({ err }, 'Using a direct DADB connection for Android Maestro tests, but failed to stop the ADB server.');
|
|
202
|
+
}
|
|
203
|
+
spawnEnv.PATH = spawnEnv.PATH
|
|
204
|
+
? `${adbOverrideDirectoryPath}${path_1.default.delimiter}${spawnEnv.PATH}`
|
|
205
|
+
: adbOverrideDirectoryPath;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
// Intentionally skip cleanup because the worker is disposable.
|
|
209
|
+
throw new eas_build_job_1.SystemError('Failed to enable direct DADB connection for Maestro', {
|
|
210
|
+
cause: err,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
// Do not restart ADB or remove the override. This keeps Maestro in direct DADB mode.
|
|
214
|
+
}
|
|
179
215
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
180
216
|
const outputPath = outputFormat === 'junit'
|
|
181
217
|
? path_1.default.join(junitReportDirectory, `${platform}-maestro-junit-attempt-${attempt}.xml`)
|
|
@@ -8,6 +8,9 @@ const eas_build_job_1 = require("@expo/eas-build-job");
|
|
|
8
8
|
const results_1 = require("@expo/results");
|
|
9
9
|
const steps_1 = require("@expo/steps");
|
|
10
10
|
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
14
|
const AndroidEmulatorUtils_1 = require("../../utils/AndroidEmulatorUtils");
|
|
12
15
|
const retry_1 = require("../../utils/retry");
|
|
13
16
|
const ANDROID_STARTUP_ATTEMPT_TIMEOUT_MS = [60_000, 120_000, 180_000];
|
|
@@ -43,7 +46,15 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
43
46
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
44
47
|
}),
|
|
45
48
|
],
|
|
46
|
-
|
|
49
|
+
outputProviders: [
|
|
50
|
+
steps_1.BuildStepOutput.createProvider({
|
|
51
|
+
id: 'logcat_directory',
|
|
52
|
+
required: true,
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
fn: async ({ logger }, { inputs, outputs, env }) => {
|
|
56
|
+
const logcatDirectory = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'eas-android-emulator-logcat-'));
|
|
57
|
+
outputs.logcat_directory.set(logcatDirectory);
|
|
47
58
|
if (env.EAS_NO_EMULATOR_HOST_SUPPORT_CHECK !== '1') {
|
|
48
59
|
await assertAndroidEmulatorHostSupportAsync({ env });
|
|
49
60
|
}
|
|
@@ -103,6 +114,7 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
103
114
|
const startResult = await AndroidEmulatorUtils_1.AndroidEmulatorUtils.startAsync({
|
|
104
115
|
deviceName,
|
|
105
116
|
env,
|
|
117
|
+
logcatDirectory,
|
|
106
118
|
});
|
|
107
119
|
attemptSerialId = startResult.serialId;
|
|
108
120
|
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.waitForReadyAsync({
|
|
@@ -184,6 +196,7 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
184
196
|
const startResult = await AndroidEmulatorUtils_1.AndroidEmulatorUtils.startAsync({
|
|
185
197
|
deviceName: cloneIdentifier,
|
|
186
198
|
env,
|
|
199
|
+
logcatDirectory,
|
|
187
200
|
});
|
|
188
201
|
cloneSerialId = startResult.serialId;
|
|
189
202
|
logger.info('Waiting for emulator to become ready');
|
|
@@ -220,7 +220,7 @@ function createRealtimeLogStream(ctx, logger) {
|
|
|
220
220
|
? 'http://localhost:4999/logs/'
|
|
221
221
|
: ctx.env.EXPO_STAGING
|
|
222
222
|
? 'https://staging-logs.expo.dev/logs/'
|
|
223
|
-
:
|
|
223
|
+
: 'https://logs.expo.dev/logs/';
|
|
224
224
|
const jobRunId = ctx.env.EAS_BUILD_ID;
|
|
225
225
|
const robotAccessToken = ctx.job.secrets?.robotAccessToken;
|
|
226
226
|
if (!baseUrl || !jobRunId || !robotAccessToken) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const EasBuildConfigureVersionGradleTemplate = "// Build integration with EAS\n\nimport java.nio.file.Paths\n\ndef versionCodeVal = null\ndef versionNameVal = null\n<% if (VERSION_CODE) { %>\n versionCodeVal = \"<%- VERSION_CODE %>\"\n<% } %>\n<% if (VERSION_NAME) { %>\n versionNameVal = \"<%- VERSION_NAME %>\"\n<% } %>\n\nandroid {\n defaultConfig {\n if (versionCodeVal) {\n versionCode = Integer.parseInt(versionCodeVal)\n }\n if (versionNameVal) {\n versionName = versionNameVal\n }\n }\n applicationVariants.all { variant ->\n variant.outputs.each { output ->\n if (versionCodeVal) {\n output.versionCodeOverride = Integer.parseInt(versionCodeVal)\n }\n if (versionNameVal) {\n output.versionNameOverride = versionNameVal\n }\n }\n }\n}\n";
|
|
1
|
+
export declare const EasBuildConfigureVersionGradleTemplate = "// Build integration with EAS\n\nimport java.nio.file.Paths\n\ndef versionCodeVal = null\ndef versionNameVal = null\n<% if (VERSION_CODE) { %>\n versionCodeVal = \"<%- VERSION_CODE %>\"\n<% } %>\n<% if (VERSION_NAME) { %>\n versionNameVal = \"<%- VERSION_NAME %>\"\n<% } %>\n\nandroid {\n defaultConfig {\n if (versionCodeVal) {\n versionCode = Integer.parseInt(versionCodeVal)\n }\n if (versionNameVal) {\n versionName = versionNameVal\n }\n }\n}\n\n// AGP 9's new DSL removes the applicationVariants API, we set the version on each output through the androidComponents API instead.\ndef easAndroidExtension = project.extensions.getByName('android')\nif (easAndroidExtension.hasProperty('applicationVariants')) {\n easAndroidExtension.applicationVariants.all { variant ->\n variant.outputs.each { output ->\n if (versionCodeVal) {\n output.versionCodeOverride = Integer.parseInt(versionCodeVal)\n }\n if (versionNameVal) {\n output.versionNameOverride = versionNameVal\n }\n }\n }\n} else {\n def easAndroidComponents = project.extensions.getByName('androidComponents')\n easAndroidComponents.onVariants(easAndroidComponents.selector().all()) { variant ->\n variant.outputs.each { output ->\n if (versionCodeVal) {\n output.versionCode.set(Integer.parseInt(versionCodeVal))\n }\n if (versionNameVal) {\n output.versionName.set(versionNameVal)\n }\n }\n }\n}\n";
|
|
@@ -23,7 +23,12 @@ android {
|
|
|
23
23
|
versionName = versionNameVal
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// AGP 9's new DSL removes the applicationVariants API, we set the version on each output through the androidComponents API instead.
|
|
29
|
+
def easAndroidExtension = project.extensions.getByName('android')
|
|
30
|
+
if (easAndroidExtension.hasProperty('applicationVariants')) {
|
|
31
|
+
easAndroidExtension.applicationVariants.all { variant ->
|
|
27
32
|
variant.outputs.each { output ->
|
|
28
33
|
if (versionCodeVal) {
|
|
29
34
|
output.versionCodeOverride = Integer.parseInt(versionCodeVal)
|
|
@@ -33,5 +38,17 @@ android {
|
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
}
|
|
41
|
+
} else {
|
|
42
|
+
def easAndroidComponents = project.extensions.getByName('androidComponents')
|
|
43
|
+
easAndroidComponents.onVariants(easAndroidComponents.selector().all()) { variant ->
|
|
44
|
+
variant.outputs.each { output ->
|
|
45
|
+
if (versionCodeVal) {
|
|
46
|
+
output.versionCode.set(Integer.parseInt(versionCodeVal))
|
|
47
|
+
}
|
|
48
|
+
if (versionNameVal) {
|
|
49
|
+
output.versionName.set(versionNameVal)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
36
53
|
}
|
|
37
54
|
`;
|
|
@@ -34,12 +34,14 @@ export declare namespace AndroidEmulatorUtils {
|
|
|
34
34
|
env: NodeJS.ProcessEnv;
|
|
35
35
|
logger: bunyan;
|
|
36
36
|
}): Promise<void>;
|
|
37
|
-
function startAsync({ deviceName, env, }: {
|
|
37
|
+
function startAsync({ deviceName, env, logcatDirectory, }: {
|
|
38
38
|
deviceName: AndroidVirtualDeviceName;
|
|
39
39
|
env: NodeJS.ProcessEnv;
|
|
40
|
+
logcatDirectory: string;
|
|
40
41
|
}): Promise<{
|
|
41
42
|
emulatorPromise: SpawnPromise<SpawnResult>;
|
|
42
43
|
serialId: AndroidDeviceSerialId;
|
|
44
|
+
logcatOutputPath: string;
|
|
43
45
|
}>;
|
|
44
46
|
function waitForReadyAsync({ serialId, env, timeoutMs, logger, }: {
|
|
45
47
|
serialId: AndroidDeviceSerialId;
|
|
@@ -4,10 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AndroidEmulatorUtils = void 0;
|
|
7
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
7
8
|
const results_1 = require("@expo/results");
|
|
8
9
|
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
9
10
|
const assert_1 = __importDefault(require("assert"));
|
|
10
11
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
13
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
14
|
const node_os_1 = __importDefault(require("node:os"));
|
|
13
15
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -212,13 +214,32 @@ var AndroidEmulatorUtils;
|
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
216
|
AndroidEmulatorUtils.cloneAsync = cloneAsync;
|
|
215
|
-
async function startAsync({ deviceName, env, }) {
|
|
217
|
+
async function startAsync({ deviceName, env, logcatDirectory, }) {
|
|
218
|
+
let logcatOutputPath;
|
|
219
|
+
try {
|
|
220
|
+
await node_fs_1.default.promises.mkdir(logcatDirectory, { recursive: true });
|
|
221
|
+
const safeDeviceName = deviceName.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
222
|
+
const timestamp = Math.floor(Date.now() / 1000)
|
|
223
|
+
.toString(16)
|
|
224
|
+
.padStart(8, '0');
|
|
225
|
+
logcatOutputPath = node_path_1.default.join(logcatDirectory, `${safeDeviceName}-${timestamp}-${(0, node_crypto_1.randomBytes)(2).toString('hex')}.log`);
|
|
226
|
+
await node_fs_1.default.promises.writeFile(logcatOutputPath, '');
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
throw new eas_build_job_1.SystemError(`Failed to prepare Android emulator logcat output for ${deviceName}.`, {
|
|
230
|
+
cause: err,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
216
233
|
const emulatorPromise = (0, turtle_spawn_1.default)(`${process.env.ANDROID_HOME}/emulator/emulator`, [
|
|
217
234
|
'-no-window',
|
|
218
235
|
'-no-boot-anim',
|
|
219
236
|
'-writable-system',
|
|
220
237
|
'-noaudio',
|
|
221
238
|
'-no-snapshot-save',
|
|
239
|
+
'-logcat',
|
|
240
|
+
'*:v',
|
|
241
|
+
'-logcat-output',
|
|
242
|
+
logcatOutputPath,
|
|
222
243
|
'-avd',
|
|
223
244
|
deviceName,
|
|
224
245
|
'-accel',
|
|
@@ -255,7 +276,7 @@ var AndroidEmulatorUtils;
|
|
|
255
276
|
});
|
|
256
277
|
// We don't want to await the SpawnPromise here.
|
|
257
278
|
// eslint-disable-next-line @typescript-eslint/return-await
|
|
258
|
-
return { emulatorPromise, serialId };
|
|
279
|
+
return { emulatorPromise, serialId, logcatOutputPath };
|
|
259
280
|
}
|
|
260
281
|
AndroidEmulatorUtils.startAsync = startAsync;
|
|
261
282
|
async function waitForReadyAsync({ serialId, env, timeoutMs = 3 * 60 * 1_000, logger, }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/build-tools",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.8.0",
|
|
4
4
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Expo <support@expo.io>",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@expo/plist": "^0.3.5",
|
|
47
47
|
"@expo/results": "^1.0.0",
|
|
48
48
|
"@expo/spawn-async": "1.7.2",
|
|
49
|
-
"@expo/steps": "21.
|
|
49
|
+
"@expo/steps": "21.7.1",
|
|
50
50
|
"@expo/template-file": "21.0.2",
|
|
51
51
|
"@expo/turtle-spawn": "21.0.0",
|
|
52
52
|
"@expo/xcpretty": "^4.3.1",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"typescript": "^5.5.4",
|
|
101
101
|
"uuid": "^9.0.1"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "b8cff2008966a6c7202736577cd231025ee47f21"
|
|
104
104
|
}
|