Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 167 additions & 20 deletions hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ def __init__(self, hook: "BundleBuildHook") -> None:
self.hook = hook
self.metadata = self._get_metadata()

def run(self):
self.build_all()
self.extract_libs()

def _get_metadata(self):
p = subprocess.Popen(
[
Expand All @@ -37,34 +33,41 @@ def _get_metadata(self):
sys.exit(1)
return json.loads(stdout)

def build_all(self):
self.hook.app.display_mini_header("Building cargo workspace")
def _run_build(self, args: list[str], env: dict[str, str] | None = None):
p = subprocess.Popen(
[
"cargo",
"build",
"--workspace",
"--release",
"--locked",
],
args,
cwd=self.hook.root,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=env,
)
assert p.stdout is not None
for line in p.stdout:
line = line.decode("utf-8").strip()
if "Finished" in line:
self.hook.app.display_info(line)
elif "error" in line:
self.hook.app.display_error(line)
line_str = line.decode("utf-8").strip()
if "finished" in line_str.lower():
self.hook.app.display_info(line_str)
elif "error" in line_str.lower():
self.hook.app.display_error(line_str)
else:
self.hook.app.display_info(line)
self.hook.app.display_info(line_str)
p.wait()
if p.returncode != 0:
self.hook.app.display_error(
f"Cargo build failed with return code {p.returncode}"
)
sys.exit(1)

def build_all(self):
self.hook.app.display_mini_header("Building cargo workspace")
self._run_build(
[
"cargo",
"build",
"--workspace",
"--release",
"--locked",
]
)
self.hook.app.display_success("Cargo build completed successfully")

def extract_libs(self):
Expand Down Expand Up @@ -113,6 +116,146 @@ def extract_libs(self):
shutil.copy(lib_path, destination)


class CargoAddonBuild:
def __init__(self, hook: "BundleBuildHook") -> None:
self.hook = hook
self.manifests = self._discover_manifests()

def _discover_manifests(self) -> list[Path]:
manifests = []
for manifest in Path(self.hook.root).glob("selene-ext/*/*/Cargo.toml"):
metadata = self._get_metadata(manifest)
package = metadata["packages"][0]
selene = (package.get("metadata") or {}).get("selene", {})
if selene.get("build_after_helios_interface"):
manifests.append(manifest)
return manifests

def _get_release_dir(self, manifest: Path) -> Path:
target = os.environ.get("CARGO_BUILD_TARGET")
target_dir = manifest.parent / "target"
if target:
return target_dir / target / "release"
return target_dir / "release"

def _get_metadata(self, manifest: Path) -> dict:
p = subprocess.Popen(
[
"cargo",
"metadata",
"--no-deps",
"--manifest-path",
str(manifest),
],
cwd=self.hook.root,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = p.communicate()
if p.returncode != 0:
self.hook.app.display_error(
f"Cargo metadata command failed with return code {p.returncode}"
)
self.hook.app.display_error(stderr.decode("utf-8"))
sys.exit(1)
return json.loads(stdout)

def build_all(self):
if not self.manifests:
return
env = os.environ.copy()
env["SELENE_HELIOS_QIS_LIB_DIR"] = str(
Path(self.hook.root)
/ "selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/_dist/lib"
)
for manifest in self.manifests:
self.hook.app.display_mini_header(
f"Building cargo addon {manifest.parent.name}"
)
p = subprocess.Popen(
[
"cargo",
"build",
"--manifest-path",
str(manifest),
"--release",
"--locked",
],
cwd=self.hook.root,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=env,
)
assert p.stdout is not None
for line in p.stdout:
line_str = line.decode("utf-8").strip()
if "finished" in line_str.lower():
self.hook.app.display_info(line_str)
elif "error" in line_str.lower():
self.hook.app.display_error(line_str)
else:
self.hook.app.display_info(line_str)
p.wait()
if p.returncode != 0:
self.hook.app.display_error(
f"Cargo addon build failed with return code {p.returncode}"
)
sys.exit(1)
self.hook.app.display_success(
f"Cargo addon build completed successfully: {manifest.parent.name}"
)

def extract_libs(self):
for manifest in self.manifests:
metadata = self._get_metadata(manifest)
release_dir = self._get_release_dir(manifest)
assert release_dir.exists()
for package in metadata["packages"]:
for target in package["targets"]:
if "cdylib" not in target["kind"]:
continue
self.hook.app.display_info(
f"Found addon cdylib target: {target['name']}"
)
lib_name = target["name"]
lib_filenames = {
"darwin": [f"lib{lib_name}.dylib"],
"linux": [f"lib{lib_name}.so"],
"win32": [f"{lib_name}.dll", f"lib{lib_name}.dll.a"],
}[sys.platform]
assert all(
(release_dir / file).exists() for file in lib_filenames
), (
f"Compiled library for {lib_name} not found in {release_dir}. "
f"Expected to find {','.join(lib_filenames)}."
)

cargo_toml_path = Path(package["manifest_path"])
python_path = cargo_toml_path.parent / "python"
subdirs = filter(lambda x: x.is_dir(), python_path.iterdir())
subdirs = filter(
lambda x: x.name != "test" and x.name != "tests", subdirs
)
subdirs = filter(lambda x: "pycache" not in x.name, subdirs)
subdirs = filter(lambda x: not x.name.endswith("egg-info"), subdirs)
subdirs = filter(lambda x: not x.name.startswith("."), subdirs)
subdirs_list = list(subdirs)
assert len(subdirs_list) == 1, (
f"Multiple python directories found in {python_path} - "
" hatch_build.py can't tell where the build artifacts should go."
)
subdir = subdirs_list[0]
destination = subdir / "_dist/lib"
destination.mkdir(parents=True, exist_ok=True)
for filename in lib_filenames:
lib_path = release_dir / filename
if lib_path.exists():
self.hook.app.display_info(
f"Copying {lib_path} to {destination}"
)
shutil.copy(lib_path, destination)


class BundleBuildHook(BuildHookInterface):
def get_cargo_release_dir(self) -> Path:
target = os.environ.get("CARGO_BUILD_TARGET")
Expand All @@ -124,9 +267,13 @@ def get_cargo_release_dir(self) -> Path:

def initialize(self, version: str, build_data: dict) -> None:
cargo_runner = CargoWorkspaceBuild(self)
cargo_runner.run()
cargo_runner.build_all()
cargo_runner.extract_libs()
self.build_selene_c_interface()
self.build_helios_qis()
addon_runner = CargoAddonBuild(self)
addon_runner.build_all()
addon_runner.extract_libs()

packages = [Path("selene-sim/python/selene_sim")]
for topic_dir in Path("selene-ext").iterdir():
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ packages = [
"selene-ext/simulators/quantum-replay/python/selene_quantum_replay_plugin",
"selene-ext/simulators/quest/python/selene_quest_plugin",
"selene-ext/simulators/stim/python/selene_stim_plugin",
"selene-ext/utilities/argreader/python/selene_argreader_plugin",
]

[tool.uv]
Expand Down Expand Up @@ -142,6 +143,8 @@ known_first_party = [
"selene_quantum_replay_plugin",
"selene_quest_plugin",
"selene_stim_plugin",
# Utilities
"selene_argreader_plugin",
]
extend_exclude = [
"target",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .selene import (
SeleneExecutableKind,
SeleneObjectFileKind,
SeleneObjectToSeleneExecutable,
SeleneObjectToSeleneExecutableStep,
register_selene_builtins,
)

Expand All @@ -25,9 +25,7 @@
LLVMIRStringToLLVMIRFileStep,
HeliosLLVMIRFileToHeliosObjectFileStep,
HeliosLLVMBitcodeFileToHeliosObjectFileStep,
HeliosObjectFileToSeleneObjectFileStep_Linux,
HeliosObjectFileToSeleneExecutableStep_Windows,
HeliosObjectFileToSeleneExecutableStep_Darwin,
HeliosObjectFileToSeleneExecutableStep,
register_helios_builtins,
)

Expand Down Expand Up @@ -112,7 +110,6 @@ def register_builtins(planner: BuildPlanner):
__all__ = [
"SeleneExecutableKind",
"SeleneObjectFileKind",
"SeleneObjectToSeleneExecutable",
"HeliosLLVMIRStringKind",
"HeliosLLVMIRFileKind",
"HeliosLLVMBitcodeStringKind",
Expand All @@ -122,8 +119,7 @@ def register_builtins(planner: BuildPlanner):
"LLVMIRStringToLLVMIRFileStep",
"HeliosLLVMIRFileToHeliosObjectFileStep",
"HeliosLLVMBitcodeFileToHeliosObjectFileStep",
"HeliosObjectFileToSeleneObjectFileStep_Linux",
"HeliosObjectFileToSeleneExecutableStep_Windows",
"HeliosObjectFileToSeleneExecutableStep_Darwin",
"HeliosObjectFileToSeleneExecutableStep",
"SeleneObjectToSeleneExecutableStep",
"register_builtins",
] + additional_exports
Loading
Loading