fix(cso): a Maven or Gradle repository is Java, not Python (#3028)

With no supported stack metadata, preparation defaulted to Python and asked
a Java repository for requirements.txt. A root pom.xml, build.gradle or
build.gradle.kts now names the stack as Java (unsupported for runtime
preparation, so the prerequisite says so), and Python helper scripts or a
root requirements.txt beside it no longer make it Python.
This commit is contained in:
garrytan committed 2026-10-06 20:42:38 +00:00
1 parent 2ff3b6a854
commit d8e934af5e
4 files changed
+86 -10

No files matched your search

+18 -6
View File
@@ -5,7 +5,8 @@ import { createHash } from 'node:crypto';
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
import { isAbsolute, relative, resolve, sep } from 'node:path';
export type CsoStack = 'node' | 'bun' | 'python' | 'rails';
/** 'java' (Maven or Gradle) is detected so it is reported as itself; no runtime prepares it. */
export type CsoStack = 'node' | 'bun' | 'python' | 'rails' | 'java';
export interface PreparationPrerequisite {
code: string;
message: string;
@@ -1196,7 +1197,13 @@ export function inspectPreparation(snapshotPath: string, stack?: CsoStack): Prep
detectedStacks.push('node');
if (read(root, 'Gemfile.lock', true) !== undefined || read(root, 'Gemfile', true) !== undefined)
detectedStacks.push('rails');
if (
// A root Maven or Gradle build names the repository's language; Python
// metadata beside it is helper tooling, not a second application stack.
const jvmBuild = ['pom.xml', 'build.gradle', 'build.gradle.kts'].find(
(file) => read(root, file, true) !== undefined,
);
if (jvmBuild) detectedStacks.push('java');
else if (
read(root, 'uv.lock', true) !== undefined ||
read(root, 'requirements.txt', true) !== undefined ||
read(root, 'pyproject.toml', true) !== undefined
@@ -1210,12 +1217,17 @@ export function inspectPreparation(snapshotPath: string, stack?: CsoStack): Prep
const detected = stack ?? detectedStacks[0] ?? 'python';
plan.stack = detected;
plan.runtimeProfile = detected;
if (detected === 'java')
fail(
'UNSUPPORTED_STACK',
`Java (${jvmBuild ?? 'Maven or Gradle'}) is not a supported runtime stack yet; supported runtime stacks are Node, Bun, Python, and Rails.`,
jvmBuild,
);
if (!['node', 'bun', 'python', 'rails'].includes(detected))
fail('UNSUPPORTED_STACK', 'Supported runtime stacks are Node, Bun, Python, and Rails.');
({ node: inspectNode, bun: inspectBun, python: inspectPython, rails: inspectRails })[detected](
root,
plan,
);
({ node: inspectNode, bun: inspectBun, python: inspectPython, rails: inspectRails })[
detected as Exclude<CsoStack, 'java'>
](root, plan);
} catch (error) {
plan.status = 'prerequisites';
plan.prerequisites.push(
+1 -1
View File
@@ -50,7 +50,7 @@ function snapshot(stack: CsoStack): string {
return base;
}
function runtime(stack: CsoStack | 'postgresql') { return qualifiedRuntimeFixture(stack); }
function runtime(stack: Parameters<typeof qualifiedRuntimeFixture>[0]) { return qualifiedRuntimeFixture(stack); }
function catalog(..._stacks: Array<CsoStack | 'postgresql'>) { return completeRuntimeCatalogFixture('executor-test-v1'); }
function cacheFixture() {
const base = root('cso-executor-cache-'), staging = path.join(base, 'staging'); fs.mkdirSync(staging, { mode: 0o700 });
+62
View File
@@ -0,0 +1,62 @@
/**
* A Java/Maven repository was reported as `stack: python` and asked for a
* requirements.txt, because no supported stack matched and preparation
* defaulted to Python. A root Maven or Gradle build now names the stack as
* Java (unsupported for runtime preparation, so static assessment only), and
* Python helper scripts or metadata beside it do not turn it into Python.
*/
import { afterEach, describe, expect, test } from 'bun:test';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { inspectPreparation } from '../lib/cso/preparation';
const roots: string[] = [];
afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
function fixture(files: Record<string, string>): string {
const root = mkdtempSync(join(tmpdir(), 'cso-preparation-java-'));
roots.push(root);
for (const [path, contents] of Object.entries(files)) {
mkdirSync(join(root, path, '..'), { recursive: true });
writeFileSync(join(root, path), contents);
}
return root;
}
const maven = {
'pom.xml': '<project><modelVersion>4.0.0</modelVersion><groupId>a</groupId><artifactId>app</artifactId><version>1</version></project>\n',
'src/main/java/app/App.java': 'package app;\npublic class App { public static void main(String[] a) {} }\n',
'scripts/release.py': 'print("helper")\n',
'tools/lint_check.py': 'import sys\n',
};
describe('/cso preparation: a Maven repository with helper Python scripts is Java', () => {
test('helper .py scripts only', () => {
const plan = inspectPreparation(fixture(maven));
expect(plan.stack).toBe('java');
expect(plan.status).toBe('prerequisites');
expect(plan.prerequisites).toEqual([{
code: 'UNSUPPORTED_STACK',
message: 'Java (pom.xml) is not a supported runtime stack yet; supported runtime stacks are Node, Bun, Python, and Rails.',
path: 'pom.xml',
}]);
expect(JSON.stringify(plan)).not.toContain('requirements.txt');
});
test('helper scripts with a root requirements.txt stay Java', () => {
const plan = inspectPreparation(fixture({ ...maven, 'requirements.txt': 'requests==2.32.3\n' }));
expect(plan.stack).toBe('java');
expect(plan.prerequisites[0]?.code).toBe('UNSUPPORTED_STACK');
});
test('Gradle builds are Java too', () => {
const plan = inspectPreparation(fixture({ 'build.gradle.kts': 'plugins { java }\n', 'scripts/x.py': '' }));
expect(plan.stack).toBe('java');
expect(plan.prerequisites[0]?.message).toContain('Java (build.gradle.kts)');
});
test('a Python project without a JVM build is still Python (control)', () => {
expect(inspectPreparation(fixture({ 'requirements.txt': 'requests==2.32.3\n', 'app.py': '' })).stack).toBe('python');
});
});
+5 -3
View File
@@ -1,4 +1,6 @@
import type { CsoStack } from '../../lib/cso/preparation';
type RuntimeStack = Exclude<CsoStack, 'java'>;
import { canonical, sha256 } from '../../lib/cso/contracts';
import { ISOLATION_POLICY_HASH } from '../../lib/cso/docker';
import {
@@ -14,7 +16,7 @@ const SOURCE_COMMIT = 'b'.repeat(40);
const WORKFLOW = 'https://github.com/garrytan/gstack/actions/runs/1';
const DIGEST = `sha256:${'a'.repeat(64)}`;
const VERSIONS: Record<CsoStack | 'postgresql', Record<string, string>> = {
const VERSIONS: Record<RuntimeStack | 'postgresql', Record<string, string>> = {
node: { node: '24.1.0', npm: '11.3.0', 'cso-preparation': '1.0.0' },
bun: { bun: '1.3.10', 'cso-preparation': '1.0.0' },
python: { python: '3.12.9', uv: '0.8.0', 'cso-preparation': '1.0.0' },
@@ -22,12 +24,12 @@ const VERSIONS: Record<CsoStack | 'postgresql', Record<string, string>> = {
postgresql: { postgresql: '17.2' },
};
function runtimeId(stack: CsoStack | 'postgresql', platform: RuntimePlatform): string {
function runtimeId(stack: RuntimeStack | 'postgresql', platform: RuntimePlatform): string {
return `${stack}-qualified-test-${platform === 'linux/amd64' ? 'amd64' : 'arm64'}`;
}
export function qualifiedRuntimeFixture(
stack: CsoStack | 'postgresql',
stack: RuntimeStack | 'postgresql',
platform: RuntimePlatform = 'linux/amd64',
): QualifiedRuntime {
const arch = platform === 'linux/amd64' ? 'amd64' : 'arm64';