Skip to content
Closed
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
28 changes: 19 additions & 9 deletions ebuild/build/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import logging
import subprocess
import shutil
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional
Expand Down Expand Up @@ -90,6 +91,19 @@ def _run_or_log(
return subprocess.run(cmd, check=check, cwd=cwd)


def ninja_command() -> list:
"""How to invoke ninja on this machine.

A real ninja on PATH is preferred; `python -m ninja` only works when the
PyPI `ninja` wheel is installed, so hardcoding it made a machine with
ninja properly installed fail with "No module named ninja".
"""
exe = shutil.which("ninja")
if exe:
return [exe]
return [sys.executable, "-m", "ninja"]


class BackendDispatcher:
"""Dispatch configure/build/clean to external build systems.

Expand Down Expand Up @@ -117,7 +131,7 @@ def configure(
dry_run: If True, log commands instead of executing them.

Raises:
ValueError: If the backend is not recognized.
RuntimeError: If the backend is not recognized.
"""
config = config or {}
self.build_dir.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -164,7 +178,7 @@ def build(
dry_run: If True, log commands instead of executing them.

Raises:
ValueError: If the backend is not recognized.
RuntimeError: If the backend is not recognized.
"""
config = config or {}

Expand Down Expand Up @@ -196,10 +210,8 @@ def build(

else:
raise RuntimeError(
f"BackendDispatcher cannot build backend '{backend}'. "
"Supported here: cargo, cmake, kbuild, make, meson. ebuild's own "
"ninja backend is generated and invoked by the CLI, not through "
"this dispatcher."
f"Unknown build backend '{backend}'. "
f"Supported backends: {', '.join(sorted(ALL_BACKENDS))}"
)

def clean(
Expand All @@ -215,9 +227,7 @@ def clean(
dry_run: If True, log commands instead of executing them.

Raises:
RuntimeError: If the backend is not recognized. configure() and
build() raise the same type for the same condition, so a
caller can guard all three with one ``except``.
RuntimeError: If the backend is not recognized.
"""
if backend == "cmake":
_run_or_log(
Expand Down
9 changes: 6 additions & 3 deletions ebuild/build/ninja_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class PackagePaths:


def _shared_flag() -> str:
"""The compiler flag that produces a shared object on this platform.
"""The compiler flag that builds a shared object on this platform.

macOS links dynamic libraries with -dynamiclib; ELF platforms use -shared.
"""
Expand Down Expand Up @@ -116,6 +116,9 @@ def _resolve_target_cflags(self, target) -> List[str]:
def _object_path(self, target, src: str) -> Path:
"""Object file for one source within one target.

Called from two places and defined in neither, so every ninja build
raised AttributeError before this existed.

Namespaced by target name: a source shared by two targets must produce
two distinct objects. Ninja rejects two edges writing the same output,
and the targets may compile it with different cflags.
Expand Down Expand Up @@ -216,8 +219,8 @@ def _write_ninja(self) -> None:
else:
# Shared libraries need the same -L/-l wiring executables
# get, which the rule preamble alone does not supply. The
# "build a shared object" flag itself lives in the
# link_shared rule, so it must not be repeated here.
# shared-object flag itself lives in the link_shared rule,
# so it must not be repeated here.
ldflags = list(target.ldflags)
libs = []
for pkg_name in target.uses:
Expand Down
2 changes: 1 addition & 1 deletion tests/ebuild/test_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_configure_unknown_raises(self, tmp_path):

def test_build_unknown_raises(self, tmp_path):
d = BackendDispatcher(tmp_path, tmp_path / "build")
with pytest.raises(RuntimeError, match="gradle"):
with pytest.raises(RuntimeError, match="Unknown build backend"):
d.build("gradle")

def test_clean_unknown_raises(self, tmp_path):
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/test_ninja_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_shared_library_gets_shared_flag(self):
shared_flag = "-dynamiclib" if sys.platform == "darwin" else "-shared"
self.assertIn(shared_flag, ninja)
# It must go through a compiler-driver rule, not the `ar` archiver.
# link_shared is that rule, and it carries the shared-object flag so
# link_shared is that rule, and it carries the shared-object flag, so
# the flag is never repeated in the edge's ldflags.
lib_line = next(line for line in ninja.splitlines() if "libmylib" in line and line.startswith("build"))
self.assertIn(": link_shared ", lib_line)
Expand All @@ -67,8 +67,8 @@ def test_static_library_unaffected(self):
self.assertIn(": ar_rule", ninja)

# The link_shared *rule* is always declared in the preamble, so the
# bare string "-shared" is present in every generated file. What must
# be absent is any build *edge* that uses it.
# bare string "-shared" appears in every generated file. What must be
# absent is any build *edge* that uses it.
edges = [line for line in ninja.splitlines() if line.startswith("build ")]
self.assertTrue(edges, "no build edges were generated")
for edge in edges:
Expand Down
Loading