Skip to content

fix: compute longest common prefix for sibling packages in empty source roots#3

Open
runchen0919 wants to merge 4 commits intodevelopfrom
support-no-standard-bazel-structures
Open

fix: compute longest common prefix for sibling packages in empty source roots#3
runchen0919 wants to merge 4 commits intodevelopfrom
support-no-standard-bazel-structures

Conversation

@runchen0919
Copy link
Copy Markdown
Owner

When a Bazel package contains source files from sibling Java packages (e.g. dto/ and merge/) without files in the root package, findCommonParentPackagePrefix returned null because it only checked if a detected package was a prefix of all others. This caused "an empty package fragment root must map to one Java package" errors.

Now falls back to computing the actual longest common prefix path, allowing targets like messier_merger_lib with sources spanning sibling sub-packages to be provisioned correctly.

runchen0919 and others added 4 commits March 31, 2026 15:41
Mark EXPLICIT jdeps classpath entries as exported so downstream
projects can resolve types referenced in this project's public API.
ECJ aggressively resolves all types in the API chain, while javac
may not record them in jdeps of downstream targets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Register fallback libraries discovered during jdeps resolution to
  avoid repeated expensive lookups for the same jar across targets.
- Index class jars under ijar/hjar paths when no interfaceJar exists,
  since jdeps files typically reference ijar/hjar paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the unnecessary LOG.isDebugEnabled() guard around a warn-level
log statement so unresolvable compile jars are always reported.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ce roots

When a Bazel package contains source files from sibling Java packages
(e.g. dto/ and merge/) without files in the root package,
findCommonParentPackagePrefix returned null because it only checked
if a detected package was a prefix of all others. This caused
"an empty package fragment root must map to one Java package" errors.

Now falls back to computing the actual longest common prefix path,
allowing targets like messier_merger_lib with sources spanning
sibling sub-packages to be provisioned correctly.

Also adds support for merging multiple targets sharing an empty source
root into a single Eclipse project, and collects dependencies from
unprovisioned sibling targets for complete classpath computation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@runchen0919 runchen0919 force-pushed the support-no-standard-bazel-structures branch from 4a7ca86 to 931ef78 Compare March 31, 2026 08:09
Comment on lines +1265 to +1286
// none of the detected packages is a prefix of all others (sibling packages case)
// compute the actual longest common prefix path
// e.g. for [com/foo/bar/dto, com/foo/bar/merge] this yields com/foo/bar
IPath commonPrefix = null;
for (IPath path : detectedJavaPackagesForSourceDirectory) {
if (commonPrefix == null) {
commonPrefix = path;
} else {
var minLen = Math.min(commonPrefix.segmentCount(), path.segmentCount());
var commonLen = 0;
for (var i = 0; i < minLen; i++) {
if (commonPrefix.segment(i).equals(path.segment(i))) {
commonLen = i + 1;
} else {
break;
}
}
commonPrefix = commonPrefix.uptoSegment(commonLen);
}
}

return (commonPrefix == null) || commonPrefix.isEmpty() ? null : commonPrefix;
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Longest common prefix computation: Previously, when detected Java packages were siblings (e.g., com/foo/bar/dto and com/foo/bar/merge) rather than nested, the method returned null, causing source root detection to fail. The fix computes the longest common path prefix (e.g., com/foo/bar) to correctly infer the source root.

Why: Non-standard Bazel structures may have sources spread across sibling packages. The old logic only handled parent-child relationships, not siblings, causing Eclipse to fail at identifying source directories.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Merged projects & sibling target support: Major refactoring:

  • Merge multi-target packages: When multiple targets in the same package share an empty source root, they are merged into a single Eclipse project (selecting the java_library with the most sources as primary).
  • Sibling target dependency collection: During classpath computation, dependencies from unprovisioned sibling targets are also collected to ensure complete classpath.
  • Improved error tolerance: Non-target projects now log a warning instead of throwing; dependency pre-loading failures no longer break the flow.

Why: Some Bazel projects split a single package into multiple small targets sharing the same source directory. Creating separate Eclipse projects for each would cause source file conflicts and incomplete classpaths.

Comment on lines +361 to +362
// Register back to avoid repeated fallback for the same jar across multiple targets
aspectsInfo.registerFallbackLibrary(artifact.getRelativePath(), library);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

After discovering a library through fallback logic (filesystem scan), it's registered back via registerFallbackLibrary() so subsequent lookups for the same jar hit the cache instead of repeating the expensive fallback.

Why: Multiple targets may reference the same jar in their jdeps. Without caching, each occurrence triggers a fresh fallback scan, causing redundant computation.

Comment on lines +264 to +275

// When there is no interfaceJar (e.g., libraries discovered from runtime classpath),
// also index under potential ijar/hjar paths so that jdeps lookups can find them.
// jdeps files typically reference ijar/hjar paths, not class jar paths.
if (interfaceJar == null) {
var classPath = classJar.getRelativePath();
if (classPath.endsWith(".jar")) {
var base = classPath.substring(0, classPath.length() - ".jar".length());
libraryByJdepsRootRelativePath.putIfAbsent(base + "-ijar.jar", library);
libraryByJdepsRootRelativePath.putIfAbsent(base + "-hjar.jar", library);
}
}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

When registering a library that has only a classJar (no interfaceJar), the system now also indexes it under -ijar.jar and -hjar.jar path variants.

Why: Bazel's jdeps files typically reference ijar/hjar paths, not raw class jar paths. Libraries discovered from runtime classpath often lack interface jars, causing jdeps lookups to miss. Pre-indexing these variants ensures direct hits without fallback.

Comment on lines +378 to +382
// Export EXPLICIT jdeps entries so downstream projects can resolve types
// referenced in this project's public API. This addresses ECJ vs javac differences:
// ECJ aggressively resolves all types in the API chain, while javac may not
// record them in jdeps of downstream targets.
entry.setExported(true);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Why: Eclipse uses ECJ (Eclipse Compiler for Java), which differs from javac in type resolution behavior. ECJ aggressively resolves all types in the public API chain — if project A's public method returns a type from project B, ECJ requires B on the classpath when compiling project C (which depends on A). However, javac may not record B as a dependency in C's jdeps. By marking explicit jdeps entries as exported, downstream projects can transitively see these dependencies, resolving ECJ compilation errors.

@runchen0919 runchen0919 marked this pull request as ready for review March 31, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant