Skip to content

Commit 336bff9

Browse files
authored
feat: JVM module discovery and package-declaration index (issue-2, PR 1/3) (#30)
1 parent b96dcf1 commit 336bff9

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

.DS_Store

6 KB
Binary file not shown.

src/repokg/deps.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
# Built-in path roots that can never be workspace crates. `self`/`super` are
3434
# relative module paths; `crate::` paths are resolved dir-level below.
3535
RUST_SKIP = {"std", "core", "alloc", "crate", "self", "super"}
36+
# JVM: a Maven/Gradle module is any dir carrying its own build file.
37+
JVM_BUILD_FILES = ("pom.xml", "build.gradle", "build.gradle.kts")
38+
# `package a.b.c;` (Java) / `package a.b.c` (Kotlin, no semicolon).
39+
JVM_PACKAGE_RE = re.compile(r"^\s*package\s+([A-Za-z_][\w.]*)\s*;?\s*$", re.M)
40+
JVM_EXTS = (".java", ".kt")
3641

3742

3843
def collect(repo, tree=None):
@@ -249,6 +254,35 @@ def _rust_resolve(src_root, segments, tree):
249254
return target
250255

251256

257+
# -- Java / Kotlin -----------------------------------------------------------
258+
259+
def _jvm_modules(tree):
260+
"""Dirs that are Maven/Gradle modules (each submodule carries its own
261+
build file, so no settings.gradle / <modules> parsing is needed)."""
262+
return sorted(rel for rel, files in tree.items()
263+
if any(b in files for b in JVM_BUILD_FILES))
264+
265+
266+
def _jvm_package_index(repo, tree):
267+
"""{package name -> set of dirs declaring it}, from `package` declarations.
268+
269+
Declarations are language-level ground truth: they work for standard
270+
src/main/java layouts, Kotlin's collapsed directory convention, and plain
271+
src/ repos with no build files at all. Imports (follow-up PR) resolve
272+
against this index by longest package prefix, which makes external
273+
imports (java.*, kotlin.*, third-party) drop out naturally.
274+
"""
275+
index = {}
276+
for rel, files in tree.items():
277+
for f in files:
278+
if not f.endswith(JVM_EXTS):
279+
continue
280+
m = JVM_PACKAGE_RE.search(_read(repo, rel, f))
281+
if m:
282+
index.setdefault(m.group(1), set()).add(rel)
283+
return index
284+
285+
252286
# -- helpers -----------------------------------------------------------------
253287

254288
def _norm(p):

tests/test_deps_jvm.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import os
2+
import tempfile
3+
import unittest
4+
5+
from repokg.code import walk
6+
from repokg.deps import _jvm_modules, _jvm_package_index
7+
8+
9+
def write(root, rel, text):
10+
path = os.path.join(root, rel)
11+
os.makedirs(os.path.dirname(path), exist_ok=True)
12+
with open(path, "w", encoding="utf-8") as f:
13+
f.write(text)
14+
15+
16+
class TestJVMDiscovery(unittest.TestCase):
17+
def setUp(self):
18+
self.tmp = tempfile.TemporaryDirectory()
19+
self.repo = self.tmp.name
20+
21+
def tearDown(self):
22+
self.tmp.cleanup()
23+
24+
def tree(self):
25+
return dict(walk(self.repo))
26+
27+
def test_maven_and_gradle_modules(self):
28+
write(self.repo, "pom.xml", "<project/>")
29+
write(self.repo, "core/pom.xml", "<project/>")
30+
write(self.repo, "api/build.gradle", "")
31+
write(self.repo, "app/build.gradle.kts", "")
32+
write(self.repo, "docs/readme.txt", "")
33+
self.assertEqual(_jvm_modules(self.tree()), ["", "api", "app", "core"])
34+
35+
def test_package_index_java_and_kotlin(self):
36+
write(self.repo, "core/src/main/java/com/acme/core/Engine.java",
37+
"// (c) acme\npackage com.acme.core;\n\npublic class Engine {}\n")
38+
write(self.repo, "core/src/main/java/com/acme/core/util/Str.java",
39+
"package com.acme.core.util;\nclass Str {}\n")
40+
# Kotlin: no semicolon, collapsed dir convention (dir != package path)
41+
write(self.repo, "api/src/main/kotlin/Handlers.kt",
42+
"package com.acme.api\n\nfun handle() {}\n")
43+
idx = _jvm_package_index(self.repo, self.tree())
44+
self.assertEqual(idx["com.acme.core"],
45+
{"core/src/main/java/com/acme/core"})
46+
self.assertEqual(idx["com.acme.core.util"],
47+
{"core/src/main/java/com/acme/core/util"})
48+
self.assertEqual(idx["com.acme.api"], {"api/src/main/kotlin"})
49+
50+
def test_javadoc_and_comments_not_mistaken_for_declarations(self):
51+
write(self.repo, "src/A.java",
52+
"/**\n * package fake.name\n */\npackage real.pkg;\nclass A {}\n")
53+
write(self.repo, "src/B.java", "// package another.fake\nclass B {}\n")
54+
idx = _jvm_package_index(self.repo, self.tree())
55+
self.assertEqual(set(idx), {"real.pkg"})
56+
57+
def test_default_package_files_ignored(self):
58+
write(self.repo, "src/Main.java", "public class Main {}\n")
59+
self.assertEqual(_jvm_package_index(self.repo, self.tree()), {})
60+
61+
62+
if __name__ == "__main__":
63+
unittest.main()

0 commit comments

Comments
 (0)