@invarn/cibuild 2.6.7 → 2.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cli.cjs +10 -10
  2. package/dist/src/commands/android-java-version.test.js +55 -24
  3. package/dist/src/commands/android-scanner-java.test.d.ts +2 -0
  4. package/dist/src/commands/android-scanner-java.test.d.ts.map +1 -0
  5. package/dist/src/commands/android-scanner-java.test.js +130 -0
  6. package/dist/src/commands/android-scanner.d.ts +47 -22
  7. package/dist/src/commands/android-scanner.d.ts.map +1 -1
  8. package/dist/src/commands/android-scanner.js +187 -52
  9. package/dist/src/commands/ios-scanner.d.ts +42 -1
  10. package/dist/src/commands/ios-scanner.d.ts.map +1 -1
  11. package/dist/src/commands/ios-scanner.js +123 -3
  12. package/dist/src/commands/ios-scheme-ranking.test.d.ts +2 -0
  13. package/dist/src/commands/ios-scheme-ranking.test.d.ts.map +1 -0
  14. package/dist/src/commands/ios-scheme-ranking.test.js +152 -0
  15. package/dist/src/shared/detect-project.d.ts +15 -3
  16. package/dist/src/shared/detect-project.d.ts.map +1 -1
  17. package/dist/src/shared/detect-project.js +126 -15
  18. package/dist/src/shared/detect-project.test.d.ts +2 -0
  19. package/dist/src/shared/detect-project.test.d.ts.map +1 -0
  20. package/dist/src/shared/detect-project.test.js +126 -0
  21. package/dist/src/shared/gradle-settings.d.ts +32 -0
  22. package/dist/src/shared/gradle-settings.d.ts.map +1 -0
  23. package/dist/src/shared/gradle-settings.js +66 -0
  24. package/dist/src/yaml/step-validator-self-assigned.test.d.ts +23 -0
  25. package/dist/src/yaml/step-validator-self-assigned.test.d.ts.map +1 -0
  26. package/dist/src/yaml/step-validator-self-assigned.test.js +129 -0
  27. package/dist/src/yaml/step-validator.d.ts +32 -0
  28. package/dist/src/yaml/step-validator.d.ts.map +1 -1
  29. package/dist/src/yaml/step-validator.js +72 -0
  30. package/dist/src/yaml/steps/android-variant-resolution.test.d.ts +2 -0
  31. package/dist/src/yaml/steps/android-variant-resolution.test.d.ts.map +1 -0
  32. package/dist/src/yaml/steps/android-variant-resolution.test.js +117 -0
  33. package/dist/src/yaml/steps/android.d.ts +37 -0
  34. package/dist/src/yaml/steps/android.d.ts.map +1 -1
  35. package/dist/src/yaml/steps/android.js +130 -17
  36. package/dist/src/yaml/steps/base.d.ts +20 -0
  37. package/dist/src/yaml/steps/base.d.ts.map +1 -1
  38. package/dist/src/yaml/steps/base.js +24 -1
  39. package/dist/src/yaml/steps/java-home-version-spec.test.d.ts +10 -0
  40. package/dist/src/yaml/steps/java-home-version-spec.test.d.ts.map +1 -0
  41. package/dist/src/yaml/steps/java-home-version-spec.test.js +58 -0
  42. package/package.json +1 -1
@@ -0,0 +1,129 @@
1
+ /**
2
+ * A step does not owe itself the variables its own shell sets.
3
+ *
4
+ * Pre-execution validation auto-discovers every `$UPPERCASE` in a step's
5
+ * inputs and treats it as a value the pipeline must supply. For a `script`
6
+ * step the inputs *are* the shell body, so a variable the script computes was
7
+ * indistinguishable from one it needed handed to it — and because the check
8
+ * cannot prompt in a sandbox, the pipeline was refused before step 1 for
9
+ * values nothing was ever meant to provide.
10
+ *
11
+ * A generated multiplatform pipeline hit exactly that. Its prepare step opens
12
+ * with `INTEGRATION="$KMM_IOS_INTEGRATION"`, `IOS_DIR=$(dirname
13
+ * "$IOS_PROJECT_PATH")` and `for WORKSPACE in "$IOS_DIR"/*.xcworkspace`, and
14
+ * the run died on "3 required value(s) missing — INTEGRATION, IOS_DIR,
15
+ * WORKSPACE" while the two variables it genuinely reads were both declared and
16
+ * present. The iOS templates do the same thing with `IOS_DIR` alone.
17
+ *
18
+ * The load-bearing half of these tests is the second half: only the assignment
19
+ * *target* is excused, never the right-hand side, so a script cannot exempt
20
+ * itself from a value it reads by mentioning it.
21
+ */
22
+ import { describe, test, expect } from '@jest/globals';
23
+ import '../yaml/steps/index.js';
24
+ import { StepValidator, shellAssignedNames } from './step-validator.js';
25
+ import { loadConfig } from '../config.js';
26
+ const config = loadConfig();
27
+ /** A one-script workflow, so the only issues are about its variables. */
28
+ function scriptPipeline(content, envs = {}) {
29
+ return {
30
+ format_version: '1',
31
+ meta: { invarn: { stack: 'macos-xcode-26.4' } },
32
+ app: { envs: Object.entries(envs).map(([k, v]) => ({ [k]: v })) },
33
+ workflows: {
34
+ primary: {
35
+ steps: [{ 'script@1.0.0': { title: 'Prepare', inputs: { content } } }],
36
+ },
37
+ },
38
+ };
39
+ }
40
+ async function missingEnv(pipeline) {
41
+ const validator = new StepValidator(pipeline, 'primary', config);
42
+ const result = await validator.validateWorkflow();
43
+ return (result.issuesByCategory.get('environment') ?? [])
44
+ .map((i) => i.requirement.name)
45
+ .sort();
46
+ }
47
+ describe('shellAssignedNames', () => {
48
+ test('plain assignment, command substitution and quoted value', () => {
49
+ const found = shellAssignedNames({
50
+ content: [
51
+ 'PLAIN=1',
52
+ 'CAPTURED=$(dirname "$SOMEWHERE")',
53
+ 'QUOTED="a b"',
54
+ 'EMPTY=',
55
+ ].join('\n'),
56
+ });
57
+ expect([...found].sort()).toEqual(['CAPTURED', 'EMPTY', 'PLAIN', 'QUOTED']);
58
+ });
59
+ test('export, local, readonly, declare, typeset and append', () => {
60
+ const found = shellAssignedNames({
61
+ content: [
62
+ 'export EXPORTED=1',
63
+ ' local SCOPED=2',
64
+ 'readonly FROZEN=3',
65
+ 'declare DECLARED=4',
66
+ 'typeset TYPESET=5',
67
+ 'APPENDED+=6',
68
+ ].join('\n'),
69
+ });
70
+ expect([...found].sort()).toEqual([
71
+ 'APPENDED',
72
+ 'DECLARED',
73
+ 'EXPORTED',
74
+ 'FROZEN',
75
+ 'SCOPED',
76
+ 'TYPESET',
77
+ ]);
78
+ });
79
+ test('loop and read bindings', () => {
80
+ const found = shellAssignedNames({
81
+ content: [
82
+ 'for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do echo "$WORKSPACE"; done',
83
+ 'select CHOICE in a b; do break; done',
84
+ 'read -r LINE',
85
+ 'echo x | while read -r KEY VALUE; do echo "$KEY"; done',
86
+ ].join('\n'),
87
+ });
88
+ expect([...found].sort()).toEqual(['CHOICE', 'KEY', 'LINE', 'VALUE', 'WORKSPACE']);
89
+ });
90
+ test('a flag that looks like an assignment is not one', () => {
91
+ const found = shellAssignedNames({
92
+ content: 'xcodebuild --setting FOO=1 -destination PLATFORM=iOS\ngradlew -PSOME=2',
93
+ });
94
+ expect([...found]).toEqual([]);
95
+ });
96
+ test('walks nested inputs, not just a top-level string', () => {
97
+ const found = shellAssignedNames({ inputs: { content: 'NESTED=1' }, other: ['ALSO=2'] });
98
+ expect([...found].sort()).toEqual(['ALSO', 'NESTED']);
99
+ });
100
+ });
101
+ describe('pre-execution validation and self-assigned variables', () => {
102
+ // The shape that was refused, reduced to its first three lines.
103
+ const PREPARE = [
104
+ 'INTEGRATION="$KMM_IOS_INTEGRATION"',
105
+ 'IOS_DIR=$(dirname "$IOS_PROJECT_PATH")',
106
+ 'for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do echo "$WORKSPACE"; done',
107
+ ].join('\n');
108
+ test('asks for nothing when the script sets its own and the rest are declared', async () => {
109
+ expect(await missingEnv(scriptPipeline(PREPARE, {
110
+ KMM_IOS_INTEGRATION: 'direct',
111
+ IOS_PROJECT_PATH: 'iosApp/iosApp.xcodeproj',
112
+ }))).toEqual([]);
113
+ });
114
+ // The regression this relaxation could introduce, and the reason only the
115
+ // assignment target is excused: a script that READS a variable still needs
116
+ // it, even where it assigns something else from it on the same line.
117
+ test('still requires the right-hand side of a self-assignment', async () => {
118
+ expect(await missingEnv(scriptPipeline(PREPARE))).toEqual([
119
+ 'IOS_PROJECT_PATH',
120
+ 'KMM_IOS_INTEGRATION',
121
+ ]);
122
+ });
123
+ test('still requires a variable the script only reads', async () => {
124
+ expect(await missingEnv(scriptPipeline('SET_HERE=1\necho "$SET_HERE $NEVER_SET"'))).toEqual([
125
+ 'NEVER_SET',
126
+ ]);
127
+ });
128
+ });
129
+ //# sourceMappingURL=step-validator-self-assigned.test.js.map
@@ -8,6 +8,38 @@
8
8
  import type { YAMLPipeline } from './types.js';
9
9
  import type { CIConfig } from '../types.js';
10
10
  import type { WorkflowValidationResult } from './validation-types.js';
11
+ /**
12
+ * Names a shell snippet assigns to itself, in every form a step script uses.
13
+ *
14
+ * The auto-discovery below treats every `$UPPERCASE` it finds in a step's
15
+ * inputs as a value the pipeline must supply. For a `script` step the inputs
16
+ * *are* the shell body, so a variable the script computes reads exactly like
17
+ * one it needs handed to it — and the run is refused for a value nothing was
18
+ * ever supposed to provide.
19
+ *
20
+ * That is not hypothetical. A generated multiplatform pipeline whose prepare
21
+ * step opens with
22
+ *
23
+ * INTEGRATION="$KMM_IOS_INTEGRATION"
24
+ * IOS_DIR=$(dirname "$IOS_PROJECT_PATH")
25
+ * for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do …
26
+ *
27
+ * was refused before step 1 on "3 required value(s) missing — INTEGRATION,
28
+ * IOS_DIR, WORKSPACE", while the two variables it genuinely reads were both
29
+ * declared and present. The shell was correct; the reader was not.
30
+ *
31
+ * Only the assignment *target* is collected, never the right-hand side, so
32
+ * `INTEGRATION="$KMM_IOS_INTEGRATION"` still requires `KMM_IOS_INTEGRATION`.
33
+ * That is what keeps this from becoming a hole: a script cannot excuse itself
34
+ * from a value it reads by mentioning it.
35
+ *
36
+ * Assignment order is deliberately not modelled. A snippet that reads `$FOO`
37
+ * before setting it is a bug in the snippet, and guessing about it here would
38
+ * trade a false refusal for a different false refusal; "the script sets this
39
+ * somewhere" is the question worth asking, and erring toward letting the build
40
+ * run is the right direction for a check that cannot prompt.
41
+ */
42
+ export declare function shellAssignedNames(obj: unknown): Set<string>;
11
43
  /**
12
44
  * StepValidator orchestrates pre-execution validation for a workflow.
13
45
  * It iterates through all steps, collects validation requirements,
@@ -1 +1 @@
1
- {"version":3,"file":"step-validator.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAGV,wBAAwB,EAGzB,MAAM,uBAAuB,CAAC;AAwB/B;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAC,CAAS;IAG9B,OAAO,CAAC,gBAAgB,CAAoE;IAG5F,OAAO,CAAC,mBAAmB,CAA6B;IAKxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAGvC,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;OAGG;IACG,gBAAgB,IAAI,OAAO,CAAC,wBAAwB,CAAC;IAkN3D;;OAEG;YACW,mBAAmB;IAmEjC;;;;;;;;;OASG;IACH,OAAO,CAAC,yBAAyB;IAsBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,SAAS;CA8BlB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CA4D/E"}
1
+ {"version":3,"file":"step-validator.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAGV,wBAAwB,EAGzB,MAAM,uBAAuB,CAAC;AAwB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CA6B5D;AAED;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAC,CAAS;IAG9B,OAAO,CAAC,gBAAgB,CAAoE;IAG5F,OAAO,CAAC,mBAAmB,CAA6B;IAKxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAGvC,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;OAGG;IACG,gBAAgB,IAAI,OAAO,CAAC,wBAAwB,CAAC;IAwN3D;;OAEG;YACW,mBAAmB;IAmEjC;;;;;;;;;OASG;IACH,OAAO,CAAC,yBAAyB;IAsBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,SAAS;CA8BlB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CA4D/E"}
@@ -30,6 +30,71 @@ const POSIX_ENV_ALLOWLIST = new Set([
30
30
  'CIBUILD_SCRATCH',
31
31
  'JAVA_HOME',
32
32
  ]);
33
+ /**
34
+ * Names a shell snippet assigns to itself, in every form a step script uses.
35
+ *
36
+ * The auto-discovery below treats every `$UPPERCASE` it finds in a step's
37
+ * inputs as a value the pipeline must supply. For a `script` step the inputs
38
+ * *are* the shell body, so a variable the script computes reads exactly like
39
+ * one it needs handed to it — and the run is refused for a value nothing was
40
+ * ever supposed to provide.
41
+ *
42
+ * That is not hypothetical. A generated multiplatform pipeline whose prepare
43
+ * step opens with
44
+ *
45
+ * INTEGRATION="$KMM_IOS_INTEGRATION"
46
+ * IOS_DIR=$(dirname "$IOS_PROJECT_PATH")
47
+ * for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do …
48
+ *
49
+ * was refused before step 1 on "3 required value(s) missing — INTEGRATION,
50
+ * IOS_DIR, WORKSPACE", while the two variables it genuinely reads were both
51
+ * declared and present. The shell was correct; the reader was not.
52
+ *
53
+ * Only the assignment *target* is collected, never the right-hand side, so
54
+ * `INTEGRATION="$KMM_IOS_INTEGRATION"` still requires `KMM_IOS_INTEGRATION`.
55
+ * That is what keeps this from becoming a hole: a script cannot excuse itself
56
+ * from a value it reads by mentioning it.
57
+ *
58
+ * Assignment order is deliberately not modelled. A snippet that reads `$FOO`
59
+ * before setting it is a bug in the snippet, and guessing about it here would
60
+ * trade a false refusal for a different false refusal; "the script sets this
61
+ * somewhere" is the question worth asking, and erring toward letting the build
62
+ * run is the right direction for a check that cannot prompt.
63
+ */
64
+ export function shellAssignedNames(obj) {
65
+ const names = new Set();
66
+ const collect = (text) => {
67
+ // `NAME=…`, `export NAME=…`, `local/readonly/declare/typeset NAME=…`, and
68
+ // `NAME+=…`. Anchored to the start of a command — line start, or after a
69
+ // separator — so `--flag=X` and `foo BAR=1` inside a longer word are not
70
+ // mistaken for assignments.
71
+ const assignment = /(?:^|[\n;&|(]|\|\||&&)[ \t]*(?:(?:export|readonly|local|declare|typeset)[ \t]+)?([A-Z_][A-Z_0-9]*)\+?=/g;
72
+ // `for NAME in …`, `select NAME in …`
73
+ const loop = /(?:^|[\n;&|(])[ \t]*(?:for|select)[ \t]+([A-Z_][A-Z_0-9]*)[ \t]+in\b/g;
74
+ // `read NAME`, `read -r NAME OTHER`, `while read -r NAME; do`
75
+ const read = /(?:^|[\n;&|(]|\|\||&&)[ \t]*(?:while[ \t]+)?read[ \t]+((?:-\S+[ \t]+)*)([A-Z_][A-Z_0-9]*(?:[ \t]+[A-Z_][A-Z_0-9]*)*)/g;
76
+ for (const re of [assignment, loop]) {
77
+ let m;
78
+ while ((m = re.exec(text)) !== null)
79
+ names.add(m[1]);
80
+ }
81
+ let m;
82
+ while ((m = read.exec(text)) !== null) {
83
+ for (const n of m[2].trim().split(/[ \t]+/))
84
+ names.add(n);
85
+ }
86
+ };
87
+ const walk = (value) => {
88
+ if (typeof value === 'string')
89
+ collect(value);
90
+ else if (Array.isArray(value))
91
+ value.forEach(walk);
92
+ else if (value && typeof value === 'object')
93
+ Object.values(value).forEach(walk);
94
+ };
95
+ walk(obj);
96
+ return names;
97
+ }
33
98
  /**
34
99
  * StepValidator orchestrates pre-execution validation for a workflow.
35
100
  * It iterates through all steps, collects validation requirements,
@@ -169,10 +234,17 @@ export class StepValidator {
169
234
  const resolvedEnv = this.envResolver.getAll();
170
235
  const alreadyDeclared = new Set(requirements.map((r) => r.name));
171
236
  const referencedVars = this.extractVariableReferences(parsedStep.inputs);
237
+ // Variables the step's own shell sets are not values anyone owes it.
238
+ const selfAssigned = shellAssignedNames(parsedStep.inputs);
172
239
  for (const varName of referencedVars) {
173
240
  // Skip POSIX vars the shell always provides
174
241
  if (POSIX_ENV_ALLOWLIST.has(varName))
175
242
  continue;
243
+ // Skip anything this step's script assigns itself — a computed local
244
+ // reads like a required input, and refusing on one stops a pipeline
245
+ // for a value nothing was ever meant to supply.
246
+ if (selfAssigned.has(varName))
247
+ continue;
176
248
  // Skip if already in resolved env (use hasOwnProperty so empty-string
177
249
  // values set by the YAML/secrets are still treated as "provided")
178
250
  if (Object.prototype.hasOwnProperty.call(resolvedEnv, varName))
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=android-variant-resolution.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"android-variant-resolution.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android-variant-resolution.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * When the pipeline's variant is a guess Gradle cannot resolve.
3
+ *
4
+ * `android/nowinandroid` declares its product flavors as an enum in
5
+ * `build-logic/convention/…/NiaFlavor.kt` and applies them through a
6
+ * convention plugin, so `app/build.gradle.kts` has no `productFlavors {}`
7
+ * block. The literal scan saw no flavors, `VARIANT` stayed `debug`, and
8
+ * Gradle refused the very first Android step of the thirty-app study.
9
+ *
10
+ * Convention plugins are the norm in modern Android, so "literal or nothing"
11
+ * will keep missing them. Gradle names the candidates in its own error; this
12
+ * reads them back.
13
+ *
14
+ * The function under test is the one that ships — the shell is executed here,
15
+ * not re-implemented.
16
+ */
17
+ import { execFileSync } from "node:child_process";
18
+ import { PICK_VARIANT_TASK_FUNCTION, variantTaskCommands } from "./android.js";
19
+ /** Run the shipped shell function over some Gradle output. */
20
+ function pick(gradleOutput, prefix, suffix, buildType) {
21
+ const script = `${PICK_VARIANT_TASK_FUNCTION}\ncibuild_pick_variant_task "${prefix}" "${suffix}" "${buildType}"`;
22
+ return execFileSync("bash", ["-c", script], {
23
+ input: gradleOutput,
24
+ encoding: "utf-8",
25
+ }).trim();
26
+ }
27
+ /** The error nowinandroid produced, verbatim. */
28
+ const NOW_IN_ANDROID = `
29
+ FAILURE: Build failed with an exception.
30
+
31
+ * What went wrong:
32
+ Cannot locate tasks that match ':app:lintDebug' as task 'lintDebug' is ambiguous in project ':app'. Candidates are: 'lintDemoDebug', 'lintDemoRelease', 'lintProdDebug', 'lintProdRelease'.
33
+
34
+ * Try:
35
+ > Run gradlew tasks to get a list of available tasks.
36
+ `;
37
+ describe("cibuild_pick_variant_task", () => {
38
+ test("reads the candidates out of the nowinandroid error", () => {
39
+ expect(pick(NOW_IN_ANDROID, "lint", "", "Debug")).toBe("lintDemoDebug");
40
+ });
41
+ // The order Gradle happens to list them in is not a ranking, but a debug
42
+ // candidate must win over a release one wherever it appears.
43
+ test("prefers a debug candidate whatever order they are listed in", () => {
44
+ const reordered = NOW_IN_ANDROID.replace("'lintDemoDebug', 'lintDemoRelease', 'lintProdDebug', 'lintProdRelease'", "'lintProdRelease', 'lintDemoRelease', 'lintProdDebug', 'lintDemoDebug'");
45
+ expect(pick(reordered, "lint", "", "Debug")).toBe("lintProdDebug");
46
+ });
47
+ test("reads a unit-test task, suffix and all", () => {
48
+ const output = "task 'testDebugUnitTest' is ambiguous in project ':app'. Candidates are: " +
49
+ "'testDemoDebugUnitTest', 'testProdDebugUnitTest', 'testDemoReleaseUnitTest'.";
50
+ expect(pick(output, "test", "UnitTest", "Debug")).toBe("testDemoDebugUnitTest");
51
+ });
52
+ test("honours a build type that is not Debug", () => {
53
+ expect(pick(NOW_IN_ANDROID, "lint", "", "Release")).toBe("lintDemoRelease");
54
+ });
55
+ // Retrying with a task that builds the wrong thing is worse than failing
56
+ // with the error the user can read.
57
+ test("says nothing when no candidate matches the build type", () => {
58
+ const output = "task 'lintDebug' is ambiguous in project ':app'. Candidates are: " +
59
+ "'lintProdRelease', 'lintStagingRelease'.";
60
+ expect(pick(output, "lint", "", "Debug")).toBe("");
61
+ });
62
+ test("says nothing when the failure is not an ambiguity", () => {
63
+ expect(pick("FAILURE: Could not resolve all dependencies.", "lint", "", "Debug")).toBe("");
64
+ });
65
+ });
66
+ describe("the step that uses it", () => {
67
+ const escape = (value) => value;
68
+ test("runs the composed task first, and only resolves if Gradle refuses it", () => {
69
+ const script = variantTaskCommands({
70
+ module: "app",
71
+ variant: "debug",
72
+ prefix: "lint",
73
+ suffix: "",
74
+ escape,
75
+ }).join("\n");
76
+ expect(script).toContain("$GRADLE_CMD :app:lintDebug");
77
+ expect(script).toContain("is ambiguous in project");
78
+ // A flavourless project pays for nothing: the retry is inside the branch.
79
+ expect(script.indexOf("cibuild_pick_variant_task \"lint\"")).toBeGreaterThan(script.indexOf("$GRADLE_CMD :app:lintDebug"));
80
+ });
81
+ // Whichever of lint and unit tests runs first pays for the discovery; the
82
+ // other reads VARIANT out of the envstore.
83
+ test("exports the variant it resolved", () => {
84
+ const script = variantTaskCommands({
85
+ module: "app",
86
+ variant: "debug",
87
+ prefix: "test",
88
+ suffix: "UnitTest",
89
+ escape,
90
+ }).join("\n");
91
+ expect(script).toContain("envman add --key VARIANT");
92
+ expect(script).toContain("$GRADLE_CMD :app:$RESOLVED_TASK");
93
+ });
94
+ test("keeps a test filter on both the first attempt and the retry", () => {
95
+ const script = variantTaskCommands({
96
+ module: "app",
97
+ variant: "debug",
98
+ prefix: "test",
99
+ suffix: "UnitTest",
100
+ gradleArgs: "--tests 'com.acme.*'",
101
+ escape,
102
+ }).join("\n");
103
+ const withFilter = script.split("\n").filter((l) => l.includes("--tests 'com.acme.*'"));
104
+ expect(withFilter).toHaveLength(2);
105
+ });
106
+ test("propagates a failure that is not an ambiguity", () => {
107
+ const script = variantTaskCommands({
108
+ module: "app",
109
+ variant: "debug",
110
+ prefix: "lint",
111
+ suffix: "",
112
+ escape,
113
+ }).join("\n");
114
+ expect(script).toContain('exit "$VARIANT_STATUS"');
115
+ });
116
+ });
117
+ //# sourceMappingURL=android-variant-resolution.test.js.map
@@ -65,6 +65,43 @@ export declare class GradleBuildStepExecutor extends BaseStepExecutor {
65
65
  * Android lint step executor
66
66
  * Runs Android lint checks
67
67
  */
68
+ /**
69
+ * Bash that picks a real variant task out of Gradle's own ambiguity error.
70
+ *
71
+ * `android/nowinandroid` declares its product flavors as an enum in
72
+ * `build-logic/convention/…/NiaFlavor.kt` and applies them through a
73
+ * convention plugin, so `app/build.gradle.kts` has no `productFlavors {}`
74
+ * block for the literal scan to find. `VARIANT` stayed `debug` and Gradle
75
+ * refused the very first Android step:
76
+ *
77
+ * Cannot locate tasks that match ':app:lintDebug' as task 'lintDebug' is
78
+ * ambiguous in project ':app'. Candidates are: 'lintDemoDebug',
79
+ * 'lintProdDebug', …
80
+ *
81
+ * Gradle has just listed the answer. Reading it back costs one extra
82
+ * invocation on the failing case and nothing at all on a flavourless project,
83
+ * which is why this is the fallback rather than an up-front `tasks --all`.
84
+ *
85
+ * Written as a shell function so what ships is what the test runs.
86
+ */
87
+ export declare const PICK_VARIANT_TASK_FUNCTION = "cibuild_pick_variant_task() {\n # $1 task prefix (lint, test), $2 task suffix (empty, UnitTest),\n # $3 the build type to prefer (Debug). Gradle's output arrives on stdin.\n awk '/Candidates are:/ { sub(/.*Candidates are:/, \"\"); print; exit }' |\n tr ',' '\\n' |\n tr -d \" '\\\".\" |\n grep -E \"^${1}[A-Za-z0-9]*${3}${2}$\" |\n head -1\n}";
88
+ /**
89
+ * The commands that run a variant-specific Gradle task and, if the variant
90
+ * turns out to be a guess Gradle cannot resolve, resolve it and retry once.
91
+ *
92
+ * The resolved variant is exported with `envman`, so the *other*
93
+ * variant-specific step in the same pipeline does not repeat the discovery —
94
+ * whichever of lint and unit tests runs first pays for it.
95
+ */
96
+ export declare function variantTaskCommands(options: {
97
+ module: string;
98
+ variant: string;
99
+ prefix: string;
100
+ suffix: string;
101
+ /** Appended to both the first attempt and the retry. */
102
+ gradleArgs?: string;
103
+ escape: (value: string) => string;
104
+ }): string[];
68
105
  export declare class AndroidLintStepExecutor extends BaseStepExecutor {
69
106
  getValidationRequirements(_inputs: AndroidLintInputs, env: Record<string, string>, _config: CIConfig): ValidationRequirement[];
70
107
  execute(inputs: AndroidLintInputs, env: Record<string, string>, config: CIConfig): Promise<StepDef>;
@@ -1 +1 @@
1
- {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}