diff --git a/src/main/java/org/javacs/InferConfig.java b/src/main/java/org/javacs/InferConfig.java index 8cc004c5..8d8209ac 100644 --- a/src/main/java/org/javacs/InferConfig.java +++ b/src/main/java/org/javacs/InferConfig.java @@ -12,6 +12,7 @@ import java.util.*; import java.util.logging.Logger; import java.util.regex.Pattern; +import java.util.stream.Collectors; // Added for Collectors.toSet() import java.util.stream.Stream; class InferConfig { @@ -25,20 +26,37 @@ class InferConfig { private final Path mavenHome; /** Location of the gradle cache, usually ~/.gradle */ private final Path gradleHome; - - InferConfig(Path workspaceRoot, Collection externalDependencies, Path mavenHome, Path gradleHome) { + /** Environment variables, primarily for testing */ + private final Map envVars; + + InferConfig( + Path workspaceRoot, + Collection externalDependencies, + Path mavenHome, + Path gradleHome, + Map envVars) { this.workspaceRoot = workspaceRoot; this.externalDependencies = externalDependencies; this.mavenHome = mavenHome; this.gradleHome = gradleHome; + this.envVars = Objects.requireNonNullElseGet(envVars, System::getenv); + } + + InferConfig(Path workspaceRoot, Collection externalDependencies, Path mavenHome, Path gradleHome) { + this(workspaceRoot, externalDependencies, mavenHome, gradleHome, null); // Null envVars defaults to System.getenv() } InferConfig(Path workspaceRoot, Collection externalDependencies) { - this(workspaceRoot, externalDependencies, defaultMavenHome(), defaultGradleHome()); + this(workspaceRoot, externalDependencies, defaultMavenHome(), defaultGradleHome(), null); } InferConfig(Path workspaceRoot) { - this(workspaceRoot, Collections.emptySet(), defaultMavenHome(), defaultGradleHome()); + this(workspaceRoot, Collections.emptySet(), defaultMavenHome(), defaultGradleHome(), null); + } + + // Constructor for testing, allowing envVars injection. + InferConfig(Path workspaceRoot, Map envVars) { + this(workspaceRoot, Collections.emptySet(), defaultMavenHome(), defaultGradleHome(), envVars); } private static Path defaultMavenHome() { @@ -51,6 +69,15 @@ private static Path defaultGradleHome() { /** Find .jar files for external dependencies, for examples maven dependencies in ~/.m2 or jars in bazel-genfiles */ Set classPath() { + // Check for CLASSPATH environment variable first + String classPathEnv = this.envVars.get("CLASSPATH"); + if (classPathEnv != null && !classPathEnv.isEmpty()) { + LOG.info("Using CLASSPATH environment variable: " + classPathEnv); + return Arrays.stream(classPathEnv.split(Pattern.quote(File.pathSeparator))) + .map(Paths::get) + .collect(Collectors.toSet()); + } + // externalDependencies if (!externalDependencies.isEmpty()) { var result = new HashSet(); @@ -69,7 +96,7 @@ Set classPath() { // Maven var pomXml = workspaceRoot.resolve("pom.xml"); if (Files.exists(pomXml)) { - return mvnDependencies(pomXml, "dependency:list"); + return mvnDependencies(pomXml, "dependency:list", this.envVars); } // Bazel @@ -110,7 +137,7 @@ Set buildDocPath() { // Maven var pomXml = workspaceRoot.resolve("pom.xml"); if (Files.exists(pomXml)) { - return mvnDependencies(pomXml, "dependency:sources"); + return mvnDependencies(pomXml, "dependency:sources", this.envVars); } // Bazel @@ -173,13 +200,13 @@ private String fileName(Artifact artifact, boolean source) { return artifact.artifactId + '-' + artifact.version + (source ? "-sources" : "") + ".jar"; } - static Set mvnDependencies(Path pomXml, String goal) { + static Set mvnDependencies(Path pomXml, String goal, Map envVars) { Objects.requireNonNull(pomXml, "pom.xml path is null"); try { // TODO consider using mvn valide dependency:copy-dependencies -DoutputDirectory=??? instead // Run maven as a subprocess String[] command = { - getMvnCommand(), + getMvnCommand(envVars), "--batch-mode", // Turns off ANSI control sequences "validate", goal, @@ -230,19 +257,25 @@ static Path readDependency(String line) { return Paths.get(path); } - static String getMvnCommand() { + static String getMvnCommand(Map envVars) { var mvnCommand = "mvn"; if (File.separatorChar == '\\') { - mvnCommand = findExecutableOnPath("mvn.cmd"); + mvnCommand = findExecutableOnPath("mvn.cmd", envVars); if (mvnCommand == null) { - mvnCommand = findExecutableOnPath("mvn.bat"); + mvnCommand = findExecutableOnPath("mvn.bat", envVars); } } - return mvnCommand; + // If findExecutableOnPath returns null (e.g. PATH is not set), we should still return "mvn" + // and let the execution fail later if it's not on the (empty) path. + return mvnCommand == null ? "mvn" : mvnCommand; } - private static String findExecutableOnPath(String name) { - for (var dirname : System.getenv("PATH").split(File.pathSeparator)) { + private static String findExecutableOnPath(String name, Map envVars) { + String pathEnv = envVars.get("PATH"); + if (pathEnv == null) { + return null; + } + for (var dirname : pathEnv.split(File.pathSeparator)) { var file = new File(dirname, name); if (file.isFile() && file.canExecute()) { return file.getAbsolutePath(); diff --git a/src/test/java/org/javacs/InferConfigTest.java b/src/test/java/org/javacs/InferConfigTest.java index ba7a100f..2aa9a041 100644 --- a/src/test/java/org/javacs/InferConfigTest.java +++ b/src/test/java/org/javacs/InferConfigTest.java @@ -3,8 +3,11 @@ import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import java.io.File; // For File.pathSeparator import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; // For mock environment variables +import java.util.Map; // For mock environment variables import java.util.Set; import org.junit.Test; @@ -15,7 +18,28 @@ public class InferConfigTest { private Set externalDependencies = Set.of("com.external:external-library:1.2"); private InferConfig both = new InferConfig(workspaceRoot, externalDependencies, mavenHome, gradleHome); private InferConfig gradle = new InferConfig(workspaceRoot, externalDependencies, Paths.get("nowhere"), gradleHome); - private InferConfig thisProject = new InferConfig(Paths.get("."), Set.of()); + private InferConfig thisProject = new InferConfig(Paths.get("."), (Map) null); // Use null to get System.getenv() + + @Test + public void classpathFromEnvironmentVariable() { + String dummyPath1 = Paths.get("dummy1.jar").toAbsolutePath().toString(); + String dummyPath2 = Paths.get("dummy2.jar").toAbsolutePath().toString(); + String classpathValue = dummyPath1 + File.pathSeparator + dummyPath2; + + Map mockEnv = new HashMap<>(); + mockEnv.put("CLASSPATH", classpathValue); + // We also need to provide a PATH, otherwise findExecutableOnPath might fail if it's called by getMvnCommand + // which could be called if externalDependencies is empty and CLASSPATH is also empty (though not in this specific test case) + // For safety, let's provide a minimal PATH. + mockEnv.put("PATH", "/usr/bin:/bin"); + + + InferConfig inferConfig = new InferConfig(Paths.get("."), mockEnv); + Set expectedPaths = Set.of(Paths.get(dummyPath1), Paths.get(dummyPath2)); + Set actualPaths = inferConfig.classPath(); + + assertThat(actualPaths, is(expectedPaths)); + } @Test public void mavenClassPath() { @@ -57,20 +81,25 @@ public void gradleDocPath() { @Test public void dependencyList() { - assertThat(InferConfig.mvnDependencies(Paths.get("pom.xml"), "dependency:list"), not(empty())); + assertThat(InferConfig.mvnDependencies(Paths.get("pom.xml"), "dependency:list", null), not(empty())); } @Test public void thisProjectClassPath() { + // Re-initialize thisProject to ensure it uses the constructor that defaults to System.getenv() + // or explicitly pass null for the env map. + InferConfig currentTestProject = new InferConfig(Paths.get("."), (Map) null); assertThat( - thisProject.classPath(), + currentTestProject.classPath(), hasItem(hasToString(endsWith(".m2/repository/junit/junit/4.13.1/junit-4.13.1.jar")))); } @Test public void thisProjectDocPath() { + // Re-initialize thisProject for the same reasons as above. + InferConfig currentTestProject = new InferConfig(Paths.get("."), (Map) null); assertThat( - thisProject.buildDocPath(), + currentTestProject.buildDocPath(), hasItem(hasToString(endsWith(".m2/repository/junit/junit/4.13.1/junit-4.13.1-sources.jar")))); } @@ -90,6 +119,8 @@ public void parseDependencyLine() { assert pair.length == 2; var line = pair[0]; var expect = pair[1]; + // Note: readDependency is static and doesn't use the envVars from an InferConfig instance. + // This test remains unaffected by the envVars changes to InferConfig. var path = InferConfig.readDependency(line); assertThat(path, equalTo(Paths.get(expect))); }