Skip to content

Commit ee7089f

Browse files
farfromrefugclaude
andcommitted
feat(android): per-plugin build options reaching the plugin gradle build
A plugin's `android.plugins.<name>` entry already reaches `buildAar`; this makes it reach the gradle build itself, and adds `abiFilters` as the first option that does: ```js android: { plugins: { "@foo/plugin-x": { abiFilters: ["arm64-v8a"] }, }, } ``` Nothing here is specific to `abiFilters`. What a plugin has native code for does not depend on what is plugged in, so the list wins over the ABIs `--filter-plugins-devices-arch` derives from the connected devices and applies whether or not that flag is set; an empty list passes nothing, opting a single plugin out of the narrowing. Every other key of the entry reaches the build as it is written. The plugin build data now records the options gradle was asked for, under a single `__buildOptions` entry rather than the `abiFilters`-specific one, since the plugin sources do not change when an option does: any per-plugin option added later takes part in the rebuild decision by being listed there. Options that only change the artifact's name, such as `aarSuffix`, produce a different file rather than a stale one and stay out of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c47fb4f commit ee7089f

5 files changed

Lines changed: 135 additions & 17 deletions

File tree

lib/definitions/project.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,15 @@ interface INsConfigAndroidPlugin {
195195
* each other - a suffix tells them apart.
196196
*/
197197
aarSuffix?: string;
198+
199+
/**
200+
* The ABIs passed to this plugin's gradle build as `-PabiFilters`, which a
201+
* plugin acts on in its own `include.gradle`. Wins over the ABIs
202+
* `--filter-plugins-devices-arch` derives from the connected devices, and
203+
* applies whether or not that flag is set. An empty array passes nothing,
204+
* which opts this plugin out of the narrowing.
205+
*/
206+
abiFilters?: string[];
198207
}
199208

200209
interface INsConfigHooks {

lib/services/android-plugin-build-service.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
6161
private $watchIgnoreListService: IWatchIgnoreListService,
6262
) {}
6363

64-
private static ABI_FILTERS_BUILD_DATA_KEY = "__abiFilters";
64+
/**
65+
* The plugin build data entry recording the build options gradle was last
66+
* asked for. The plugin sources do not change when an option does, so every
67+
* per-plugin option that changes what gradle produces belongs in here -
68+
* otherwise the aar built with the old one is kept.
69+
*/
70+
private static BUILD_OPTIONS_DATA_KEY = "__buildOptions";
6571

6672
private static MANIFEST_ROOT = {
6773
$: {
@@ -239,14 +245,11 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
239245
shortPluginName,
240246
);
241247

242-
// the aar of a plugin built for a subset of the ABIs is not the aar of the
243-
// same sources built for another subset, so the ABIs take part in the
244-
// decision to rebuild - the sources alone would not change when a device
245-
// with another ABI joins the run.
246-
if (options.abiFilters && options.abiFilters.length) {
248+
const buildOptions = this.getArtifactAffectingOptions(options);
249+
if (buildOptions) {
247250
pluginSourceFileHashesInfo[
248-
AndroidPluginBuildService.ABI_FILTERS_BUILD_DATA_KEY
249-
] = options.abiFilters.join(",");
251+
AndroidPluginBuildService.BUILD_OPTIONS_DATA_KEY
252+
] = buildOptions;
250253
}
251254

252255
const shouldBuildAar = await this.shouldBuildAar({
@@ -296,6 +299,28 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
296299
return shouldBuildAar;
297300
}
298301

302+
/**
303+
* The build options that change what gradle produces for this plugin, in
304+
* the form they are recorded in the plugin build data. `null` when none of
305+
* them is set, so a project that uses none of them keeps the build data it
306+
* already has.
307+
*
308+
* `abiFilters` is the only one today: the aar of a plugin built for a
309+
* subset of the ABIs is not the aar of the same sources built for another
310+
* subset. Options that only change the *name* of the artifact - `aarSuffix`
311+
* - do not belong here, they produce a different file rather than a stale
312+
* one.
313+
*/
314+
private getArtifactAffectingOptions(options: IPluginBuildOptions): string {
315+
const affectingOptions: { [key: string]: any } = {};
316+
317+
if (options.abiFilters && options.abiFilters.length) {
318+
affectingOptions.abiFilters = options.abiFilters;
319+
}
320+
321+
return _.isEmpty(affectingOptions) ? null : JSON.stringify(affectingOptions);
322+
}
323+
299324
private cleanPluginDir(pluginTempDir: string): void {
300325
// In case plugin was already built in the current process, we need to clean the old sources as they may break the new build.
301326
this.$fs.deleteDirectory(pluginTempDir);

lib/services/android-project-service.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Configurations, LiveSyncPaths } from "../common/constants";
99
import { hook } from "../common/helpers";
1010
import { performanceLog } from ".././common/decorators";
1111
import {
12+
INsConfigAndroidPlugin,
1213
IProjectData,
1314
IProjectDataService,
1415
IValidatePlatformOutput,
@@ -698,12 +699,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
698699
const options: IPluginBuildOptions = {
699700
gradlePath: this.$options.gradlePath,
700701
gradleArgs: this.$options.gradleArgs,
701-
abiFilters: this.getPluginsAbiFilters(),
702+
abiFilters: this.getPluginsAbiFilters(pluginConfig),
702703
projectDir: projectData.projectDir,
703704
pluginName: pluginData.name,
704705
platformsAndroidDirPath: pluginPlatformsFolderPath,
705706
aarOutputDir: pluginPlatformsFolderPath,
706707
tempPluginDirPath: path.join(projectData.platformsDir, "tempPlugin"),
708+
// the rest of the plugin's config entry reaches the build as it is
709+
// written - `abiFilters` is resolved above only because it falls
710+
// back to the devices when the entry does not set it
707711
...pluginConfig,
708712
};
709713

@@ -716,13 +720,21 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
716720
}
717721

718722
/**
719-
* The ABIs passed to the gradle build of a plugin built from source. Opt-in
720-
* (`--filter-plugins-devices-arch`): nothing in the gradle files the CLI
721-
* generates for a plugin acts on `abiFilters`, so this is only useful for a
722-
* plugin whose own `include.gradle` reads the property - a long native build
723-
* can then skip the ABIs this run is not going to deploy to.
723+
* The ABIs passed to the gradle build of a plugin built from source. Nothing
724+
* in the gradle files the CLI generates for a plugin acts on `abiFilters`,
725+
* so this is only useful for a plugin whose own `include.gradle` reads the
726+
* property - a long native build can then skip the ABIs it is not asked for.
727+
*
728+
* A list in the plugin's config entry always wins: what a plugin has native
729+
* code for does not depend on what is plugged in. Otherwise the ABIs of the
730+
* devices this run is about to deploy to are used, and only when
731+
* `--filter-plugins-devices-arch` asks for it.
724732
*/
725-
private getPluginsAbiFilters(): string[] {
733+
private getPluginsAbiFilters(pluginConfig: INsConfigAndroidPlugin): string[] {
734+
if (pluginConfig.abiFilters) {
735+
return pluginConfig.abiFilters;
736+
}
737+
726738
if (!this.$options.filterPluginsDevicesArch) {
727739
return null;
728740
}

test/services/android-plugin-build-service.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,32 @@ dependencies {
350350
assert.isFalse(spawnFromEventCalled);
351351
});
352352

353+
it("records the options that changed what gradle produced", async () => {
354+
const config: IPluginBuildOptions = setup({ addManifest: true });
355+
config.abiFilters = ["arm64-v8a"];
356+
357+
await androidBuildPluginService.buildAar(config);
358+
359+
const buildData = fs.readJson(
360+
path.join(tempFolder, shortPluginName, PLUGIN_BUILD_DATA_FILENAME),
361+
);
362+
assert.deepStrictEqual(
363+
buildData["__buildOptions"],
364+
JSON.stringify({ abiFilters: ["arm64-v8a"] }),
365+
);
366+
});
367+
368+
it("records no build options when none of them is set", async () => {
369+
const config: IPluginBuildOptions = setup({ addManifest: true });
370+
371+
await androidBuildPluginService.buildAar(config);
372+
373+
const buildData = fs.readJson(
374+
path.join(tempFolder, shortPluginName, PLUGIN_BUILD_DATA_FILENAME),
375+
);
376+
assert.isUndefined(buildData["__buildOptions"]);
377+
});
378+
353379
it("builds aar with the latest runtime gradle versions when no project dir is specified", async () => {
354380
const expectedGradleVersion = "4.4";
355381
const expectedAndroidVersion = "4.5.6";

test/services/android-project-service.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
IProjectDir,
2121
} from "../../lib/common/declarations";
2222
import { IPluginBuildOptions } from "../../lib/definitions/android-plugin-migrator";
23+
import { INsConfigAndroidPlugin } from "../../lib/definitions/project";
2324

2425
const createTestInjector = (): IInjector => {
2526
const testInjector = new Yok();
@@ -424,7 +425,8 @@ describe("androidProjectService plugins abi filtering", () => {
424425

425426
const preparePluginNativeCode = async (
426427
options: any,
427-
devices: any[]
428+
devices: any[],
429+
pluginsConfig?: IDictionary<INsConfigAndroidPlugin>
428430
): Promise<IPluginBuildOptions> => {
429431
const testInjector = createTestInjector();
430432
let pluginBuildOptions: IPluginBuildOptions = null;
@@ -452,7 +454,11 @@ describe("androidProjectService plugins abi filtering", () => {
452454
name: "my-plugin",
453455
pluginPlatformsFolderPath: (): string => "pluginPlatformsDir",
454456
},
455-
<any>{ projectDir: "projectDir", platformsDir: "platformsDir" }
457+
<any>{
458+
projectDir: "projectDir",
459+
platformsDir: "platformsDir",
460+
nsConfig: { android: { plugins: pluginsConfig } },
461+
}
456462
);
457463

458464
return pluginBuildOptions;
@@ -490,6 +496,46 @@ describe("androidProjectService plugins abi filtering", () => {
490496
assert.isNull(options.abiFilters);
491497
});
492498

499+
it("passes the abis from the plugin's config entry", async () => {
500+
const options = await preparePluginNativeCode(
501+
{ filterPluginsDevicesArch: true },
502+
[createDevice("device1", ["x86_64"], true)],
503+
{ "my-plugin": { abiFilters: ["arm64-v8a"] } }
504+
);
505+
506+
assert.deepStrictEqual(options.abiFilters, ["arm64-v8a"]);
507+
});
508+
509+
it("passes the abis from the config entry without the option", async () => {
510+
const options = await preparePluginNativeCode(
511+
{},
512+
[createDevice("device1", ["x86_64"], true)],
513+
{ "my-plugin": { abiFilters: ["arm64-v8a"] } }
514+
);
515+
516+
assert.deepStrictEqual(options.abiFilters, ["arm64-v8a"]);
517+
});
518+
519+
it("passes no abis when the config entry is an empty list", async () => {
520+
const options = await preparePluginNativeCode(
521+
{ filterPluginsDevicesArch: true },
522+
[createDevice("device1", ["x86_64"], true)],
523+
{ "my-plugin": { abiFilters: [] } }
524+
);
525+
526+
assert.deepStrictEqual(options.abiFilters, []);
527+
});
528+
529+
it("ignores the config entry of another plugin", async () => {
530+
const options = await preparePluginNativeCode(
531+
{ filterPluginsDevicesArch: true },
532+
[createDevice("device1", ["x86_64"], true)],
533+
{ "other-plugin": { abiFilters: ["arm64-v8a"] } }
534+
);
535+
536+
assert.deepStrictEqual(options.abiFilters, ["x86_64"]);
537+
});
538+
493539
it("passes no abis when no device reports its abis", async () => {
494540
const options = await preparePluginNativeCode(
495541
{ filterPluginsDevicesArch: true },

0 commit comments

Comments
 (0)