Skip to content
Closed
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
6 changes: 6 additions & 0 deletions bazel-jdt-bridge/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"baseDir": ".bazel-jdt",
"configFile": ".bazelproject",
"projectsDir": "projects",
"aspectsDir": "aspects"
}
30 changes: 21 additions & 9 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/aspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ pub enum AspectError {
Utf8Error(#[from] std::string::FromUtf8Error),
}

const ASPECT_DIR_NAME: &str = ".bazel-jdt/aspects";
fn aspect_dir_name() -> String {
crate::internal_config::aspects_dir()
}
const VERSION_FILE: &str = ".version";

const ASPECT_BUILD: &str = "";
Expand Down Expand Up @@ -113,7 +115,8 @@ pub fn version_hash(bazel_major: u32) -> String {
/// what's already on disk. Returns the workspace-relative Bazel aspect label.
pub fn extract_if_needed(workspace_root: &Path, bazel_path: &str) -> Result<String, AspectError> {
let bazel_major = detect_bazel_major_version(bazel_path);
let aspect_dir = workspace_root.join(ASPECT_DIR_NAME);
let dir_name = aspect_dir_name();
let aspect_dir = workspace_root.join(&dir_name);
let version_path = aspect_dir.join(VERSION_FILE);
let current_hash = version_hash(bazel_major);

Expand Down Expand Up @@ -146,7 +149,7 @@ pub fn extract_if_needed(workspace_root: &Path, bazel_path: &str) -> Result<Stri

let label = format!(
"//{}:intellij_info_bundled.bzl%intellij_info_aspect",
ASPECT_DIR_NAME
dir_name
);
Ok(label)
}
Expand All @@ -156,18 +159,24 @@ pub fn extract_if_needed(workspace_root: &Path, bazel_path: &str) -> Result<Stri
pub fn check_bazelignore(workspace_root: &Path) -> Option<String> {
let bazelignore_path = workspace_root.join(".bazelignore");
let contents = fs::read_to_string(&bazelignore_path).ok()?;
let base_dir = crate::internal_config::base_dir();
let dir_name = aspect_dir_name();

for line in contents.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line == ".bazel-jdt" || line == ".bazel-jdt/" || line == ".bazel-jdt/**" || line == "/" {
if line == base_dir
|| line == format!("{}/", base_dir)
|| line == format!("{}/**", base_dir)
|| line == "/"
{
return Some(format!(
"Aspect directory '{}' is covered by .bazelignore \
(pattern: '{}') — aspects may not be found by Bazel. \
Please remove this pattern from .bazelignore.",
ASPECT_DIR_NAME, line
dir_name, line
));
}
}
Expand Down Expand Up @@ -201,9 +210,12 @@ mod tests {
let label = extract_if_needed(tmp.path(), "bazel").unwrap();

assert!(label.contains("intellij_info_bundled.bzl%intellij_info_aspect"));
assert!(label.starts_with("//.bazel-jdt/aspects:intellij_info_bundled.bzl"));
assert!(label.starts_with(&format!(
"//{}:intellij_info_bundled.bzl",
aspect_dir_name()
)));

let aspect_dir = tmp.path().join(ASPECT_DIR_NAME);
let aspect_dir = tmp.path().join(aspect_dir_name());
assert!(aspect_dir.join("BUILD").exists());
assert!(aspect_dir.join("intellij_info_bundled.bzl").exists());
assert!(aspect_dir.join("intellij_info_impl_bundled.bzl").exists());
Expand All @@ -223,7 +235,7 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
extract_if_needed(tmp.path(), "bazel").unwrap();

let version_path = tmp.path().join(ASPECT_DIR_NAME).join(VERSION_FILE);
let version_path = tmp.path().join(aspect_dir_name()).join(VERSION_FILE);
std::thread::sleep(std::time::Duration::from_millis(10));

extract_if_needed(tmp.path(), "bazel").unwrap();
Expand All @@ -235,7 +247,7 @@ mod tests {
#[test]
fn test_extract_updates_when_version_differs() {
let tmp = tempfile::tempdir().unwrap();
let aspect_dir = tmp.path().join(ASPECT_DIR_NAME);
let aspect_dir = tmp.path().join(aspect_dir_name());

extract_if_needed(tmp.path(), "bazel").unwrap();

Expand Down
38 changes: 38 additions & 0 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/internal_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const CONFIG_JSON: &str = include_str!("../../../config.json");

fn extract_json_str<'a>(json: &'a str, key: &str) -> Option<&'a str> {
let needle = format!("\"{}\"", key);
let after_key = json.split(&needle).nth(1)?;
let after_colon = after_key.split_once(':')?.1.trim();
let after_quote = after_colon.strip_prefix('"')?;
let value = after_quote.split('"').next()?;
Some(value)
}

pub fn base_dir() -> &'static str {
extract_json_str(CONFIG_JSON, "baseDir").unwrap_or(".bazel-jdt")
}

pub fn config_file() -> &'static str {
extract_json_str(CONFIG_JSON, "configFile").unwrap_or(".bazelproject")
}

pub fn aspects_dir() -> String {
format!(
"{}/{}",
base_dir(),
extract_json_str(CONFIG_JSON, "aspectsDir").unwrap_or("aspects")
)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_config_reads_from_root_config_json() {
assert_eq!(base_dir(), ".bazel-jdt");
assert_eq!(config_file(), ".bazelproject");
assert_eq!(aspects_dir(), ".bazel-jdt/aspects");
}
}
1 change: 1 addition & 0 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod aspect;
pub mod change_detector;
pub mod internal_config;
pub mod jni_exports;
pub mod state;
pub mod watcher;
Expand Down
46 changes: 38 additions & 8 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl BuildFileWatcher {
let mut debouncer =
notify_debouncer_full::new_debouncer(Duration::from_millis(500), None, tx)?;

// Watch workspace root non-recursively for WORKSPACE and .bazelproject changes
// Watch workspace root non-recursively for WORKSPACE changes
if let Err(e) = debouncer.watch(&workspace_root, RecursiveMode::NonRecursive) {
if cfg!(target_os = "linux") {
log::warn!(
Expand All @@ -48,6 +48,18 @@ impl BuildFileWatcher {
return Err(WatcherError::NotifyError(e));
}

// Watch base dir non-recursively for .bazelproject changes
let bazel_jdt_dir = workspace_root.join(crate::internal_config::base_dir());
if bazel_jdt_dir.is_dir() {
if let Err(e) = debouncer.watch(&bazel_jdt_dir, RecursiveMode::NonRecursive) {
log::warn!(
"Failed to watch {} directory: {}",
crate::internal_config::base_dir(),
e
);
}
}

// Watch each user-selected directory recursively for BUILD file changes
for rel_path in &watch_paths {
let abs_path = workspace_root.join(rel_path);
Expand Down Expand Up @@ -196,17 +208,26 @@ pub fn is_watched_file(path: &Path) -> bool {
.map(|name| {
matches!(
name,
"BUILD" | "BUILD.bazel" | "WORKSPACE" | "WORKSPACE.bazel" | ".bazelproject"
)
"BUILD" | "BUILD.bazel" | "WORKSPACE" | "WORKSPACE.bazel"
) || is_bazelproject_file(path)
})
.unwrap_or(false)
}

fn is_in_bazel_jdt_dir(path: &Path) -> bool {
path.parent()
.and_then(|p| p.file_name())
.and_then(|n| n.to_str())
.map(|n| n == crate::internal_config::base_dir())
.unwrap_or(false)
}

pub fn is_bazelproject_file(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| name == ".bazelproject")
.map(|name| name == crate::internal_config::config_file())
.unwrap_or(false)
&& is_in_bazel_jdt_dir(path)

@kduy969 kduy969 Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_bazelproject_file should check both the name and the location

}

impl Drop for BuildFileWatcher {
Expand Down Expand Up @@ -240,8 +261,10 @@ mod tests {
assert!(is_watched_file(Path::new("WORKSPACE.bazel")));
assert!(is_watched_file(Path::new("/some/path/BUILD")));
assert!(is_watched_file(Path::new("/some/path/BUILD.bazel")));
assert!(is_watched_file(Path::new(".bazelproject")));
assert!(is_watched_file(Path::new("/some/path/.bazelproject")));
assert!(is_watched_file(Path::new(".bazel-jdt/.bazelproject")));
assert!(is_watched_file(Path::new(
"/some/path/.bazel-jdt/.bazelproject"
)));
}

#[test]
Expand All @@ -252,12 +275,19 @@ mod tests {
assert!(!is_watched_file(Path::new("Cargo.toml")));
assert!(!is_watched_file(Path::new("src/main.rs")));
assert!(!is_watched_file(Path::new("")));
// .bazelproject at root (wrong location) should not be watched
assert!(!is_watched_file(Path::new(".bazelproject")));
assert!(!is_watched_file(Path::new("/workspace/.bazelproject")));
}

#[test]
fn test_is_bazelproject_file() {
assert!(is_bazelproject_file(Path::new(".bazelproject")));
assert!(is_bazelproject_file(Path::new("/workspace/.bazelproject")));
assert!(is_bazelproject_file(Path::new(".bazel-jdt/.bazelproject")));
assert!(is_bazelproject_file(Path::new(
"/workspace/.bazel-jdt/.bazelproject"
)));
assert!(!is_bazelproject_file(Path::new(".bazelproject")));
assert!(!is_bazelproject_file(Path::new("/workspace/.bazelproject")));
assert!(!is_bazelproject_file(Path::new("BUILD")));
assert!(!is_bazelproject_file(Path::new("WORKSPACE")));
assert!(!is_bazelproject_file(Path::new("")));
Expand Down
2 changes: 1 addition & 1 deletion bazel-jdt-bridge/java-bridge/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Import-Package: \
org.osgi.framework.hooks.weaving, \
*
Export-Package: com.bazel.jdt
Private-Package: org.objectweb.asm,org.objectweb.asm.signature
Private-Package: org.objectweb.asm,org.objectweb.asm.signature,org.json
Bundle-NativeCode: \
native/linux-x86_64/libbazel_jdt_core.so; osname=Linux; processor=x86_64, \
native/linux-aarch64/libbazel_jdt_core.so; osname=Linux; processor=aarch64, \
Expand Down
16 changes: 16 additions & 0 deletions bazel-jdt-bridge/java-bridge/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@
<version>9.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand All @@ -71,6 +76,17 @@
</dependencies>

<build>
<resources>
<resource>
<directory>${project.basedir}/..</directory>
<includes>
<include>config.json</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class BazelBuildSupport implements IBuildSupport {
"**/BUILD.bazel",
"**/WORKSPACE",
"**/WORKSPACE.bazel",
"**/.bazelproject"
"**/" + InternalConfig.bazelprojectRelPath()
);

private static final ConcurrentLinkedQueue<String> pendingChangedFiles = new ConcurrentLinkedQueue<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static IProject createProjectForPackage(
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IProject project = workspaceRoot.getProject(projectName);

File bazelProjectDir = new File(workspacePath, ".bazel-projects/" + projectName);
File bazelProjectDir = new File(workspacePath, InternalConfig.projectsDirRelPath() + "/" + projectName);
IPath expectedLocation = new Path(bazelProjectDir.getAbsolutePath());

if (project.exists()) {
Expand All @@ -66,17 +66,17 @@ public static IProject createProjectForPackage(
TargetProjectMapping.appendTargets(project, Collections.singletonList(targetLabel));
}
LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Project '" + projectName + "' already at .bazel-projects/, skipping rebuild"));
"Project '" + projectName + "' already at " + InternalConfig.projectsDirRelPath() + "/, skipping rebuild"));
return project;
}
LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Migrating project '" + projectName + "' to .bazel-projects/ location"));
"Migrating project '" + projectName + "' to " + InternalConfig.projectsDirRelPath() + "/ location"));
project.delete(false, true, monitor);
}

if (!bazelProjectDir.exists() && !bazelProjectDir.mkdirs()) {
LOG.log(new Status(IStatus.ERROR, "com.bazel.jdt",
"Failed to create .bazel-projects directory: " + bazelProjectDir.getAbsolutePath()));
"Failed to create projects directory: " + bazelProjectDir.getAbsolutePath()));
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.bazel.jdt;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Hashtable;

import org.eclipse.core.resources.IProject;
Expand Down Expand Up @@ -30,7 +32,7 @@ public boolean applies(IProgressMonitor monitor) {
boolean hasWorkspace = new File(rootFolder, "WORKSPACE").exists()
|| new File(rootFolder, "WORKSPACE.bazel").exists();
if (!hasWorkspace) return false;
return new File(rootFolder, ".bazelproject").exists();
return new File(rootFolder, InternalConfig.bazelprojectRelPath()).exists();
}

@Override
Expand Down Expand Up @@ -62,10 +64,11 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException {
}

bridge.initialize(workspacePath, bazelPath, cacheDir);

LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Importing Bazel workspace: " + workspacePath));

ensureBazelProjectsGitignore(workspacePath);
ensureProjectGitignore(workspacePath);

if (projectView != null && !projectView.getDirectories().isEmpty()) {
String[] watchDirs = projectView.getDirectories().toArray(new String[0]);
Expand Down Expand Up @@ -273,7 +276,7 @@ private boolean tryFastReload(IProgressMonitor monitor) throws CoreException {
bridge.setProjectView(projectView);
}

ensureBazelProjectsGitignore(workspacePath);
ensureProjectGitignore(workspacePath);

if (projectView != null && !projectView.getDirectories().isEmpty()) {
String[] watchDirs = projectView.getDirectories().toArray(new String[0]);
Expand Down Expand Up @@ -325,31 +328,18 @@ public void run(IProgressMonitor pm) throws CoreException {
return true;
}

private static void ensureBazelProjectsGitignore(String workspacePath) {
File gitignore = new File(workspacePath, ".gitignore");
String entry = ".bazel-projects/";
private static void ensureProjectGitignore(String workspacePath) {
File gitignore = new File(workspacePath, InternalConfig.BASE_DIR + "/.gitignore");
if (gitignore.exists()) return;
try {
if (gitignore.exists()) {
String content = new String(java.nio.file.Files.readAllBytes(gitignore.toPath()),
java.nio.charset.StandardCharsets.UTF_8);
for (String line : content.split("\n")) {
if (line.trim().equals(entry)) {
return;
}
}
String separator = content.endsWith("\n") ? "" : "\n";
java.nio.file.Files.write(gitignore.toPath(),
(separator + entry + "\n").getBytes(java.nio.charset.StandardCharsets.UTF_8),
java.nio.file.StandardOpenOption.APPEND);
} else {
java.nio.file.Files.write(gitignore.toPath(),
(entry + "\n").getBytes(java.nio.charset.StandardCharsets.UTF_8));
}
gitignore.getParentFile().mkdirs();
Files.write(gitignore.toPath(),
"*\n".getBytes(StandardCharsets.UTF_8));
LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Added .bazel-projects/ to .gitignore"));
"Created " + InternalConfig.BASE_DIR + "/.gitignore"));
} catch (Exception e) {
LOG.log(new Status(IStatus.WARNING, "com.bazel.jdt",
"Failed to update .gitignore: " + e.getMessage()));
"Failed to create .gitignore: " + e.getMessage()));
}
}

Expand Down
Loading
Loading