Skip to content

Commit 85c958d

Browse files
farfromrefugclaude
andcommitted
feat(android): build only the ABIs of the devices being deployed to
A `ns run android` with a single arm64 device still builds every ABI. This narrows the native build down to the ABIs of the devices it is about to deploy to, and picks the matching package when installing. - `GradleBuildService` passes `-PabiFilters=<abis>` built from the devices the build targets (honouring `--device`/`--emulator`). The app's gradle configuration decides what to do with it - typically an `ndk.abiFilters` or `splits` block in `App_Resources/Android/app.gradle`. An explicit `-PabiFilters` in `--gradleArgs` always wins. - `--no-filter-devices-arch` turns the narrowing off. `ns build` never narrows, since its artifact is meant to be shipped, and neither does an app bundle build, which carries every ABI anyway. - `Mobile.IDeviceInfo` gained `abis`, read on android from `ro.product.cpu.abilist64`/`abilist32`, falling back to `ro.product.cpu.abi` on old devices. - `AndroidProjectService.checkForChanges` marks the native project as changed when a connected device has no package of its own in the build output - a device that joins later would otherwise never get one, as the sources did not change. - `DeviceInstallAppService` installs the package matching the device's ABIs, falling back to the universal one and then to the newest package. - `copyLatestAppPackage` became `copyAppPackages`: a directory `--copy-to` target receives every package the build produced, a single file target receives the universal one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 2f9f2e0 commit 85c958d

17 files changed

Lines changed: 368 additions & 18 deletions

File tree

docs/man_pages/project/testing/debug-android.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Attach the debug tools to a running app in the native emulator | `$ ns debug and
3838
* `--env.sourceMap` - creates inline source maps.
3939
* `--env.hiddenSourceMap` - creates sources maps in the root folder (useful for Crashlytics usage with bundled app in release).
4040
* `--aab` - Specifies that the command will produce and deploy an Android App Bundle.
41+
* `--no-filter-devices-arch` - If set, builds every ABI instead of only the ones the connected devices report. The narrowing only applies when the app's gradle configuration acts on the `abiFilters` property, and `ns build` never narrows.
4142
* `--force` - If set, skips the application compatibility checks and forces `npm i` to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring `ns migrate`.
4243

4344
<% if(isHtml) { %>

docs/man_pages/project/testing/run-android.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Start a default emulator if none are running, or run application on all connecte
4343
* `--env.sourceMap` - creates inline source maps.
4444
* `--env.hiddenSourceMap` - creates sources maps in the root folder (useful for Crashlytics usage with bundled app in release).
4545
* `--aab` - Specifies that the command will produce and deploy an Android App Bundle.
46+
* `--no-filter-devices-arch` - If set, builds every ABI instead of only the ones the connected devices report. The narrowing only applies when the app's gradle configuration acts on the `abiFilters` property, and `ns build` never narrows.
4647
* `--force` - If set, skips the application compatibility checks and forces `npm i` to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring `ns migrate`.
4748

4849
<% if(isHtml) { %>

lib/commands/build.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase {
5353
const buildData = this.$buildDataService.getBuildData(
5454
this.$projectData.projectDir,
5555
platform,
56-
this.$options,
56+
{
57+
...this.$options.argv,
58+
// `ns build` produces an artifact meant to be shipped, so it must
59+
// not be narrowed down to the ABIs of whatever is plugged in
60+
filterDevicesArch: false,
61+
},
5762
);
5863
const outputPath = await this.$buildController.prepareAndBuild(buildData);
5964

lib/common/definitions/mobile.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ declare global {
100100
* For iOS simulators - same as the identifier.
101101
*/
102102
imageIdentifier?: string;
103+
/**
104+
* Optional property listing the ABIs the device supports, most
105+
* preferred first. Available for Android only.
106+
*/
107+
abis?: string[];
103108
}
104109

105110
interface IDeviceError extends Error, IDeviceIdentifier {}

lib/common/mobile/android/android-device.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ interface IAndroidDeviceDetails {
1313
name: string;
1414
release: string;
1515
brand: string;
16+
"cpu.abi"?: string;
17+
"cpu.abilist32"?: string;
18+
"cpu.abilist64"?: string;
1619
}
1720

1821
interface IAdbDeviceStatusInfo {
@@ -96,6 +99,7 @@ export class AndroidDevice implements Mobile.IAndroidDevice {
9699
identifier: this.identifier,
97100
displayName: details.name,
98101
model: details.model,
102+
abis: this.getAbis(details),
99103
version,
100104
vendor: details.brand,
101105
platform: this.$devicePlatformsConstants.Android,
@@ -179,6 +183,25 @@ export class AndroidDevice implements Mobile.IAndroidDevice {
179183
return parsedDetails;
180184
}
181185

186+
// `ro.product.cpu.abilist64`/`abilist32` list every ABI the device supports,
187+
// most preferred first. Old devices report neither and only have the single
188+
// `ro.product.cpu.abi`.
189+
private getAbis(details: IAndroidDeviceDetails): string[] {
190+
const abis = [
191+
...(details["cpu.abilist64"] || "").split(","),
192+
...(details["cpu.abilist32"] || "").split(",")
193+
]
194+
.map((abi) => abi.trim())
195+
.filter((abi) => !!abi);
196+
197+
if (abis.length) {
198+
return abis;
199+
}
200+
201+
const abi = (details["cpu.abi"] || "").trim();
202+
return abi ? [abi] : [];
203+
}
204+
182205
private getIsTablet(details: any): boolean {
183206
//version 3.x.x (also known as Honeycomb) is a tablet only version
184207
return (

lib/controllers/build-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class BuildController extends EventEmitter implements IBuildController {
116116
);
117117

118118
if (buildData.copyTo) {
119-
this.$buildArtifactsService.copyLatestAppPackage(
119+
this.$buildArtifactsService.copyAppPackages(
120120
buildData.copyTo,
121121
platformData,
122122
buildData

lib/data/build-data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class AndroidBuildData extends BuildData {
5151
public keyStoreAliasPassword: string;
5252
public keyStorePassword: string;
5353
public androidBundle: boolean;
54+
public buildFilterDevicesArch: boolean;
5455
public gradlePath: string;
5556
public gradleArgs: string;
5657
public hostProjectPath: string;
@@ -63,6 +64,9 @@ export class AndroidBuildData extends BuildData {
6364
this.keyStoreAliasPassword = data.keyStoreAliasPassword;
6465
this.keyStorePassword = data.keyStorePassword;
6566
this.androidBundle = data.androidBundle || data.aab;
67+
// an app bundle already carries every ABI, so there is nothing to filter
68+
this.buildFilterDevicesArch =
69+
!this.androidBundle && data.filterDevicesArch !== false;
6670
this.gradlePath = data.gradlePath;
6771
this.gradleArgs = data.gradleArgs;
6872
this.hostProjectPath = data.hostProjectPath;

lib/declarations.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,12 @@ interface IEmbedOptions {
571571
}
572572

573573
interface IAndroidOptions extends IEmbedOptions {
574+
/**
575+
* When true (the default) `ns run`/`ns debug` restrict the native build to
576+
* the ABIs of the devices it is about to deploy to. Pass
577+
* `--no-filter-devices-arch` to always build every ABI.
578+
*/
579+
filterDevicesArch: boolean;
574580
gradlePath: string;
575581
gradleArgs: string;
576582
}

lib/definitions/build.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface IAndroidBuildData
3131
extends IBuildData,
3232
IAndroidSigningData,
3333
IHasAndroidBundle {
34+
buildFilterDevicesArch?: boolean;
3435
gradlePath?: string;
3536
gradleArgs?: string;
3637
}
@@ -62,7 +63,7 @@ interface IBuildArtifactsService {
6263
platformData: IPlatformData,
6364
buildOutputOptions: IBuildOutputOptions
6465
): Promise<string>;
65-
copyLatestAppPackage(
66+
copyAppPackages(
6667
targetPath: string,
6768
platformData: IPlatformData,
6869
buildOutputOptions: IBuildOutputOptions

lib/options.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ export class Options {
219219
default: false,
220220
hasSensitiveValue: false,
221221
},
222+
filterDevicesArch: {
223+
type: OptionType.Boolean,
224+
default: true,
225+
hasSensitiveValue: false,
226+
},
222227
gradlePath: { type: OptionType.String, hasSensitiveValue: false },
223228
gradleArgs: { type: OptionType.String, hasSensitiveValue: false },
224229
hostProjectPath: { type: OptionType.String, hasSensitiveValue: false },

0 commit comments

Comments
 (0)