Skip to content

Commit 3eda0bc

Browse files
authored
fix: install Android tools only when a flow needs them (#98)
1 parent 3a84310 commit 3eda0bc

8 files changed

Lines changed: 402 additions & 183 deletions

File tree

packages/platform-android/src/__tests__/environment.test.ts

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,149 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import fs from 'node:fs';
3+
import os from 'node:os';
4+
import path from 'node:path';
25
import {
6+
ensureAndroidAdbAvailable,
7+
ensureAndroidEmulatorAvailable,
38
getAndroidSdkRoot,
49
getAndroidSystemImagePackage,
510
getDefaultUnixAndroidSdkRoot,
611
getHostAndroidSystemImageArch,
712
getRequiredAndroidSdkPackages,
813
} from '../environment.js';
14+
import * as tools from '@react-native-harness/tools';
915

1016
describe('Android environment', () => {
1117
beforeEach(() => {
1218
vi.restoreAllMocks();
1319
vi.unstubAllEnvs();
1420
});
1521

22+
it('skips bootstrapping command-line tools when adb is already installed', async () => {
23+
const sdkRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'android-sdk-'));
24+
const adbPath = path.join(sdkRoot, 'platform-tools', 'adb');
25+
26+
fs.mkdirSync(path.dirname(adbPath), { recursive: true });
27+
fs.writeFileSync(adbPath, '');
28+
29+
const spawnSpy = vi.spyOn(tools, 'spawn');
30+
31+
await expect(
32+
ensureAndroidAdbAvailable({
33+
env: { ANDROID_HOME: sdkRoot },
34+
}),
35+
).resolves.toBe(sdkRoot);
36+
37+
expect(spawnSpy).not.toHaveBeenCalled();
38+
39+
fs.rmSync(sdkRoot, { force: true, recursive: true });
40+
});
41+
42+
it('installs only platform-tools when adb is missing', async () => {
43+
const sdkRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'android-sdk-'));
44+
const sdkManagerDirectory = path.join(
45+
sdkRoot,
46+
'cmdline-tools',
47+
'latest',
48+
'bin',
49+
);
50+
51+
fs.mkdirSync(sdkManagerDirectory, { recursive: true });
52+
fs.writeFileSync(path.join(sdkManagerDirectory, 'sdkmanager'), '');
53+
fs.writeFileSync(path.join(sdkManagerDirectory, 'avdmanager'), '');
54+
55+
const spawnSpy = vi.spyOn(tools, 'spawn').mockImplementation((async (
56+
command: string,
57+
args?: readonly string[],
58+
) => {
59+
if (command === 'bash' && typeof args?.[1] === 'string') {
60+
const commandString = args[1];
61+
62+
if (commandString.includes('platform-tools')) {
63+
const adbPath = path.join(sdkRoot, 'platform-tools', 'adb');
64+
fs.mkdirSync(path.dirname(adbPath), { recursive: true });
65+
fs.writeFileSync(adbPath, '');
66+
}
67+
}
68+
69+
return {} as Awaited<ReturnType<typeof tools.spawn>>;
70+
}) as typeof tools.spawn);
71+
72+
await expect(
73+
ensureAndroidAdbAvailable({
74+
env: { ANDROID_HOME: sdkRoot },
75+
}),
76+
).resolves.toBe(sdkRoot);
77+
78+
expect(spawnSpy).toHaveBeenCalledWith(
79+
'bash',
80+
['-lc', expect.stringContaining('platform-tools')],
81+
expect.any(Object),
82+
);
83+
expect(
84+
spawnSpy.mock.calls.some(
85+
([command, args]) =>
86+
command === 'bash' &&
87+
typeof args?.[1] === 'string' &&
88+
args[1].includes('platform-tools') &&
89+
args[1].includes('emulator'),
90+
),
91+
).toBe(false);
92+
93+
fs.rmSync(sdkRoot, { force: true, recursive: true });
94+
});
95+
96+
it('installs emulator only when emulator is missing', async () => {
97+
const sdkRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'android-sdk-'));
98+
const sdkManagerDirectory = path.join(
99+
sdkRoot,
100+
'cmdline-tools',
101+
'latest',
102+
'bin',
103+
);
104+
105+
fs.mkdirSync(sdkManagerDirectory, { recursive: true });
106+
fs.writeFileSync(path.join(sdkManagerDirectory, 'sdkmanager'), '');
107+
fs.writeFileSync(path.join(sdkManagerDirectory, 'avdmanager'), '');
108+
109+
const spawnSpy = vi.spyOn(tools, 'spawn').mockImplementation((async (
110+
command: string,
111+
args?: readonly string[],
112+
) => {
113+
if (command === 'bash' && typeof args?.[1] === 'string') {
114+
const commandString = args[1];
115+
116+
if (commandString.includes('emulator')) {
117+
const emulatorPath = path.join(sdkRoot, 'emulator', 'emulator');
118+
fs.mkdirSync(path.dirname(emulatorPath), { recursive: true });
119+
fs.writeFileSync(emulatorPath, '');
120+
}
121+
}
122+
123+
return {} as Awaited<ReturnType<typeof tools.spawn>>;
124+
}) as typeof tools.spawn);
125+
126+
await expect(
127+
ensureAndroidEmulatorAvailable({
128+
env: { ANDROID_HOME: sdkRoot },
129+
}),
130+
).resolves.toBe(sdkRoot);
131+
132+
expect(spawnSpy).toHaveBeenCalledWith(
133+
'bash',
134+
['-lc', expect.stringContaining('emulator')],
135+
expect.any(Object),
136+
);
137+
138+
fs.rmSync(sdkRoot, { force: true, recursive: true });
139+
});
140+
16141
it('uses the default Unix SDK root when env vars are missing', () => {
17142
expect(
18143
getDefaultUnixAndroidSdkRoot({
19144
platform: 'darwin',
20145
homeDirectory: '/Users/tester',
21-
})
146+
}),
22147
).toBe('/Users/tester/Library/Android/sdk');
23148

24149
expect(
@@ -27,8 +152,8 @@ describe('Android environment', () => {
27152
{
28153
platform: 'linux',
29154
homeDirectory: '/home/tester',
30-
}
31-
)
155+
},
156+
),
32157
).toBe('/home/tester/Android/Sdk');
33158
});
34159

@@ -42,8 +167,8 @@ describe('Android environment', () => {
42167
{
43168
platform: 'darwin',
44169
homeDirectory: '/Users/tester',
45-
}
46-
)
170+
},
171+
),
47172
).toBe('/env/android-home');
48173

49174
expect(
@@ -54,19 +179,19 @@ describe('Android environment', () => {
54179
{
55180
platform: 'linux',
56181
homeDirectory: '/home/tester',
57-
}
58-
)
182+
},
183+
),
59184
).toBe('/env/android-sdk-root');
60185
});
61186

62187
it('selects Android packages using the host architecture', () => {
63188
expect(getHostAndroidSystemImageArch('x64')).toBe('x86_64');
64189
expect(getHostAndroidSystemImageArch('arm64')).toBe('arm64-v8a');
65190
expect(getAndroidSystemImagePackage(35, 'x86_64')).toBe(
66-
'system-images;android-35;default;x86_64'
191+
'system-images;android-35;default;x86_64',
67192
);
68193
expect(getAndroidSystemImagePackage(35, 'arm64-v8a')).toBe(
69-
'system-images;android-35;default;arm64-v8a'
194+
'system-images;android-35;default;arm64-v8a',
70195
);
71196
});
72197

@@ -76,7 +201,7 @@ describe('Android environment', () => {
76201
apiLevel: 34,
77202
includeEmulator: true,
78203
architecture: 'x86_64',
79-
})
204+
}),
80205
).toEqual([
81206
'platform-tools',
82207
'emulator',

0 commit comments

Comments
 (0)