Skip to content

Commit c6a6a97

Browse files
farfromrefugclaude
andcommitted
feat(android): --gradleFlavor to build a product flavor
An app that declares product flavors in its gradle configuration could not tell the CLI which one to build - it always ran `assembleDebug` / `assembleRelease`, which fails outright once flavors exist. `--gradleFlavor foo` inserts the flavor into the task name, so the CLI runs `assembleFooDebug`, `assembleFooRelease`, `bundleFooDebug` or `bundleFooRelease`. The flavor is capitalized to match the gradle task naming. The build output does not need any special handling: gradle writes a flavored build to `build/outputs/apk/<flavor>/<buildType>`, which the existing recursive package lookup and its `-.*-(Debug|Release)` regex already cover. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 2f9f2e0 commit c6a6a97

9 files changed

Lines changed: 71 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ General | `$ ns build android [--compileSdk <API Level>] [--key-store-path <File
3434
* `--env.sourceMap` - creates inline source maps.
3535
* `--env.hiddenSourceMap` - creates sources maps in the root folder (useful for Crashlytics usage with bundled app in release).
3636
* `--aab` - Specifies that the build will produce an Android App Bundle(`.aab`) file.
37+
* `--gradleFlavor` - Builds the given product flavor, when the app declares any. `--gradleFlavor foo` runs the `assembleFooDebug`/`assembleFooRelease` gradle task instead of `assembleDebug`/`assembleRelease`.
3738
* `--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`.
3839
* `--path <Directory>` - Specifies the directory that contains the project. If not set, the project is searched for in the current directory and all directories above it.
3940

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+
* `--gradleFlavor` - Builds the given product flavor, when the app declares any. `--gradleFlavor foo` runs the `assembleFooDebug`/`assembleFooRelease` gradle task instead of `assembleDebug`/`assembleRelease`.
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+
* `--gradleFlavor` - Builds the given product flavor, when the app declares any. `--gradleFlavor foo` runs the `assembleFooDebug`/`assembleFooRelease` gradle task instead of `assembleDebug`/`assembleRelease`.
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/data/build-data.ts

Lines changed: 2 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 gradleFlavor: string;
5455
public gradlePath: string;
5556
public gradleArgs: string;
5657
public hostProjectPath: string;
@@ -63,6 +64,7 @@ export class AndroidBuildData extends BuildData {
6364
this.keyStoreAliasPassword = data.keyStoreAliasPassword;
6465
this.keyStorePassword = data.keyStorePassword;
6566
this.androidBundle = data.androidBundle || data.aab;
67+
this.gradleFlavor = data.gradleFlavor;
6668
this.gradlePath = data.gradlePath;
6769
this.gradleArgs = data.gradleArgs;
6870
this.hostProjectPath = data.hostProjectPath;

lib/declarations.d.ts

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

573573
interface IAndroidOptions extends IEmbedOptions {
574+
/**
575+
* The product flavor to build, when the app declares any. `--gradleFlavor foo`
576+
* runs `assembleFooDebug` instead of `assembleDebug`.
577+
*/
578+
gradleFlavor: string;
574579
gradlePath: string;
575580
gradleArgs: string;
576581
}

lib/definitions/build.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface IAndroidBuildData
3131
extends IBuildData,
3232
IAndroidSigningData,
3333
IHasAndroidBundle {
34+
gradleFlavor?: string;
3435
gradlePath?: string;
3536
gradleArgs?: string;
3637
}

lib/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export class Options {
219219
default: false,
220220
hasSensitiveValue: false,
221221
},
222+
gradleFlavor: { type: OptionType.String, hasSensitiveValue: false },
222223
gradlePath: { type: OptionType.String, hasSensitiveValue: false },
223224
gradleArgs: { type: OptionType.String, hasSensitiveValue: false },
224225
hostProjectPath: { type: OptionType.String, hasSensitiveValue: false },

lib/services/android/gradle-build-args-service.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,15 @@ export class GradleBuildArgsService implements IGradleBuildArgsService {
8585
}
8686

8787
private getBuildTaskName(buildData: IAndroidBuildData): string {
88-
const baseTaskName = buildData.androidBundle ? "bundle" : "assemble";
88+
let baseTaskName = buildData.androidBundle ? "bundle" : "assemble";
89+
90+
// a product flavor sits between the task and the build type -
91+
// `assembleFooRelease`, `bundleFooDebug`
92+
const flavor = buildData.gradleFlavor;
93+
if (flavor) {
94+
baseTaskName += flavor[0].toUpperCase() + flavor.slice(1);
95+
}
96+
8997
const buildTaskName = buildData.release
9098
? `${baseTaskName}${Configurations.Release}`
9199
: `${baseTaskName}${Configurations.Debug}`;

test/services/android/gradle-build-args-service.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,56 @@ describe("GradleBuildArgsService", () => {
163163
);
164164
});
165165

166+
describe("gradleFlavor", async () => {
167+
const testCases = [
168+
{
169+
name: "should build the flavor of a debug build",
170+
buildConfig: { release: false, gradleFlavor: "foo" },
171+
logLevel: "INFO",
172+
expectedTask: "assembleFooDebug",
173+
},
174+
{
175+
name: "should build the flavor of a release build",
176+
buildConfig: { ...releaseBuildConfig, gradleFlavor: "foo" },
177+
logLevel: "INFO",
178+
expectedTask: "assembleFooRelease",
179+
},
180+
{
181+
name: "should build the flavor of an android bundle",
182+
buildConfig: {
183+
release: false,
184+
androidBundle: true,
185+
gradleFlavor: "foo",
186+
},
187+
logLevel: "INFO",
188+
expectedTask: "bundleFooDebug",
189+
},
190+
{
191+
name: "should keep an already capitalized flavor",
192+
buildConfig: { release: false, gradleFlavor: "Foo" },
193+
logLevel: "INFO",
194+
expectedTask: "assembleFooDebug",
195+
},
196+
];
197+
198+
for (const testCase of testCases) {
199+
it(testCase.name, async () => {
200+
const injector = createTestInjector();
201+
const logger = injector.resolve("logger");
202+
logger.getLevel = () => testCase.logLevel;
203+
204+
const gradleBuildArgsService = injector.resolve(
205+
"gradleBuildArgsService"
206+
);
207+
const args = await gradleBuildArgsService.getBuildTaskArgs(
208+
<any>testCase.buildConfig
209+
);
210+
211+
assert.deepStrictEqual(args[0], testCase.expectedTask);
212+
});
213+
}
214+
});
215+
166216
describe("getCleanTaskArgs", async () => {
167217
const testCases = [
168218
{

0 commit comments

Comments
 (0)