fix: compute longest common prefix for sibling packages in empty source roots#3
fix: compute longest common prefix for sibling packages in empty source roots#3runchen0919 wants to merge 4 commits intodevelopfrom
Conversation
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>
4a7ca86 to
931ef78
Compare
| // 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| // Register back to avoid repeated fallback for the same jar across multiple targets | ||
| aspectsInfo.registerFallbackLibrary(artifact.getRelativePath(), library); |
There was a problem hiding this comment.
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.
|
|
||
| // 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| // 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); |
There was a problem hiding this comment.
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.
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.