Skip to content

Commit 15bd5ab

Browse files
author
Subbarao Garlapati
committed
Add python -m __static__ for running Static Python scripts
Allow users to run Static Python files with a single command: python -m __static__ script.py This installs the Static Python loader and executes the script through it, so both the entry file and all its imports benefit from Static Python compilation. Previously users had to write a separate bootstrap file that called loader.install() before importing their code. Also updates the tutorial documentation to document this new approach as the recommended way to get started. Resolves T256371881
1 parent 982e4cb commit 15bd5ab

4 files changed

Lines changed: 128 additions & 11 deletions

File tree

cinderx/Docs/StaticPython/tutorial.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@
55
Static Python is still under development, and there are a lot of rough
66
edges, including probably bugs that can crash the interpreter.
77

8-
Getting full benefit from Static Python (with cross-module compilation)
9-
requires a module loader able to detect Static Python modules based on
10-
some marker (we use the presence of `import __static__`) and compile
11-
them using the Static Python bytecode compiler. Such a loader is
12-
included (at
13-
`compiler.strict.loader.StrictSourceFileLoader`) and you can
14-
install it by calling `compiler.strict.loader.install()` in
15-
the `main` module of your program (before anything else is imported.)
16-
Note this means the main module itself cannot be Static Python. You can
17-
also just set the `PYTHONINSTALLSTRICTLOADER` environment variable to a
18-
nonzero value, and the loader will be installed for you.
8+
The simplest way to run a Static Python script is:
9+
10+
python -m __static__ script.py
11+
12+
This installs the Static Python loader and executes your script. Both
13+
the entry script and any modules it imports that use `import __static__`
14+
will be compiled through the Static Python pipeline.
15+
16+
### Manual loader installation
17+
18+
For more control, you can install the loader yourself by calling
19+
`cinderx.compiler.strict.loader.install()` in the `main` module of
20+
your program (before anything else is imported.) Note this means the
21+
main module itself cannot be Static Python. You can also just set the
22+
`PYTHONINSTALLSTRICTLOADER` environment variable to a nonzero value,
23+
and the loader will be installed for you.
1924

2025
Once you've installed the loader, any module with `import __static__`
2126
as its first line of code (barring optional docstring and optional
@@ -25,6 +30,8 @@ immutable modules that can\'t have side effects at import time --
2530
without Static Python. `import __static__` also implies Strict, so you
2631
should never use both.)
2732

33+
### Compiling individual files
34+
2835
It is also possible to try out Static Python on simple examples by
2936
running `./python -m compiler --static somemod.py`. This will compile
3037
and execute `somemod.py` as Static Python. Add `--dis` to also dump a
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
3+
"""Run a Python script with Static Python compilation.
4+
5+
Usage: python -m __static__ script.py [args...]
6+
"""
7+
8+
import importlib.util
9+
import os
10+
import sys
11+
12+
13+
def main() -> None:
14+
if len(sys.argv) < 2:
15+
print("Usage: python -m __static__ <script.py> [args...]", file=sys.stderr)
16+
sys.exit(1)
17+
18+
script = sys.argv[1]
19+
if not os.path.isfile(script):
20+
print(f"Error: {script!r} is not a file", file=sys.stderr)
21+
sys.exit(1)
22+
23+
sys.argv[:] = sys.argv[1:]
24+
25+
from cinderx.compiler.strict.loader import install, StrictSourceFileLoader
26+
27+
install()
28+
29+
script_dir = os.path.dirname(os.path.abspath(script))
30+
if script_dir not in sys.path:
31+
sys.path.insert(0, script_dir)
32+
33+
loader = StrictSourceFileLoader("__main__", script)
34+
spec = importlib.util.spec_from_file_location("__main__", script, loader=loader)
35+
if spec is None:
36+
print(f"Error: could not load {script!r}", file=sys.stderr)
37+
sys.exit(1)
38+
39+
module = importlib.util.module_from_spec(spec)
40+
sys.modules["__main__"] = module
41+
spec.loader.exec_module(module)
42+
43+
44+
main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) Meta Platforms, Inc. and affiliates.
22
# flake8: noqa
33
from .test_enum import TestEnum
4+
from .test_module_runner import TestStaticModuleRunner
45
from .test_native_utils import TestNativeInvoke
56
from .tests import StaticTests
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
3+
import os
4+
import subprocess
5+
import sys
6+
import tempfile
7+
import textwrap
8+
import unittest
9+
10+
11+
class TestStaticModuleRunner(unittest.TestCase):
12+
def test_entry_script_is_statically_compiled(self):
13+
with tempfile.NamedTemporaryFile(
14+
mode="w", suffix=".py", delete=False
15+
) as f:
16+
f.write(textwrap.dedent("""
17+
import __static__
18+
from cinderx.compiler.consts import CI_CO_STATICALLY_COMPILED
19+
20+
def add(x: int, y: int) -> int:
21+
return x + y
22+
23+
assert add.__code__.co_flags & CI_CO_STATICALLY_COMPILED
24+
"""))
25+
script_path = f.name
26+
27+
try:
28+
result = subprocess.run(
29+
[sys.executable, "-m", "__static__", script_path],
30+
capture_output=True, text=True, timeout=30,
31+
)
32+
self.assertEqual(result.returncode, 0, result.stderr)
33+
finally:
34+
os.unlink(script_path)
35+
36+
def test_imported_module_is_statically_compiled(self):
37+
with tempfile.TemporaryDirectory() as tmpdir:
38+
lib_path = os.path.join(tmpdir, "sp_lib.py")
39+
with open(lib_path, "w") as f:
40+
f.write(textwrap.dedent("""
41+
import __static__
42+
43+
def compute(x: int, y: int) -> int:
44+
return x + y
45+
"""))
46+
47+
main_path = os.path.join(tmpdir, "sp_main.py")
48+
with open(main_path, "w") as f:
49+
f.write(textwrap.dedent("""
50+
import __static__
51+
from cinderx.compiler.consts import CI_CO_STATICALLY_COMPILED
52+
from sp_lib import compute
53+
54+
assert compute.__code__.co_flags & CI_CO_STATICALLY_COMPILED
55+
"""))
56+
57+
result = subprocess.run(
58+
[sys.executable, "-m", "__static__", main_path],
59+
capture_output=True, text=True, timeout=30,
60+
)
61+
self.assertEqual(result.returncode, 0, result.stderr)
62+
63+
64+
if __name__ == "__main__":
65+
unittest.main()

0 commit comments

Comments
 (0)