Skip to content

I've added support for the CLASSPATH environment variable in InferCon… #323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 47 additions & 14 deletions src/main/java/org/javacs/InferConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<String> externalDependencies, Path mavenHome, Path gradleHome) {
/** Environment variables, primarily for testing */
private final Map<String, String> envVars;

InferConfig(
Path workspaceRoot,
Collection<String> externalDependencies,
Path mavenHome,
Path gradleHome,
Map<String, String> envVars) {
this.workspaceRoot = workspaceRoot;
this.externalDependencies = externalDependencies;
this.mavenHome = mavenHome;
this.gradleHome = gradleHome;
this.envVars = Objects.requireNonNullElseGet(envVars, System::getenv);
}

InferConfig(Path workspaceRoot, Collection<String> externalDependencies, Path mavenHome, Path gradleHome) {
this(workspaceRoot, externalDependencies, mavenHome, gradleHome, null); // Null envVars defaults to System.getenv()
}

InferConfig(Path workspaceRoot, Collection<String> 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<String, String> envVars) {
this(workspaceRoot, Collections.emptySet(), defaultMavenHome(), defaultGradleHome(), envVars);
}

private static Path defaultMavenHome() {
Expand All @@ -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<Path> 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<Path>();
Expand All @@ -69,7 +96,7 @@ Set<Path> 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
Expand Down Expand Up @@ -110,7 +137,7 @@ Set<Path> 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
Expand Down Expand Up @@ -173,13 +200,13 @@ private String fileName(Artifact artifact, boolean source) {
return artifact.artifactId + '-' + artifact.version + (source ? "-sources" : "") + ".jar";
}

static Set<Path> mvnDependencies(Path pomXml, String goal) {
static Set<Path> mvnDependencies(Path pomXml, String goal, Map<String, String> 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,
Expand Down Expand Up @@ -230,19 +257,25 @@ static Path readDependency(String line) {
return Paths.get(path);
}

static String getMvnCommand() {
static String getMvnCommand(Map<String, String> 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<String, String> 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();
Expand Down
39 changes: 35 additions & 4 deletions src/test/java/org/javacs/InferConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -15,7 +18,28 @@ public class InferConfigTest {
private Set<String> 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<String, String>) 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<String, String> 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<Path> expectedPaths = Set.of(Paths.get(dummyPath1), Paths.get(dummyPath2));
Set<Path> actualPaths = inferConfig.classPath();

assertThat(actualPaths, is(expectedPaths));
}

@Test
public void mavenClassPath() {
Expand Down Expand Up @@ -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<String, String>) 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<String, String>) null);
assertThat(
thisProject.buildDocPath(),
currentTestProject.buildDocPath(),
hasItem(hasToString(endsWith(".m2/repository/junit/junit/4.13.1/junit-4.13.1-sources.jar"))));
}

Expand All @@ -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)));
}
Expand Down