codeanalyzer-java 2.3.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ # Build outputs
2
+ /dist/
3
+ /build/
4
+ *.egg-info/
5
+
6
+ # Native binary + jmods are injected at build time, never committed.
7
+ codeanalyzer_java/_vendor/
8
+
9
+ # Python caches
10
+ __pycache__/
11
+ *.py[cod]
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: codeanalyzer-java
3
+ Version: 2.3.7
4
+ Summary: Static analysis for Java, shipped as a self-contained native binary (no JVM required).
5
+ Project-URL: Homepage, https://github.com/codellm-devkit/codeanalyzer-java
6
+ Project-URL: Issues, https://github.com/codellm-devkit/codeanalyzer-java/issues
7
+ Project-URL: Source, https://github.com/codellm-devkit/codeanalyzer-java
8
+ Author-email: Rahul Krishna <i.m.ralk@gmail.com>
9
+ License-Expression: Apache-2.0
10
+ Keywords: call-graph,codeanalyzer,java,javaparser,static-analysis,wala
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Java
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+
20
+ ![logo](https://raw.githubusercontent.com/codellm-devkit/codeanalyzer-java/main/docs/assets/logo.png)
21
+
22
+ Native WALA implementation of source code analysis tool for Enterprise Java Applications.
23
+
24
+ ## 1. Installing `codeanalyzer`
25
+
26
+ `codeanalyzer` ships as a self-contained, JVM-free native binary. No JDK, no
27
+ GraalVM, and no build step are required — just install from PyPI:
28
+
29
+ ```bash
30
+ pip install codeanalyzer-java
31
+ ```
32
+
33
+ This installs the `codajv` command, which runs the bundled native binary
34
+ (`pip install` automatically selects the wheel matching your OS/architecture).
35
+
36
+ ## 2. Using `codeanalyzer`
37
+
38
+ ```help
39
+ Usage: codajv [-hvV] [--no-build] [-a=<analysisLevel>] [-b=<build>]
40
+ [-i=<input>] [-o=<output>] [-s=<sourceAnalysis>]
41
+ Convert java binary into a comprehensive system dependency graph.
42
+ -i, --input=<input> Path to the project root directory.
43
+ -s, --source-analysis=<sourceAnalysis>
44
+ Analyze a single string of java source code instead
45
+ the project.
46
+ -o, --output=<output> Destination directory to save the output graphs. By
47
+ default, the SDG formatted as a JSON will be
48
+ printed to the console.
49
+ -b, --build-cmd=<build> Custom build command. Defaults to auto build.
50
+ --no-build Do not build your application. Use this option if
51
+ you have already built your application.
52
+ -a, --analysis-level=<analysisLevel>
53
+ Level of analysis to perform. Options: 1 (for just
54
+ symbol table) or 2 (for call graph). Default: 1
55
+ -v, --verbose Print logs to console.
56
+ -h, --help Show this help message and exit.
57
+ -V, --version Print version information and exit.
58
+ -t, --target-files For each file user wants to perform source analysis on top of existing analysis.json
59
+
60
+ ```
61
+
62
+ For example, to analyze a project and print the system dependency graph to the
63
+ console:
64
+
65
+ ```sh
66
+ codajv -i /path/to/java/project
67
+ ```
68
+
69
+ Pass `-o <dir>` to save the output as JSON. Explore the other flags above to
70
+ control the analysis level and build behavior.
71
+
72
+ ## LICENSE
73
+
74
+ ```LICENSE
75
+ Copyright IBM Corporation 2023, 2024
76
+
77
+ Licensed under the Apache Public License 2.0, Version 2.0 (the "License");
78
+ you may not use this file except in compliance with the License.
79
+
80
+ Unless required by applicable law or agreed to in writing, software
81
+ distributed under the License is distributed on an "AS IS" BASIS,
82
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
83
+ See the License for the specific language governing permissions and
84
+ limitations under the License.
85
+ ```
@@ -0,0 +1,66 @@
1
+ ![logo](https://raw.githubusercontent.com/codellm-devkit/codeanalyzer-java/main/docs/assets/logo.png)
2
+
3
+ Native WALA implementation of source code analysis tool for Enterprise Java Applications.
4
+
5
+ ## 1. Installing `codeanalyzer`
6
+
7
+ `codeanalyzer` ships as a self-contained, JVM-free native binary. No JDK, no
8
+ GraalVM, and no build step are required — just install from PyPI:
9
+
10
+ ```bash
11
+ pip install codeanalyzer-java
12
+ ```
13
+
14
+ This installs the `codajv` command, which runs the bundled native binary
15
+ (`pip install` automatically selects the wheel matching your OS/architecture).
16
+
17
+ ## 2. Using `codeanalyzer`
18
+
19
+ ```help
20
+ Usage: codajv [-hvV] [--no-build] [-a=<analysisLevel>] [-b=<build>]
21
+ [-i=<input>] [-o=<output>] [-s=<sourceAnalysis>]
22
+ Convert java binary into a comprehensive system dependency graph.
23
+ -i, --input=<input> Path to the project root directory.
24
+ -s, --source-analysis=<sourceAnalysis>
25
+ Analyze a single string of java source code instead
26
+ the project.
27
+ -o, --output=<output> Destination directory to save the output graphs. By
28
+ default, the SDG formatted as a JSON will be
29
+ printed to the console.
30
+ -b, --build-cmd=<build> Custom build command. Defaults to auto build.
31
+ --no-build Do not build your application. Use this option if
32
+ you have already built your application.
33
+ -a, --analysis-level=<analysisLevel>
34
+ Level of analysis to perform. Options: 1 (for just
35
+ symbol table) or 2 (for call graph). Default: 1
36
+ -v, --verbose Print logs to console.
37
+ -h, --help Show this help message and exit.
38
+ -V, --version Print version information and exit.
39
+ -t, --target-files For each file user wants to perform source analysis on top of existing analysis.json
40
+
41
+ ```
42
+
43
+ For example, to analyze a project and print the system dependency graph to the
44
+ console:
45
+
46
+ ```sh
47
+ codajv -i /path/to/java/project
48
+ ```
49
+
50
+ Pass `-o <dir>` to save the output as JSON. Explore the other flags above to
51
+ control the analysis level and build behavior.
52
+
53
+ ## LICENSE
54
+
55
+ ```LICENSE
56
+ Copyright IBM Corporation 2023, 2024
57
+
58
+ Licensed under the Apache Public License 2.0, Version 2.0 (the "License");
59
+ you may not use this file except in compliance with the License.
60
+
61
+ Unless required by applicable law or agreed to in writing, software
62
+ distributed under the License is distributed on an "AS IS" BASIS,
63
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
64
+ See the License for the specific language governing permissions and
65
+ limitations under the License.
66
+ ```
@@ -0,0 +1,15 @@
1
+ """Python wrapper that ships and runs the codeanalyzer native binary."""
2
+
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ from importlib.metadata import PackageNotFoundError, version
7
+
8
+ try:
9
+ __version__ = version("codeanalyzer-java")
10
+ except PackageNotFoundError: # running from a source checkout
11
+ __version__ = "0.0.0"
12
+ except Exception: # pragma: no cover - importlib.metadata always present on 3.9+
13
+ __version__ = "0.0.0"
14
+
15
+ __all__ = ["__version__"]
@@ -0,0 +1,86 @@
1
+ """Console entry point (``codajv``) for the bundled codeanalyzer native binary.
2
+
3
+ The wheel ships a prebuilt, JVM-free GraalVM native image together with the JDK
4
+ ``.jmod`` files it needs at runtime: both WALA's primordial scope (analysis
5
+ level 2) and the JavaParser bytecode symbol solver read the jmods straight off
6
+ disk, so they cannot be baked into the image. This module locates those bundled
7
+ assets, points ``CODEANALYZER_JMODS_DIR`` at the bundled jmods, and hands off to
8
+ the native binary.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ _PKG_DIR = Path(__file__).resolve().parent
18
+ _VENDOR = _PKG_DIR / "_vendor"
19
+ # pypi/codeanalyzer_java/ -> pypi/ -> repo root (dev/source-checkout fallback).
20
+ _REPO_ROOT = _PKG_DIR.parent.parent
21
+
22
+
23
+ def _find_binary() -> Path | None:
24
+ candidates: list[Path] = []
25
+ override = os.environ.get("CODEANALYZER_NATIVE_BINARY")
26
+ if override:
27
+ candidates.append(Path(override))
28
+ candidates += [
29
+ _VENDOR / "bin" / "codeanalyzer",
30
+ _VENDOR / "bin" / "codeanalyzer.exe",
31
+ _REPO_ROOT / "build" / "native" / "nativeCompile" / "codeanalyzer",
32
+ _REPO_ROOT / "build" / "native" / "nativeCompile" / "codeanalyzer.exe",
33
+ ]
34
+ return next((c for c in candidates if c.is_file()), None)
35
+
36
+
37
+ def _find_jmods() -> Path | None:
38
+ bundled = _VENDOR / "jmods"
39
+ if bundled.is_dir() and any(bundled.glob("*.jmod")):
40
+ return bundled
41
+ override = os.environ.get("CODEANALYZER_JMODS_DIR")
42
+ if override and Path(override).is_dir():
43
+ return Path(override)
44
+ java_home = os.environ.get("JAVA_HOME")
45
+ if java_home:
46
+ jmods = Path(java_home) / "jmods"
47
+ if jmods.is_dir():
48
+ return jmods
49
+ return None
50
+
51
+
52
+ def main() -> int:
53
+ binary = _find_binary()
54
+ if binary is None:
55
+ sys.stderr.write(
56
+ "codajv: could not find a codeanalyzer native binary.\n"
57
+ "This usually means the installed wheel does not match your "
58
+ "platform/architecture, or you are running from a source checkout "
59
+ "without a built binary (run `./gradlew nativeCompile`).\n"
60
+ )
61
+ return 1
62
+
63
+ env = dict(os.environ)
64
+ jmods = _find_jmods()
65
+ if jmods is not None:
66
+ env["CODEANALYZER_JMODS_DIR"] = str(jmods)
67
+
68
+ # Wheel installation does not reliably preserve the executable bit.
69
+ try:
70
+ os.chmod(binary, binary.stat().st_mode | 0o111)
71
+ except OSError:
72
+ pass
73
+
74
+ argv = [str(binary), *sys.argv[1:]]
75
+ if os.name == "posix":
76
+ # Replace this process so signals and the exit code pass through cleanly.
77
+ os.execve(str(binary), argv, env)
78
+ return 127 # unreachable when execve succeeds
79
+
80
+ import subprocess
81
+
82
+ return subprocess.run(argv, env=env).returncode
83
+
84
+
85
+ if __name__ == "__main__":
86
+ raise SystemExit(main())
@@ -0,0 +1,167 @@
1
+ """Local hatchling plugins for the codeanalyzer-java wheel.
2
+
3
+ Two concerns live here:
4
+
5
+ 1. **Version lockstep.** The wheel version is read from the Java project's
6
+ ``gradle.properties`` so the Python package and the native binary it ships
7
+ can never drift apart.
8
+
9
+ 2. **Impure, platform-tagged wheels.** The wheel carries a prebuilt native
10
+ binary plus the JDK ``.jmod`` files it needs at runtime. The build hook
11
+ force-includes those assets, marks the wheel non-purelib, and stamps a
12
+ concrete ``py3-none-<platform>`` tag so pip resolves the correct artifact
13
+ per OS/arch instead of a universal ``py3-none-any`` wheel.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import sysconfig
20
+ from pathlib import Path
21
+
22
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
23
+ from hatchling.metadata.plugin.interface import MetadataHookInterface
24
+
25
+ _BINARY_NAMES = ("codeanalyzer", "codeanalyzer.exe")
26
+
27
+ # Bundling every JDK jmod (~104 MB) pushes the wheel over PyPI's default 100 MB
28
+ # per-file limit, and the native binary does not compress further (~23.5 MB),
29
+ # leaving room for ~80 MB of jmods. We therefore drop only the largest modules
30
+ # that static type resolution never needs, keeping 79 of 83 (~90 MB wheel):
31
+ # - jdk.localedata locale resource *data*, not API types
32
+ # - jdk.compiler com.sun.tools.javac.* internals (annotation-
33
+ # processor sources are the only mild compromise)
34
+ # - jdk.internal.vm.compiler Graal compiler internals, never referenced
35
+ # - jdk.hotspot.agent Serviceability Agent internals, never referenced
36
+ # Set CODEANALYZER_BUNDLE_ALL_JMODS=1 to bundle all 83 (needs a PyPI size bump).
37
+ _EXCLUDED_JMODS = frozenset(
38
+ {
39
+ "jdk.localedata.jmod",
40
+ "jdk.compiler.jmod",
41
+ "jdk.internal.vm.compiler.jmod",
42
+ "jdk.hotspot.agent.jmod",
43
+ }
44
+ )
45
+
46
+
47
+ def _bundle_all_jmods() -> bool:
48
+ return os.environ.get("CODEANALYZER_BUNDLE_ALL_JMODS", "").lower() in {"1", "true", "yes"}
49
+
50
+
51
+ def _select_jmods(jmod_files: list[Path]) -> list[Path]:
52
+ if _bundle_all_jmods():
53
+ return jmod_files
54
+ return [jmod for jmod in jmod_files if jmod.name not in _EXCLUDED_JMODS]
55
+
56
+
57
+ def read_gradle_version(repo_root: Path) -> str:
58
+ """Return the ``version=`` value from ``<repo_root>/gradle.properties``."""
59
+ gradle_properties = repo_root / "gradle.properties"
60
+ for line in gradle_properties.read_text(encoding="utf-8").splitlines():
61
+ line = line.strip()
62
+ if line.startswith("version="):
63
+ return line.split("=", 1)[1].strip()
64
+ raise RuntimeError(f"no 'version=' entry found in {gradle_properties}")
65
+
66
+
67
+ def _wheel_platform_tag() -> str:
68
+ """Concrete wheel tag for the current platform, e.g. ``py3-none-linux_x86_64``.
69
+
70
+ CI sets ``CODEANALYZER_WHEEL_PLATFORM`` to stamp the exact target tag per
71
+ build leg (e.g. ``manylinux_2_28_x86_64``, ``musllinux_1_2_aarch64``,
72
+ ``macosx_11_0_arm64``, ``win_amd64``). The Linux legs build inside the
73
+ matching manylinux container (or a static musl binary), so the tag is
74
+ correct by construction and no ``auditwheel repair`` pass is needed. When
75
+ the override is unset, the tag falls back to the build host's platform.
76
+ """
77
+ override = os.environ.get("CODEANALYZER_WHEEL_PLATFORM")
78
+ if override:
79
+ platform = override
80
+ else:
81
+ platform = sysconfig.get_platform()
82
+ platform = platform.replace("-", "_").replace(".", "_")
83
+ return f"py3-none-{platform}"
84
+
85
+
86
+ def _resolve_binary(repo_root: Path) -> Path:
87
+ override = os.environ.get("CODEANALYZER_NATIVE_BINARY")
88
+ if override:
89
+ candidate = Path(override)
90
+ if candidate.is_file():
91
+ return candidate
92
+ raise RuntimeError(
93
+ f"CODEANALYZER_NATIVE_BINARY is set to '{override}' but no file exists there."
94
+ )
95
+ native_dir = repo_root / "build" / "native" / "nativeCompile"
96
+ for name in _BINARY_NAMES:
97
+ candidate = native_dir / name
98
+ if candidate.is_file():
99
+ return candidate
100
+ raise RuntimeError(
101
+ "no prebuilt codeanalyzer native binary found for this platform/arch.\n"
102
+ f"Looked for {_BINARY_NAMES} under {native_dir}.\n"
103
+ "Build it first with `./gradlew nativeCompile`, or point "
104
+ "CODEANALYZER_NATIVE_BINARY at an existing binary. "
105
+ "codeanalyzer-java ships only prebuilt wheels; there is no from-source "
106
+ "build path for unsupported platforms."
107
+ )
108
+
109
+
110
+ def _resolve_jmods() -> Path:
111
+ override = os.environ.get("CODEANALYZER_JMODS_DIR")
112
+ if override:
113
+ candidate = Path(override)
114
+ if candidate.is_dir():
115
+ return candidate
116
+ raise RuntimeError(
117
+ f"CODEANALYZER_JMODS_DIR is set to '{override}' but it is not a directory."
118
+ )
119
+ java_home = os.environ.get("JAVA_HOME")
120
+ if java_home:
121
+ candidate = Path(java_home) / "jmods"
122
+ if candidate.is_dir():
123
+ return candidate
124
+ raise RuntimeError(
125
+ "could not locate JDK .jmod files to bundle. Set CODEANALYZER_JMODS_DIR "
126
+ "to a directory of .jmod files, or JAVA_HOME to a JDK that has a jmods/ "
127
+ "directory (a JDK 9+ image, not a JRE)."
128
+ )
129
+
130
+
131
+ class CustomMetadataHook(MetadataHookInterface):
132
+ """Inject the version read from gradle.properties."""
133
+
134
+ def update(self, metadata: dict) -> None:
135
+ metadata["version"] = read_gradle_version(Path(self.root).parent)
136
+
137
+
138
+ class CustomBuildHook(BuildHookInterface):
139
+ """Bundle the native binary + jmods and force an impure platform wheel."""
140
+
141
+ def initialize(self, version: str, build_data: dict) -> None:
142
+ if self.target_name != "wheel":
143
+ return
144
+
145
+ repo_root = Path(self.root).parent
146
+ binary = _resolve_binary(repo_root)
147
+ jmods_dir = _resolve_jmods()
148
+ jmod_files = sorted(jmods_dir.glob("*.jmod"))
149
+ if not jmod_files:
150
+ raise RuntimeError(f"no .jmod files found in {jmods_dir}")
151
+ selected = _select_jmods(jmod_files)
152
+ if not selected:
153
+ raise RuntimeError(f"jmod selection is empty (from {jmods_dir})")
154
+
155
+ force_include = build_data["force_include"]
156
+ force_include[str(binary)] = f"codeanalyzer_java/_vendor/bin/{binary.name}"
157
+ for jmod in selected:
158
+ force_include[str(jmod)] = f"codeanalyzer_java/_vendor/jmods/{jmod.name}"
159
+
160
+ build_data["pure_python"] = False
161
+ build_data["infer_tag"] = False
162
+ build_data["tag"] = _wheel_platform_tag()
163
+
164
+ self.app.display_info(
165
+ f"codeanalyzer-java: bundling {binary.name} + {len(selected)}/"
166
+ f"{len(jmod_files)} jmods as {build_data['tag']}"
167
+ )
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "codeanalyzer-java"
7
+ dynamic = ["version"]
8
+ description = "Static analysis for Java, shipped as a self-contained native binary (no JVM required)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "Rahul Krishna", email = "i.m.ralk@gmail.com" }]
13
+ keywords = ["java", "static-analysis", "wala", "javaparser", "call-graph", "codeanalyzer"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Java",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Libraries",
20
+ "Topic :: Software Development :: Quality Assurance",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/codellm-devkit/codeanalyzer-java"
25
+ Issues = "https://github.com/codellm-devkit/codeanalyzer-java/issues"
26
+ Source = "https://github.com/codellm-devkit/codeanalyzer-java"
27
+
28
+ [project.scripts]
29
+ codajv = "codeanalyzer_java.__main__:main"
30
+
31
+ # Version is read from ../gradle.properties by CustomMetadataHook so the wheel
32
+ # stays in lockstep with the native binary.
33
+ [tool.hatch.metadata.hooks.custom]
34
+ path = "hatch_build.py"
35
+
36
+ # Bundles the native binary + jmods and forces an impure, platform-tagged wheel.
37
+ [tool.hatch.build.targets.wheel.hooks.custom]
38
+ path = "hatch_build.py"
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["codeanalyzer_java"]
42
+ # The native binary and jmods are injected at build time via force_include in
43
+ # hatch_build.py; nothing under _vendor/ is tracked in source control.
44
+ exclude = ["codeanalyzer_java/_vendor"]
45
+
46
+ [tool.hatch.build.targets.sdist]
47
+ # The sdist carries only the wrapper sources. Building a wheel from it requires
48
+ # a prebuilt native binary (CODEANALYZER_NATIVE_BINARY) and jmods; without them
49
+ # the build hook fails with a clear message rather than silently shipping a
50
+ # binary-less, non-functional wheel.
51
+ include = [
52
+ "codeanalyzer_java",
53
+ "hatch_build.py",
54
+ "pyproject.toml",
55
+ "README.md",
56
+ ]