Skip to content

Commit bc7375b

Browse files
farfromrefugclaude
andcommitted
feat(android): per-plugin build options, with aarSuffix to break name clashes
The name of the `.aar` built for a plugin comes from `getShortPluginName`, which drops the npm scope. `@foo/plugin-x` and `@bar/plugin-x` therefore both build a `plugin_x.aar` into their own platforms folder, and the one that gradle picks up depends on which was built last. A project can now give one of them a suffix: ```js export default { android: { plugins: { "@bar/plugin-x": { aarSuffix: "-bar" }, }, }, } satisfies NativeScriptConfig; ``` `android.plugins` is a map keyed by npm package name, spread into the options `buildAar` receives, so it is the place to put future per-plugin build settings too. The suffix is appended to the plugin name before it is shortened, and the resulting name is used consistently - for the temp build directory, the produced `.aar` and the namespace fallback, which `setupGradle` used to recompute without it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 2f9f2e0 commit bc7375b

5 files changed

Lines changed: 66 additions & 7 deletions

File tree

lib/definitions/android-plugin-migrator.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ interface IAndroidBuildOptions {
1212
tempPluginDirPath: string;
1313
gradlePath?: string;
1414
gradleArgs?: string;
15+
/**
16+
* Appended to the plugin name before it is shortened into the name of the
17+
* produced `.aar`. The npm scope is dropped when shortening, so two plugins
18+
* from different scopes can end up with the same `.aar` - a suffix tells
19+
* them apart.
20+
*/
21+
aarSuffix?: string;
1522
}
1623

1724
interface IAndroidPluginBuildService {

lib/definitions/project.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { ICheckEnvironmentRequirementsOutput, IPlatformData } from "./platform";
99
import { IPluginData, IBasePluginData } from "./plugins";
1010
import {
11+
IDictionary,
1112
IStringDictionary,
1213
IProjectDir,
1314
IDeviceIdentifier,
@@ -179,6 +180,21 @@ interface INsConfigAndroid extends INsConfigPlaform {
179180
* Custom runtime package name
180181
*/
181182
runtimePackageName?: string;
183+
184+
/**
185+
* Per plugin build options, keyed by the plugin's npm package name.
186+
*/
187+
plugins?: IDictionary<INsConfigAndroidPlugin>;
188+
}
189+
190+
interface INsConfigAndroidPlugin {
191+
/**
192+
* Appended to the plugin name before it is shortened into the name of the
193+
* produced `.aar`. The npm scope is dropped when shortening, so
194+
* `@foo/plugin` and `@bar/plugin` both build a `plugin.aar` and overwrite
195+
* each other - a suffix tells them apart.
196+
*/
197+
aarSuffix?: string;
182198
}
183199

184200
interface INsConfigHooks {

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,11 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
226226
const androidSourceDirectories = this.getAndroidSourceDirectories(
227227
options.platformsAndroidDirPath,
228228
);
229-
const shortPluginName = getShortPluginName(options.pluginName);
229+
// the npm scope is dropped when shortening, so an optional suffix is what
230+
// keeps two same-named plugins from overwriting each other's `.aar`
231+
const shortPluginName = getShortPluginName(
232+
`${options.pluginName}${options.aarSuffix || ""}`,
233+
);
230234
const pluginTempDir = path.join(options.tempPluginDirPath, shortPluginName);
231235
const pluginSourceFileHashesInfo = await this.getSourceFilesHashes(
232236
options.platformsAndroidDirPath,
@@ -260,6 +264,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
260264
options.platformsAndroidDirPath,
261265
options.projectDir,
262266
options.pluginName,
267+
shortPluginName,
263268
);
264269
await this.buildPlugin({
265270
gradlePath: options.gradlePath,
@@ -401,6 +406,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
401406
platformsAndroidDirPath: string,
402407
projectDir: string,
403408
pluginName: string,
409+
shortPluginName: string,
404410
): Promise<void> {
405411
const gradleTemplatePath = path.resolve(
406412
path.join(__dirname, "../../vendor/gradle-plugin"),
@@ -425,8 +431,6 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
425431
this.replaceFileContent(settingsGradlePath, "{{pluginName}}", pluginName);
426432

427433
// gets the package from the AndroidManifest to use as the namespace or fallback to the `org.nativescript.${shortPluginName}`
428-
const shortPluginName = getShortPluginName(pluginName);
429-
430434
const manifestPath = path.join(
431435
pluginTempDir,
432436
"src",

lib/services/android-project-service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
688688
AndroidProjectService.ANDROID_PLATFORM_NAME
689689
);
690690
if (this.$fs.exists(pluginPlatformsFolderPath)) {
691+
const pluginConfig =
692+
(projectData.nsConfig?.android?.plugins || {})[pluginData.name] || {};
691693
const options: IPluginBuildOptions = {
692694
gradlePath: this.$options.gradlePath,
693695
gradleArgs: this.$options.gradleArgs,
@@ -696,6 +698,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
696698
platformsAndroidDirPath: pluginPlatformsFolderPath,
697699
aarOutputDir: pluginPlatformsFolderPath,
698700
tempPluginDirPath: path.join(projectData.platformsDir, "tempPlugin"),
701+
...pluginConfig,
699702
};
700703

701704
if (await this.$androidPluginBuildService.buildAar(options)) {

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ describe("androidPluginBuildService", () => {
2424
const pluginName = "my-plugin";
2525
const shortPluginName = getShortPluginName(pluginName);
2626
let spawnFromEventCalled = false;
27+
let builtPluginDirName: string = null;
2728
let fs: IFileSystem;
2829
let androidBuildPluginService: AndroidPluginBuildService;
2930
let tempFolder: string;
@@ -46,6 +47,7 @@ describe("androidPluginBuildService", () => {
4647
}): IPluginBuildOptions {
4748
options = options || {};
4849
spawnFromEventCalled = false;
50+
builtPluginDirName = null;
4951
tempFolder = mkdtempSync(
5052
path.join(tmpdir(), "androidPluginBuildService-temp-"),
5153
);
@@ -75,11 +77,16 @@ describe("androidPluginBuildService", () => {
7577
const testInjector: IInjector = new stubs.InjectorStub();
7678
testInjector.register("fs", FsLib.FileSystem);
7779
testInjector.register("childProcess", {
78-
spawnFromEvent: async (command: string): Promise<ISpawnResult> => {
79-
const finalAarName = `${shortPluginName}-release.aar`;
80+
spawnFromEvent: async (
81+
command: string,
82+
args: string[],
83+
): Promise<ISpawnResult> => {
84+
// the plugin dir gradle was pointed at is what names the built aar
85+
const pluginDir = args[args.indexOf("-p") + 1];
86+
builtPluginDirName = path.basename(pluginDir);
87+
const finalAarName = `${builtPluginDirName}-release.aar`;
8088
const aar = path.join(
81-
tempFolder,
82-
shortPluginName,
89+
pluginDir,
8390
"build",
8491
"outputs",
8592
"aar",
@@ -269,6 +276,28 @@ dependencies {
269276
assert.isTrue(spawnFromEventCalled);
270277
});
271278

279+
it("builds an aar named after the plugin", async () => {
280+
const config: IPluginBuildOptions = setup({ addManifest: true });
281+
282+
await androidBuildPluginService.buildAar(config);
283+
284+
assert.deepStrictEqual(builtPluginDirName, shortPluginName);
285+
assert.isTrue(
286+
fs.exists(path.join(pluginFolder, `${shortPluginName}.aar`)),
287+
);
288+
});
289+
290+
it("appends aarSuffix to the name of the built aar", async () => {
291+
const config: IPluginBuildOptions = setup({ addManifest: true });
292+
config.aarSuffix = "-v2";
293+
294+
await androidBuildPluginService.buildAar(config);
295+
296+
const expectedName = getShortPluginName(`${pluginName}-v2`);
297+
assert.deepStrictEqual(builtPluginDirName, expectedName);
298+
assert.isTrue(fs.exists(path.join(pluginFolder, `${expectedName}.aar`)));
299+
});
300+
272301
it("does not build aar when there are no supported files in the plugin", async () => {
273302
const config: IPluginBuildOptions = setup();
274303

0 commit comments

Comments
 (0)