Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
`cjson` (v1.7.18), `nanopb` (v0.4.9.1), `lvgl` (v9.2.2), `tinyusb` (v0.18.0), and `unity` (v2.6.1).

### Fixed
- **Ninja shared-library sources compile as position-independent code.**
Shared-library targets now default to `-fPIC`, while preserving an explicit
PIC policy supplied by the target or toolchain
(`ebuild/build/ninja_backend.py`).
- **`ebuild test` now finds Windows test binaries.** The Ninja edge for a
native `type: test` target already carried the platform suffix
(`_exe_suffix()` names it `<name>.exe` on Windows), but `ebuild test`
Expand Down
22 changes: 17 additions & 5 deletions ebuild/build/ninja_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ class PackagePaths:
libraries: List[str] = field(default_factory=list)


# Flags that already request position-independent code. If one of these is
# present (or explicitly disabled with -fno-*) we must not add -fPIC again.
_PIC_FLAGS = {"-fPIC", "-fpic", "-fPIE", "-fpie", "-fno-pic", "-fno-PIC",
"-fno-pie", "-fno-PIE"}
# Flags that explicitly control PIC generation. PIE is intentionally excluded:
# its output is suitable for executables, not shared libraries.
_PIC_FLAGS = {"-fPIC", "-fpic", "-fno-pic", "-fno-PIC"}
_PIE_FLAGS = {"-fPIE", "-fpie", "-fno-pie", "-fno-PIE"}
_POSITION_INDEPENDENCE_FLAGS = _PIC_FLAGS | _PIE_FLAGS


def _exe_suffix() -> str:
Expand Down Expand Up @@ -146,7 +147,9 @@ def _resolve_target_cflags(self, target) -> List[str]:
"""Resolve all cflags for a target (toolchain + target + packages).

Combines toolchain flags, target-specific flags, include paths,
defines, and package include directories into a single list.
defines, and package include directories into a single list. Shared
library sources default to position-independent code unless the target
or toolchain explicitly selects a PIC policy.

Args:
target: A TargetConfig with cflags, includes, defines, and uses.
Expand All @@ -166,6 +169,15 @@ def _resolve_target_cflags(self, target) -> List[str]:
for inc_dir in pkg.include_dirs:
cflags.append(f"-I{inc_dir}")

if target.target_type == "shared_library":
effective_flag = None
for flag in reversed(cflags):
if flag in _POSITION_INDEPENDENCE_FLAGS:
effective_flag = flag
break
if effective_flag not in _PIC_FLAGS:
cflags.append("-fPIC")

return cflags

def _object_path(self, target, src: str) -> Path:
Expand Down
64 changes: 64 additions & 0 deletions tests/ebuild/test_ninja_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,70 @@ def test_shared_library_links_with_the_platform_shared_flag(tmp_path):
assert ": link_shared " in ninja_file


def test_shared_library_sources_default_to_fpic(tmp_path):
config = _shared_library_config(tmp_path)
toolchain = SimpleNamespace(cc="cc", cxx="c++", ar="ar")

NinjaBackend(config, tmp_path / "build", toolchain).generate()

ninja_file = (tmp_path / "build" / "build.ninja").read_text(encoding="utf-8")
compile_commands = json.loads(
(tmp_path / "build" / "compile_commands.json").read_text(encoding="utf-8")
)

assert "-fPIC" in ninja_file
assert "-fPIC" in compile_commands[0]["command"].split()


def test_shared_library_respects_explicit_pic_policy(tmp_path):
config = _shared_library_config(tmp_path, target_cflags=["-fno-pic"])
toolchain = SimpleNamespace(cc="cc", cxx="c++", ar="ar")

NinjaBackend(config, tmp_path / "build", toolchain).generate()

compile_commands = json.loads(
(tmp_path / "build" / "compile_commands.json").read_text(encoding="utf-8")
)
flags = compile_commands[0]["command"].split()

assert "-fno-pic" in flags
assert "-fPIC" not in flags


@pytest.mark.parametrize("pie_flag", ["-fPIE", "-fpie", "-fno-PIE", "-fno-pie"])
def test_shared_library_does_not_treat_pie_as_pic(tmp_path, pie_flag):
config = _shared_library_config(tmp_path)
toolchain = SimpleNamespace(cc="cc", cxx="c++", ar="ar", cflags=[pie_flag])

NinjaBackend(config, tmp_path / "build", toolchain).generate()

compile_commands = json.loads(
(tmp_path / "build" / "compile_commands.json").read_text(encoding="utf-8")
)
flags = compile_commands[0]["command"].split()

assert pie_flag in flags
assert "-fPIC" in flags


@pytest.mark.parametrize("pie_flag", ["-fPIE", "-fpie", "-fno-PIE", "-fno-pie"])
def test_shared_library_respects_the_last_position_independence_flag(
tmp_path, pie_flag
):
config = _shared_library_config(tmp_path, target_cflags=[pie_flag])
toolchain = SimpleNamespace(cc="cc", cxx="c++", ar="ar", cflags=["-fPIC"])

NinjaBackend(config, tmp_path / "build", toolchain).generate()

compile_commands = json.loads(
(tmp_path / "build" / "compile_commands.json").read_text(encoding="utf-8")
)
flags = compile_commands[0]["command"].split()
pic_and_pie_flags = [flag for flag in flags if flag in {"-fPIC", pie_flag}]

assert pic_and_pie_flags == ["-fPIC", pie_flag, "-fPIC"]


def test_cc_rule_emits_and_consumes_a_depfile(tmp_path):
"""The compile rule must generate a depfile and tell Ninja to read it.

Expand Down