Skip to content

Commit 4b44979

Browse files
committed
Merge branch 'develop'
2 parents e56f09e + 1f54fd5 commit 4b44979

File tree

30 files changed

+1135
-1402
lines changed

30 files changed

+1135
-1402
lines changed

.cargo/Makefile.toml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
[env]
2+
RADIATE_PYTHON_VERSION = "3.12"
3+
UV_PROJECT_ENVIRONMENT = ".venv"
4+
5+
[config]
6+
default_to_workspace = false
7+
8+
[tasks.print-env]
9+
workspace = false
10+
script = '''
11+
echo RADIATE_PYTHON_VERSION=${RADIATE_PYTHON_VERSION:-<unset>}
12+
echo UV_PROJECT_ENVIRONMENT=${UV_PROJECT_ENVIRONMENT:-<unset>}
13+
'''
14+
15+
[tasks.cargo-py]
16+
description = "UV + maturin (parametrized). Use MODE=gil|nogil and MATURIN_SUBCMD=develop|build"
17+
script_extension = "py"
18+
cwd = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}"
19+
script = '''
20+
.cargo/scripts/cargo.py "$@"
21+
'''
22+
23+
[tasks.python]
24+
description = "UV + maturin"
25+
script_extension = "py"
26+
cwd = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}"
27+
script = '''
28+
.cargo/scripts/cargo.py "$@"
29+
'''
30+
31+
[tasks.example-run]
32+
description = "Run an example"
33+
cwd = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}"
34+
script = '''
35+
#!/usr/bin/env bash
36+
set -euo pipefail
37+
38+
echo "[example-run] --rs-- args: $*"
39+
40+
main() {
41+
# Skip a leading `--` forwarded by the alias
42+
if [[ "${1:-}" == "--" ]]; then shift; fi
43+
44+
name="${1:-}"
45+
if [[ -z "$name" ]]; then
46+
echo "usage: cargo ex <example-name|subpath>"
47+
exit 2
48+
fi
49+
50+
root="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY:-$(pwd)}"
51+
52+
manifest=""
53+
case "$name" in
54+
*/Cargo.toml)
55+
manifest="$([[ "$name" = /* ]] && echo "$name" || echo "$root/$name")"
56+
;;
57+
*/*)
58+
manifest="$root/examples/$name/Cargo.toml"
59+
;;
60+
*)
61+
if [[ -f "$root/examples/$name/Cargo.toml" ]]; then
62+
manifest="$root/examples/$name/Cargo.toml"
63+
else
64+
manifest="$(find "$root/examples" -maxdepth 3 -type f -name Cargo.toml -path "*/$name/Cargo.toml" | head -n1 || true)"
65+
fi
66+
;;
67+
esac
68+
69+
if [[ -z "${manifest:-}" || ! -f "$manifest" ]]; then
70+
echo "example not found for: $name"
71+
exit 2
72+
fi
73+
74+
echo "[run] cargo run --manifest-path $manifest --release"
75+
exec cargo run --manifest-path "$manifest" --release
76+
}
77+
78+
main "$@"
79+
'''

.cargo/config.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[env]
2+
RADIATE_PYTHON_VERSION = "3.12"
3+
UV_PROJECT_ENVIRONMENT = ".venv"
4+
5+
6+
[alias]
7+
ex-hello-world = "run --manifest-path examples/hello-world/Cargo.toml"
8+
ex-knapsack = "run --manifest-path examples/knapsack/Cargo.toml"
9+
ex-min-sum = "run --manifest-path examples/min-sum/Cargo.toml"
10+
ex-nqueens = "run --manifest-path examples/nqueens/Cargo.toml"
11+
ex-rastrigin = "run --manifest-path examples/rastrigin/Cargo.toml"
12+
ex-rosenbrock = "run --manifest-path examples/rosenbrock/Cargo.toml"
13+
ex-simple-nn = "run --manifest-path examples/simple-nn/Cargo.toml"
14+
ex-TSP = "run --manifest-path examples/TSP/Cargo.toml"
15+
ex-DTLZ = "run --manifest-path examples/DTLZ/Cargo.toml"
16+
17+
# graphs/*
18+
ex-graphs-iris = "run --manifest-path examples/graphs/iris-graph/Cargo.toml"
19+
ex-graphs-regression = "run --manifest-path examples/graphs/regression-graph/Cargo.toml"
20+
ex-graphs-simple-mem = "run --manifest-path examples/graphs/simple-memory-graph/Cargo.toml"
21+
ex-graphs-xor = "run --manifest-path examples/graphs/xor-graph/Cargo.toml"
22+
23+
# trees/*
24+
ex-trees-iris = "run --manifest-path examples/trees/iris-tree/Cargo.toml"
25+
ex-trees-regression = "run --manifest-path examples/trees/regression-tree/Cargo.toml"
26+
27+
# image-evo (top-level example with multiple files)
28+
ex-image-evo = "run --manifest-path examples/image-evo/Cargo.toml"
29+
30+
py = ["make", "--makefile", ".cargo/Makefile.toml", "cargo-py"]
31+
ex = ["make", "--makefile", ".cargo/Makefile.toml", "example-run", "--"]

.cargo/scripts/cargo.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import sys
4+
5+
from tools.action import (
6+
cmd_dev,
7+
cmd_wheel,
8+
cmd_test,
9+
cmd_ex,
10+
cmd_clean,
11+
)
12+
from tools.cli import normalize_args
13+
14+
15+
def build_parser() -> argparse.ArgumentParser:
16+
p = argparse.ArgumentParser(
17+
prog="cargo py",
18+
description="Radiate Python helper (uv + maturin + pytest + examples)",
19+
)
20+
21+
# shortcuts (mutually exclusive)
22+
g = p.add_mutually_exclusive_group()
23+
g.add_argument(
24+
"--dev",
25+
nargs="?",
26+
const="",
27+
metavar="PY",
28+
help="shortcut for dev (optional PY, e.g. 3.13t)",
29+
)
30+
g.add_argument(
31+
"--wheel",
32+
nargs="?",
33+
const="",
34+
metavar="PY",
35+
help="shortcut for wheel (optional PY)",
36+
)
37+
g.add_argument("--test", action="store_true", help="shortcut for test")
38+
g.add_argument("--ex", metavar="NAME", help="shortcut for example name/path")
39+
g.add_argument(
40+
"--clean", action="store_true", help="clean build artifacts before action"
41+
)
42+
43+
# subcommands (exact same behavior)
44+
sub = p.add_subparsers(dest="cmd")
45+
46+
s_dev = sub.add_parser("dev", help="maturin develop (editable)")
47+
s_dev.add_argument("--py", help="Python spec (e.g. 3.12, 3.13t)")
48+
s_dev.add_argument("rest", nargs=argparse.REMAINDER)
49+
50+
s_wheel = sub.add_parser("wheel", help="maturin build (wheel)")
51+
s_wheel.add_argument("--py", help="Python spec")
52+
s_wheel.add_argument("rest", nargs=argparse.REMAINDER)
53+
54+
s_test = sub.add_parser("test", help="pytest via uv")
55+
s_test.add_argument("rest", nargs=argparse.REMAINDER)
56+
57+
s_ex = sub.add_parser("ex", help="run example from py-radiate/examples")
58+
s_ex.add_argument("name", help="example name or path")
59+
s_ex.add_argument("rest", nargs=argparse.REMAINDER)
60+
61+
s_clean = sub.add_parser("clean", help="clean build artifacts")
62+
s_clean.add_argument("rest", nargs=argparse.REMAINDER)
63+
64+
return p
65+
66+
67+
def main(argv=None):
68+
# print(argv)
69+
# return
70+
argv = sys.argv[1:] if argv is None else argv
71+
parser = build_parser()
72+
args = parser.parse_args(argv)
73+
norm = normalize_args(args, argv)
74+
75+
if norm.cmd == "dev":
76+
return cmd_dev(norm.py, norm.rest or [])
77+
if norm.cmd == "wheel":
78+
return cmd_wheel(norm.py, norm.rest or [])
79+
if norm.cmd == "test":
80+
return cmd_test(norm.rest or [])
81+
if norm.cmd == "ex":
82+
return cmd_ex(norm.name, norm.rest or [])
83+
if norm.cmd == "clean":
84+
return cmd_clean(norm.rest or [])
85+
86+
parser.print_help()
87+
return 2
88+
89+
90+
if __name__ == "__main__":
91+
raise SystemExit(main())

.cargo/scripts/tools/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
__all__ = [
2+
'utils',
3+
'env',
4+
'action',
5+
'cli',
6+
]

.cargo/scripts/tools/action.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# action.py
2+
import os
3+
from pathlib import Path
4+
from .utils import info, die, run, require_tool, warn
5+
from .env import (
6+
DEFAULT_PY_SPEC,
7+
DEFAULT_VENV,
8+
resolve_python,
9+
is_free_threaded,
10+
configure_uv_env,
11+
)
12+
13+
REPO_ROOT = (
14+
Path(os.getenv("CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY"))
15+
or Path(__file__).parent.parent.parent
16+
)
17+
18+
if not REPO_ROOT.exists():
19+
raise SystemExit(f"REPO_ROOT does not exist: {REPO_ROOT}")
20+
elif not str(REPO_ROOT).split("/")[-1] == "radiate":
21+
raise SystemExit(f"REPO_ROOT does not look like the radiate repo: {REPO_ROOT}")
22+
23+
PY_DIR = REPO_ROOT / "py-radiate"
24+
PY_TESTS_DIR = PY_DIR / "tests"
25+
PY_EXAMPLES_DIR = PY_DIR / "examples"
26+
27+
28+
def do_clean() -> None:
29+
info("cleaning build artifacts")
30+
try:
31+
import shutil
32+
33+
venv = PY_DIR / ".venv"
34+
examples_venv = PY_EXAMPLES_DIR / ".venv"
35+
if venv.exists():
36+
shutil.rmtree(venv)
37+
if examples_venv.exists():
38+
shutil.rmtree(examples_venv)
39+
40+
# remove any .so files under radiate/
41+
for so_file in (PY_DIR / "radiate").glob("*.so"):
42+
try:
43+
os.remove(so_file)
44+
except Exception as e:
45+
warn(f"failed to remove {so_file}: {e}")
46+
47+
except Exception as e:
48+
die(f"failed to clean: {e}")
49+
50+
51+
def do_dev(py_spec: str, extra_args: list[str]) -> None:
52+
py_bin = resolve_python(py_spec)
53+
venv_dir = Path(DEFAULT_VENV)
54+
configure_uv_env(py_bin, venv_dir)
55+
56+
# choose features by mode
57+
# feats = ["--features", "gil"]
58+
# if is_free_threaded(py_bin):
59+
# feats = ["--no-default-features", "--features", "nogil"]
60+
61+
require_tool("uvx")
62+
cmd = [
63+
"uvx",
64+
"--python",
65+
py_bin,
66+
"maturin",
67+
"develop",
68+
"--release",
69+
# *feats,
70+
# *extra_args,
71+
]
72+
run(cmd, cwd=PY_DIR)
73+
74+
75+
def do_wheel(py_spec: str, extra_args: list[str]) -> None:
76+
py_bin = resolve_python(py_spec)
77+
venv_dir = Path(DEFAULT_VENV)
78+
configure_uv_env(py_bin, venv_dir)
79+
80+
feats = ["--features", "gil"]
81+
if is_free_threaded(py_bin):
82+
feats = ["--no-default-features", "--features", "nogil"]
83+
84+
require_tool("uvx")
85+
cmd = [
86+
"uvx",
87+
"--python",
88+
py_bin,
89+
"maturin",
90+
"build",
91+
"--release",
92+
*feats,
93+
*extra_args,
94+
]
95+
run(cmd, cwd=PY_DIR)
96+
97+
98+
def do_test(extra_args: list[str]) -> None:
99+
require_tool("uv")
100+
cmd = ["uv", "run", "pytest", "--benchmark-disable", *extra_args]
101+
run(cmd, cwd=PY_DIR)
102+
103+
104+
def resolve_example_py(arg: str) -> Path:
105+
p = Path(arg)
106+
if p.is_absolute():
107+
return p
108+
# allow bare names like "image.py" or "image"
109+
if "/" in arg or "\\" in arg:
110+
return (
111+
(PY_DIR / "examples" / arg).with_suffix(".py")
112+
if not arg.endswith(".py")
113+
else (PY_DIR / "examples" / arg)
114+
)
115+
return PY_DIR / "examples" / (arg if arg.endswith(".py") else f"{arg}.py")
116+
117+
118+
def do_example(example: str, extra_args: list[str]) -> None:
119+
target = resolve_example_py(example)
120+
if not target.exists():
121+
die(f"Python example not found: {target}")
122+
require_tool("uv")
123+
run(["uv", "sync", "--group", "dev"], cwd=PY_DIR)
124+
rel = target.relative_to(PY_DIR)
125+
run(["uv", "run", str(rel), *extra_args], cwd=PY_DIR)
126+
127+
128+
def cmd_dev(py, rest):
129+
os.chdir(PY_DIR)
130+
print(f"[dev] python={py}, rest={rest}")
131+
do_clean()
132+
do_dev(py or DEFAULT_PY_SPEC, rest)
133+
134+
135+
def cmd_wheel(py, rest):
136+
os.chdir(PY_DIR)
137+
print(f"[wheel] python={py}, rest={rest}")
138+
do_clean()
139+
do_wheel(py or DEFAULT_PY_SPEC, rest)
140+
141+
142+
def cmd_test(rest):
143+
os.chdir(PY_TESTS_DIR)
144+
print(f"[test] rest={rest}")
145+
do_test(rest)
146+
147+
148+
def cmd_ex(name, rest):
149+
print(f"[ex] name={name}, rest={rest}")
150+
do_example(name, rest)
151+
152+
153+
def cmd_clean(rest):
154+
os.chdir(PY_DIR)
155+
print(f"[clean] rest={rest}")
156+
do_clean()

0 commit comments

Comments
 (0)