scripts/build_modules.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""Build disjoint official wheels from one source revision.
Run with --output dist/modules. Optional --module selects individual wheels;
the default builds core, all modules, and the CLI compatibility metapackage.
This prepares local artifacts only; it never uploads or installs packages.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import zipfile
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from build_metadata import optional_dependencies, project_version
from build_support import owner
def build(output: Path, selected: list[str]) -> list[Path]:
modules = {
path.stem: json.loads(path.read_text())
for path in (ROOT / "deeptutor/modules/manifests").glob("*.json")
}
version = project_version(ROOT)
names = selected or [*modules, "cli"]
output.mkdir(parents=True, exist_ok=True)
files = [
path
for base in ("deeptutor", "deeptutor_cli", "deeptutor_web")
for path in (ROOT / base).rglob("*")
if path.is_file()
and "__pycache__" not in path.parts
and "tests" not in path.parts
and path.suffix not in {".pyc", ".pyo"}
]
ownership = {path: owner(path.relative_to(ROOT).as_posix()) for path in files}
built = []
for name in names:
if name not in {*modules, "cli"}:
raise ValueError(f"Unknown module: {name}")
with tempfile.TemporaryDirectory(prefix=f"deeptutor-build-{name}-") as directory:
stage = Path(directory)
module = modules.get(name)
package_name = module["distribution"] if module else f"deeptutor-{name}"
if module:
for source in files:
if ownership[source] != name:
continue
dest = stage / source.relative_to(ROOT)
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, dest)
dependencies = list(module["requirements"]) if module else []
if name != "core":
dependencies += [f"deeptutor=={version}"]
dependencies += [
f"{modules[dep]['distribution']}=={version}"
for dep in (module["requires"] if module else [])
]
config = f"""[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = {json.dumps(package_name)}
version = {json.dumps(version)}
description = {json.dumps("DeepTutor " + name + " module")}
requires-python = ">=3.11,<3.15"
dependencies = {json.dumps(dependencies)}
[tool.setuptools.packages.find]
where = ["."]
include = ["deeptutor*", "deeptutor_cli*", "deeptutor_web*"]
namespaces = true
[tool.setuptools.package-data]
"deeptutor_web" = ["**/*", ".next/**/*"]
"*" = ["*.json", "*.yaml", "*.yml", "*.md", "*.sh", "*.j2", "*.jinja", "*.ts", "*.tsx", "*.mjs", "*.js", "*.css", "*.html", "*.svg", "*.png", "*.woff2"]
"""
if name == "core":
config += '\n[project.scripts]\ndeeptutor = "deeptutor_cli.main:main"\n'
config += "\n[project.optional-dependencies]\n"
for key, values in optional_dependencies(ROOT).items():
config += f"{json.dumps(key)} = {json.dumps(values)}\n"
for group, entries in (module or {}).get("entry_points", {}).items():
config += f"\n[project.entry-points.{json.dumps(group)}]\n"
for key, value in entries.items():
config += f"{json.dumps(key)} = {json.dumps(value)}\n"
(stage / "MANIFEST.in").write_text(
"recursive-include deeptutor_web *\nrecursive-include deeptutor_web/.next *\n"
)
(stage / "pyproject.toml").write_text(config)
result = subprocess.run(
[
sys.executable,
"-m",
"pip",
"wheel",
"--no-deps",
"--no-build-isolation",
"--wheel-dir",
str(output.resolve()),
str(stage),
],
text=True,
capture_output=True,
)
if result.returncode:
raise RuntimeError(result.stdout + result.stderr)
wheel = output / f"{package_name.replace('-', '_')}-{version}-py3-none-any.whl"
if not wheel.is_file():
raise RuntimeError(f"Expected wheel missing: {wheel}")
built.append(wheel)
print(wheel.name, flush=True)
# One installed file has one owner; namespace parents need no duplicate init.
owners = {}
for wheel in built:
with zipfile.ZipFile(wheel) as archive:
for path in archive.namelist():
if ".dist-info/" in path or path.endswith("/"):
continue
if path in owners:
raise RuntimeError(f"Wheel overlap: {path} in {owners[path]} and {wheel.name}")
owners[path] = wheel.name
return built
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=ROOT / "dist/modules")
parser.add_argument("--module", action="append", default=[])
args = parser.parse_args()
build(args.output, args.module)