Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ jobs:
- name: Maven compile
run: mvn compile -Pci
- name: Maven test
run: mvn test -Djava.library.path=../target/release -Pci -Dtest='!BazelClasspathContainerTest,!BazelProjectViewTest'
run: mvn test -Djava.library.path=../target/release -Pci -Dtest='!BazelClasspathContainerTest,!BazelProjectViewTest,!BazelProjectCreatorTest'
25 changes: 25 additions & 0 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/jni_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,31 @@ pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeQueryTargets(
}
}

#[no_mangle]
pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeIsTestTarget(
mut env: JNIEnv,
_class: JClass,
handle: jlong,
target_label: JString,
) -> jboolean {
let state = match get_state(&mut env, handle) {
Some(s) => s,
None => return 0,
};
let label: String = match env.get_string(&target_label) {
Ok(s) => s.into(),
Err(_) => return 0,
};
let graph = state.graph.lock().unwrap_or_else(|e| e.into_inner());
let is_test = graph.get_target_kind(&label) == bazel_graph::TargetKind::JavaTest;
drop(graph);
if is_test {
1
} else {
0
}
}

#[no_mangle]
pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativePopulateGraph(
mut env: JNIEnv,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ public void shutdown() {
}
}

public String getWorkspacePath() {
rwLock.readLock().lock();
try {
return lastWorkspacePath;
} finally {
rwLock.readLock().unlock();
}
}

public String[] discoverTargets(String[] scopePatterns) {
return discoverTargets(scopePatterns, null);
}
Expand Down Expand Up @@ -381,10 +390,17 @@ private long snapshotHandleNullable() {
private native String[] nativeGetTransitiveWorkspaceDeps(long handle, String[] targetLabels);
private native String[] nativeSyncIncremental(long handle, String[] changedFilePaths);
private native String nativeGetAspectBuildStats(long handle);
private native boolean nativeIsTestTarget(long handle, String targetLabel);

public String getAspectBuildStats() {
long h = snapshotHandleNullable();
if (h == -1) return null;
return nativeGetAspectBuildStats(h);
}

public boolean isTestTarget(String targetLabel) {
long h = snapshotHandleNullable();
if (h == -1) return false;
return nativeIsTestTarget(h, targetLabel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,22 @@ private IClasspathEntry parseEntry(String raw) {
case "LIB":
IPath jarPath = Path.fromPortableString(path);
if (!fileExists(path, jarPath)) {
if (WARNED_MISSING_PATHS.add(path)) {
LOG.log(new Status(IStatus.WARNING, "com.bazel.jdt",
"Skipping non-existent JAR: " + path));
String workspacePath = BazelBridge.getInstance().getWorkspacePath();
if (workspacePath != null) {
String fallback = BazelExternalRepoResolver.resolveFallbackJar(
path, workspacePath);
if (fallback != null) {
jarPath = Path.fromPortableString(fallback);
path = fallback;
}
}
if (!fileExists(path, jarPath)) {
if (WARNED_MISSING_PATHS.add(path)) {
LOG.log(new Status(IStatus.WARNING, "com.bazel.jdt",
"Skipping non-existent JAR: " + path));
}
return null;
}
return null;
}
IPath srcPath = sourcePath != null ? Path.fromPortableString(sourcePath) : null;
if (srcPath != null && !fileExists(sourcePath, srcPath)) {
Expand Down Expand Up @@ -146,6 +157,13 @@ private IClasspathEntry parseEntry(String raw) {
}
return JavaCore.newProjectEntry(Path.fromPortableString("/" + projectName));
case "SRC":
if (isTest) {
IClasspathAttribute[] testAttrs = new IClasspathAttribute[]{
JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true")
};
return JavaCore.newSourceEntry(Path.fromPortableString(path),
null, null, null, testAttrs);
}
return JavaCore.newSourceEntry(Path.fromPortableString(path));
default:
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ private Object handleBuildTarget(List<Object> arguments) {
LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Pre-debug build complete for " + projectName));

BazelRuntimeClasspathEntryResolver.clearCacheForProject(projectName);
BazelClasspathContainer.resetWarnings();
BazelClasspathManager.setMergedClasspathContainer(project, false);
BazelClasspathManager.setMergedClasspathContainer(project, true);
BazelRuntimeClasspathEntryResolver.clearCacheForProject(projectName);

return null;
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.bazel.jdt;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

public final class BazelExternalRepoResolver {
private static final Logger LOG = Logger.getLogger(BazelExternalRepoResolver.class.getName());
private static final ConcurrentHashMap<String, String> OUTPUT_BASE_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, String> JAR_FALLBACK_CACHE = new ConcurrentHashMap<>();
private static final int MAX_JAR_SEARCH_DEPTH = 3;

private BazelExternalRepoResolver() {}

static String resolveOutputBase(String workspacePath) {
return OUTPUT_BASE_CACHE.computeIfAbsent(workspacePath, ws -> {
try {
Path bazelOut = new File(ws, "bazel-out").toPath();
if (Files.exists(bazelOut)) {
Path resolved = bazelOut.toRealPath();
Path execroot = resolved.getParent();
if (execroot != null) {
Path execrootParent = execroot.getParent();
if (execrootParent != null) {
Path outputBase = execrootParent.getParent();
if (outputBase != null
&& Files.isDirectory(outputBase.resolve("external"))) {
String result = outputBase.toString();
LOG.info("Resolved output_base from bazel-out symlink: " + result);
return result;
}
}
}
}
} catch (IOException e) {
LOG.warning("Failed to resolve bazel-out symlink: " + e.getMessage());
}

try {
ProcessBuilder pb = new ProcessBuilder("bazel", "info", "output_base");
pb.directory(new File(ws));
pb.redirectErrorStream(true);
Process proc = pb.start();
String output = new String(proc.getInputStream().readAllBytes()).trim();
int exitCode = proc.waitFor();
if (exitCode == 0 && !output.isEmpty() && new File(output).isDirectory()) {
LOG.info("Resolved output_base from bazel info: " + output);
return output;
}
} catch (Exception e) {
LOG.warning("Failed to run bazel info output_base: " + e.getMessage());
}

return null;
});
}

static String resolveFallbackJar(String missingPath, String workspacePath) {
return JAR_FALLBACK_CACHE.computeIfAbsent(missingPath, path -> {
String outputBase = resolveOutputBase(workspacePath);
if (outputBase == null) return null;

String repoName = extractRepoName(path);
if (repoName == null) return null;

File repoDir = new File(outputBase, "external/" + repoName);
if (!repoDir.isDirectory()) {
repoDir = findBzlmodRepoDir(outputBase, repoName);
}
if (repoDir == null || !repoDir.isDirectory()) return null;

String found = findJarInDirectory(repoDir, MAX_JAR_SEARCH_DEPTH);
if (found != null) {
LOG.info("Fallback JAR resolved: " + path + " -> " + found);
}
return found;
});
}

static String extractRepoName(String path) {
int idx = path.indexOf("/external/");
if (idx < 0) return null;
String afterExternal = path.substring(idx + "/external/".length());
int slash = afterExternal.indexOf('/');
if (slash <= 0) return null;
return afterExternal.substring(0, slash);
}

static File findBzlmodRepoDir(String outputBase, String repoName) {
File externalRoot = new File(outputBase, "external");
if (!externalRoot.isDirectory()) return null;
File[] candidates = externalRoot.listFiles((dir, name) ->
name.contains("~~") && name.endsWith("~" + repoName));
if (candidates != null && candidates.length > 0) {
return candidates[0];
}
return null;
}

private static String findJarInDirectory(File dir, int maxDepth) {
if (maxDepth <= 0 || !dir.isDirectory()) return null;
File[] files = dir.listFiles();
if (files == null) return null;

for (File f : files) {
if (f.isFile() && f.getName().endsWith(".jar")
&& !f.getName().endsWith("-sources.jar")) {
return f.getAbsolutePath();
}
}
for (File f : files) {
if (f.isDirectory()) {
String found = findJarInDirectory(f, maxDepth - 1);
if (found != null) return found;
}
}
return null;
}

static void setOutputBaseForTest(String workspacePath, String outputBase) {
OUTPUT_BASE_CACHE.put(workspacePath, outputBase);
}

static void resetCaches() {
OUTPUT_BASE_CACHE.clear();
JAR_FALLBACK_CACHE.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
Expand All @@ -37,12 +38,18 @@ private BazelProjectCreator() {}
public static IProject createProjectForPackage(
String workspacePath, String packagePath, String targetLabel,
IProgressMonitor monitor) {
return createProjectForPackage(workspacePath, packagePath, targetLabel, monitor, false);
return createProjectForPackage(workspacePath, packagePath, targetLabel, monitor, false, false);
}

public static IProject createProjectForPackage(
String workspacePath, String packagePath, String targetLabel,
IProgressMonitor monitor, boolean deferContainerResolution) {
return createProjectForPackage(workspacePath, packagePath, targetLabel, monitor, deferContainerResolution, false);
}

public static IProject createProjectForPackage(
String workspacePath, String packagePath, String targetLabel,
IProgressMonitor monitor, boolean deferContainerResolution, boolean isTestProject) {
try {
String projectName = LabelUtils.toProjectName(packagePath);
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
Expand Down Expand Up @@ -90,7 +97,7 @@ public static IProject createProjectForPackage(
preCreateResourceFilter(project);
ensureNatures(project, monitor);
String inferredSourceRoot = SourceRootUtils.inferSourceRoot(workspacePath, packagePath);
configureClasspath(project, packagePath, workspacePath, targetLabel, inferredSourceRoot, monitor, deferContainerResolution);
configureClasspath(project, packagePath, workspacePath, targetLabel, inferredSourceRoot, monitor, deferContainerResolution, isTestProject);

return project;
} catch (Exception e) {
Expand Down Expand Up @@ -165,7 +172,8 @@ private static void removeJavaBuilder(IProject project, IProgressMonitor monitor

private static void configureClasspath(IProject project, String packageName,
String workspacePath, String targetLabel, String inferredSourceRoot,
IProgressMonitor monitor, boolean deferContainerResolution) throws CoreException {
IProgressMonitor monitor, boolean deferContainerResolution,
boolean isTestProject) throws CoreException {
IJavaProject javaProject = JavaCore.create(project);

List<IClasspathEntry> sourceEntries = new ArrayList<>();
Expand All @@ -179,7 +187,7 @@ private static void configureClasspath(IProject project, String packageName,
linkedFolder.createLink(new Path(srcDir.getAbsolutePath()), 0, monitor);
}
IPath sourcePath = new Path("/" + project.getName() + "/" + linkedName);
sourceEntries.add(JavaCore.newSourceEntry(sourcePath));
sourceEntries.add(newSourceEntry(sourcePath, isTestProject));
}
}

Expand All @@ -188,15 +196,18 @@ private static void configureClasspath(IProject project, String packageName,
if (inferredSourceRoot != null) {
try {
SourceRootUtils.configureLinkedSourceFolder(
project, workspacePath, inferredSourceRoot, packageName, entries, monitor);
project, workspacePath, inferredSourceRoot, packageName, entries,
monitor, isTestProject);
} catch (Exception e) {
LOG.log(new Status(IStatus.WARNING, "com.bazel.jdt",
"Failed to create linked source folder for " + packageName
+ ", falling back to linked package folder: " + e.getMessage()));
configureLinkedPackageFolder(project, workspacePath, packageName, entries, monitor);
configureLinkedPackageFolder(project, workspacePath, packageName, entries,
monitor, isTestProject);
}
} else {
configureLinkedPackageFolder(project, workspacePath, packageName, entries, monitor);
configureLinkedPackageFolder(project, workspacePath, packageName, entries,
monitor, isTestProject);
}
} else {
entries.addAll(sourceEntries);
Expand All @@ -216,14 +227,24 @@ private static void configureClasspath(IProject project, String packageName,

private static void configureLinkedPackageFolder(IProject project, String workspacePath,
String packageName, List<IClasspathEntry> entries,
IProgressMonitor monitor) throws CoreException {
IProgressMonitor monitor, boolean isTestProject) throws CoreException {
String linkedName = "_pkg";
org.eclipse.core.resources.IFolder linkedFolder = project.getFolder(linkedName);
if (!linkedFolder.exists()) {
File packageDir = new File(workspacePath, packageName);
linkedFolder.createLink(new Path(packageDir.getAbsolutePath()), 0, monitor);
}
entries.add(JavaCore.newSourceEntry(new Path("/" + project.getName() + "/" + linkedName)));
entries.add(newSourceEntry(new Path("/" + project.getName() + "/" + linkedName), isTestProject));
}

static IClasspathEntry newSourceEntry(IPath path, boolean isTestProject) {
if (isTestProject) {
IClasspathAttribute[] attrs = new IClasspathAttribute[]{
JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true")
};
return JavaCore.newSourceEntry(path, null, null, null, attrs);
}
return JavaCore.newSourceEntry(path);
}

private static void addJreContainerEntry(List<IClasspathEntry> entries) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@ public void run(IProgressMonitor pm) throws CoreException {
for (String targetLabel : finalTargets) {
try {
String packagePath = extractPackageName(targetLabel);
boolean isTestTarget = bridge.isTestTarget(targetLabel);
IProject project = BazelProjectCreator.createProjectForPackage(
workspacePath, packagePath, targetLabel, pm, true);
workspacePath, packagePath, targetLabel, pm, true, isTestTarget);

if (firstProject && project != null) {
if (TargetProjectMapping.readWorkspaceConfig(project) == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ private IRuntimeClasspathEntry[] buildEntries(IJavaProject project) {
result.add(rte);
}

LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Resolved " + result.size() + " runtime classpath entries for " + project.getElementName()));
return result.toArray(EMPTY);
} catch (Exception e) {
LOG.log(new Status(IStatus.WARNING, "com.bazel.jdt",
Expand Down
Loading
Loading