anpyra 0.1.0__py3-none-any.whl
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.
- anpyra/__init__.py +22 -0
- anpyra/__main__.py +3 -0
- anpyra/android/__init__.py +1 -0
- anpyra/android/manifest.py +14 -0
- anpyra/android/manifest_inspect.py +14 -0
- anpyra/android/signing.py +14 -0
- anpyra/android/verify.py +14 -0
- anpyra/api.py +5 -0
- anpyra/build.py +16 -0
- anpyra/cli.py +124 -0
- anpyra/common/README.md +17 -0
- anpyra/common/__init__.py +1 -0
- anpyra/common/compiler/__init__.py +1 -0
- anpyra/common/compiler/frontend.py +544 -0
- anpyra/common/compiler/ir.py +156 -0
- anpyra/common/config.py +30 -0
- anpyra/common/project.py +59 -0
- anpyra/compiler/__init__.py +1 -0
- anpyra/compiler/dex.py +14 -0
- anpyra/compiler/frontend.py +14 -0
- anpyra/compiler/ir.py +14 -0
- anpyra/config.py +7 -0
- anpyra/platforms/README.md +9 -0
- anpyra/platforms/__init__.py +1 -0
- anpyra/platforms/desktop/__init__.py +1 -0
- anpyra/platforms/desktop/linux/README.md +7 -0
- anpyra/platforms/desktop/linux/__init__.py +1 -0
- anpyra/platforms/desktop/macos/README.md +7 -0
- anpyra/platforms/desktop/macos/__init__.py +1 -0
- anpyra/platforms/desktop/windows/README.md +7 -0
- anpyra/platforms/desktop/windows/__init__.py +1 -0
- anpyra/platforms/mobile/__init__.py +1 -0
- anpyra/platforms/mobile/android/README.md +20 -0
- anpyra/platforms/mobile/android/__init__.py +1 -0
- anpyra/platforms/mobile/android/api.py +16 -0
- anpyra/platforms/mobile/android/backend.py +17 -0
- anpyra/platforms/mobile/android/build.py +142 -0
- anpyra/platforms/mobile/android/config.py +48 -0
- anpyra/platforms/mobile/android/dex.py +721 -0
- anpyra/platforms/mobile/android/manifest.py +322 -0
- anpyra/platforms/mobile/android/manifest_inspect.py +166 -0
- anpyra/platforms/mobile/android/signing.py +327 -0
- anpyra/platforms/mobile/android/verify.py +269 -0
- anpyra/platforms/mobile/ios/README.md +7 -0
- anpyra/platforms/mobile/ios/__init__.py +1 -0
- anpyra/platforms/registry.py +50 -0
- anpyra/py.typed +0 -0
- anpyra/scaffold.py +35 -0
- anpyra-0.1.0.dist-info/METADATA +96 -0
- anpyra-0.1.0.dist-info/RECORD +55 -0
- anpyra-0.1.0.dist-info/WHEEL +5 -0
- anpyra-0.1.0.dist-info/entry_points.txt +2 -0
- anpyra-0.1.0.dist-info/licenses/LICENSE +201 -0
- anpyra-0.1.0.dist-info/top_level.txt +2 -0
- pyandroid/__init__.py +5 -0
anpyra/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Anpyra: Python source to native Android bytecode."""
|
|
2
|
+
|
|
3
|
+
from .api import Activity, TextView
|
|
4
|
+
from .build import BuildResult, build_apk, build_project
|
|
5
|
+
from .common.compiler.frontend import CompileError, compile_file, compile_source
|
|
6
|
+
from .config import AppConfig, ConfigError, Project, load_project
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Activity",
|
|
11
|
+
"TextView",
|
|
12
|
+
"AppConfig",
|
|
13
|
+
"Project",
|
|
14
|
+
"ConfigError",
|
|
15
|
+
"CompileError",
|
|
16
|
+
"BuildResult",
|
|
17
|
+
"load_project",
|
|
18
|
+
"build_project",
|
|
19
|
+
"build_apk",
|
|
20
|
+
"compile_source",
|
|
21
|
+
"compile_file",
|
|
22
|
+
]
|
anpyra/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Historical Android imports; implementation lives in platforms.mobile.android."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Compatibility import path; implementation lives in anpyra.platforms.mobile.android.manifest."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module as _import_module
|
|
4
|
+
|
|
5
|
+
_implementation = _import_module("anpyra.platforms.mobile.android.manifest")
|
|
6
|
+
__all__ = [name for name in dir(_implementation) if not name.startswith("_")]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def __getattr__(name):
|
|
10
|
+
return getattr(_implementation, name)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __dir__():
|
|
14
|
+
return sorted(set(globals()) | set(dir(_implementation)))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Compatibility import path; implementation lives in anpyra.platforms.mobile.android.manifest_inspect."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module as _import_module
|
|
4
|
+
|
|
5
|
+
_implementation = _import_module("anpyra.platforms.mobile.android.manifest_inspect")
|
|
6
|
+
__all__ = [name for name in dir(_implementation) if not name.startswith("_")]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def __getattr__(name):
|
|
10
|
+
return getattr(_implementation, name)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __dir__():
|
|
14
|
+
return sorted(set(globals()) | set(dir(_implementation)))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Compatibility import path; implementation lives in anpyra.platforms.mobile.android.signing."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module as _import_module
|
|
4
|
+
|
|
5
|
+
_implementation = _import_module("anpyra.platforms.mobile.android.signing")
|
|
6
|
+
__all__ = [name for name in dir(_implementation) if not name.startswith("_")]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def __getattr__(name):
|
|
10
|
+
return getattr(_implementation, name)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __dir__():
|
|
14
|
+
return sorted(set(globals()) | set(dir(_implementation)))
|
anpyra/android/verify.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Compatibility import path; implementation lives in anpyra.platforms.mobile.android.verify."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module as _import_module
|
|
4
|
+
|
|
5
|
+
_implementation = _import_module("anpyra.platforms.mobile.android.verify")
|
|
6
|
+
__all__ = [name for name in dir(_implementation) if not name.startswith("_")]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def __getattr__(name):
|
|
10
|
+
return getattr(_implementation, name)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __dir__():
|
|
14
|
+
return sorted(set(globals()) | set(dir(_implementation)))
|
anpyra/api.py
ADDED
anpyra/build.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Public build entry points and native target dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .common.project import Project
|
|
8
|
+
from .platforms.mobile.android.build import BuildResult, _zip_payload, build_apk
|
|
9
|
+
from .platforms.registry import get_backend
|
|
10
|
+
|
|
11
|
+
__all__ = ["BuildResult", "build_apk", "build_project", "_zip_payload"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_project(project: str | Path | Project = ".", *, target: str = "android") -> BuildResult:
|
|
15
|
+
"""Build using an implemented native backend; Android remains the default."""
|
|
16
|
+
return get_backend(target).build_project(project)
|
anpyra/cli.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""The anpyra command and python -m anpyra share this entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from dataclasses import asdict
|
|
11
|
+
from importlib.metadata import version
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from . import __version__
|
|
15
|
+
from .build import build_project
|
|
16
|
+
from .platforms.mobile.android.verify import inspect_apk
|
|
17
|
+
from .platforms.registry import get_backend, list_targets
|
|
18
|
+
from .scaffold import init_project
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _dump(dex, ir, args):
|
|
22
|
+
if args.dump_ir:
|
|
23
|
+
print(ir.pretty())
|
|
24
|
+
if args.dump_dalvik:
|
|
25
|
+
for method in dex.methods:
|
|
26
|
+
print(f"\n{method.name} ({method.code_units} code units)")
|
|
27
|
+
print("\n".join(" " + line for line in method.assembly))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main(argv: list[str] | None = None) -> int:
|
|
31
|
+
parser = argparse.ArgumentParser(
|
|
32
|
+
prog="anpyra", description="Native Python app toolchain; Android builds available today."
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument("--version", action="version", version=f"Anpyra {__version__}")
|
|
35
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
36
|
+
init = commands.add_parser("init", help="create an application project")
|
|
37
|
+
init.add_argument("directory", type=Path)
|
|
38
|
+
init.add_argument("--package", default="dev.anpyra.app")
|
|
39
|
+
init.add_argument("--label", default="Anpyra")
|
|
40
|
+
for name, help_text in (
|
|
41
|
+
("build", "build and verify a signed APK"),
|
|
42
|
+
("check", "validate source and DEX generation without writing artifacts"),
|
|
43
|
+
):
|
|
44
|
+
command = commands.add_parser(name, help=help_text)
|
|
45
|
+
command.add_argument("project", type=Path, nargs="?", default=Path("."))
|
|
46
|
+
command.add_argument("--dump-ir", action="store_true")
|
|
47
|
+
command.add_argument("--dump-dalvik", action="store_true")
|
|
48
|
+
command.add_argument("--target", default="android", help="native target (default: android)")
|
|
49
|
+
commands.add_parser("targets", help="list implemented and planned native targets")
|
|
50
|
+
verify = commands.add_parser(
|
|
51
|
+
"verify",
|
|
52
|
+
help="verify an Anpyra APK's signature, content digest and DEX checksums",
|
|
53
|
+
)
|
|
54
|
+
verify.add_argument("apk", type=Path)
|
|
55
|
+
verify.add_argument("--json", action="store_true")
|
|
56
|
+
commands.add_parser("doctor", help="show host prerequisites and optional adb availability")
|
|
57
|
+
install = commands.add_parser("install", help="verify and install an APK using optional adb")
|
|
58
|
+
install.add_argument("apk", type=Path)
|
|
59
|
+
install.add_argument("--serial", help="select the adb device")
|
|
60
|
+
install.add_argument("--launch", action="store_true")
|
|
61
|
+
args = parser.parse_args(argv)
|
|
62
|
+
try:
|
|
63
|
+
if args.command == "init":
|
|
64
|
+
project = init_project(args.directory, package=args.package, label=args.label)
|
|
65
|
+
print(f"Created {project.root}")
|
|
66
|
+
print(f'anpyra build "{project.root}"')
|
|
67
|
+
elif args.command in {"build", "check"}:
|
|
68
|
+
backend = get_backend(args.target)
|
|
69
|
+
project = backend.load_project(args.project)
|
|
70
|
+
if args.command == "build":
|
|
71
|
+
result = build_project(project, target=args.target)
|
|
72
|
+
print(f"Built {result.apk_path} ({result.apk_size} bytes)")
|
|
73
|
+
print("Verified APK v2 signature, content digest, DEX SHA-1 and Adler-32.")
|
|
74
|
+
print(f"Build report: {result.report_path}")
|
|
75
|
+
_dump(result.dex_build, result.compile_result.ir, args)
|
|
76
|
+
else:
|
|
77
|
+
compiled, dex = backend.check_project(project)
|
|
78
|
+
print(f"Valid: {project.source_path}; {len(dex.methods)} method listings")
|
|
79
|
+
_dump(dex, compiled.ir, args)
|
|
80
|
+
elif args.command == "targets":
|
|
81
|
+
for target in list_targets():
|
|
82
|
+
status = "available" if target.implemented else "planned (not implemented)"
|
|
83
|
+
print(f"{target.name:8} {target.family:7} {status}")
|
|
84
|
+
elif args.command == "verify":
|
|
85
|
+
report = inspect_apk(args.apk)
|
|
86
|
+
if args.json:
|
|
87
|
+
print(json.dumps(asdict(report), indent=2))
|
|
88
|
+
else:
|
|
89
|
+
print(f"Verified {args.apk}: {report.package} / {report.activity}")
|
|
90
|
+
elif args.command == "doctor":
|
|
91
|
+
print(f"Anpyra: {__version__}")
|
|
92
|
+
print(f"Python: {sys.version.split()[0]} ({sys.executable})")
|
|
93
|
+
print(f"cryptography: {version('cryptography')}")
|
|
94
|
+
print("Build prerequisites: OK")
|
|
95
|
+
print(f"adb (optional for install): {shutil.which('adb') or 'not found'}")
|
|
96
|
+
elif args.command == "install":
|
|
97
|
+
report = inspect_apk(args.apk)
|
|
98
|
+
adb = shutil.which("adb")
|
|
99
|
+
if adb is None:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"adb was not found on PATH; install Android platform-tools to use this command"
|
|
102
|
+
)
|
|
103
|
+
command = [adb] + (["-s", args.serial] if args.serial else [])
|
|
104
|
+
subprocess.run(command + ["install", "-r", str(args.apk.resolve())], check=True)
|
|
105
|
+
if args.launch:
|
|
106
|
+
subprocess.run(
|
|
107
|
+
command
|
|
108
|
+
+ [
|
|
109
|
+
"shell",
|
|
110
|
+
"am",
|
|
111
|
+
"start",
|
|
112
|
+
"-W",
|
|
113
|
+
"-n",
|
|
114
|
+
f"{report.package}/{report.activity}",
|
|
115
|
+
],
|
|
116
|
+
check=True,
|
|
117
|
+
)
|
|
118
|
+
return 0
|
|
119
|
+
except KeyboardInterrupt:
|
|
120
|
+
print("Interrupted.", file=sys.stderr)
|
|
121
|
+
return 130
|
|
122
|
+
except Exception as exc:
|
|
123
|
+
print(f"anpyra: {exc}", file=sys.stderr)
|
|
124
|
+
return 1
|
anpyra/common/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 🧩 Common Framework Code
|
|
2
|
+
|
|
3
|
+
Reusable source and project rules live here. Dependencies flow from platform backends toward common code.
|
|
4
|
+
|
|
5
|
+
| File | Responsibility |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `config.py` | Application ID, label and version validation; shared `ConfigError` |
|
|
8
|
+
| `project.py` | Project settings protocol and source/output/state path validation |
|
|
9
|
+
| `compiler/frontend.py` | Static Python AST validation, types and lowering |
|
|
10
|
+
| `compiler/ir.py` | Immutable operation, function and application records |
|
|
11
|
+
| Package `__init__.py` files | Package markers; no target registration |
|
|
12
|
+
|
|
13
|
+
Common code must not import platform implementations or public Android facades. SDK values, DEX, binary XML, APK packaging and signing belong to Android's backend.
|
|
14
|
+
|
|
15
|
+
The current front end and IR still describe Activity/TextView apps and carry Android lifecycle/descriptor/register assumptions. Future native backends require deliberate generalization; these folders do not imply cross-platform source compatibility today.
|
|
16
|
+
|
|
17
|
+
Read the [native architecture guide](../../../docs/developer_guide/native_platforms.md) and [source reference](../../../docs/developer_guide/source_reference.md).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shared application metadata, project paths and compiler analysis."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shared Python source analysis and intermediate representation."""
|