From 3ff768b6ec0dfa2524f82fe185e5c97275ef92ff Mon Sep 17 00:00:00 2001 From: Enrique Saurez Date: Thu, 4 Jun 2026 09:12:12 -0700 Subject: [PATCH] [build] E: Build libxslt.so and libexslt.so Produce position-independent libxslt.so and libexslt.so alongside the existing static libxslt.a / libexslt.a. The new .so files form an explicit DT_NEEDED chain: libxslt.so -> DT_NEEDED libxml2.so libexslt.so -> DT_NEEDED libxslt.so, DT_NEEDED libxml2.so so consumers (lxml C extensions, future XSLT-using user binaries) dlopen a single .so and the loader resolves the rest of the chain transitively. libposix/libc/libm symbols are left UND and bound at dlopen time against the host executable's .dynsym, matching the extension-module model used elsewhere on Nanvix. Concretely: * `--enable-static --disable-shared` stays in configure (libtool's shared-library detection has no rules for i686-nanvix); `-fPIC` is added to CFLAGS so the same .o files are usable for both archives. * New `.libs/libxslt.so` and `.libs/libexslt.so` targets link the .so files manually via `-shared -fPIC -nostdlib`, setting DT_SONAME=libxslt.so / DT_SONAME=libexslt.so and emitting the cross-library DT_NEEDED edges by linking through `-L. -lxml2` (resolved against the freshly-built libxml2.so). * `test-functional` now sanity-checks each .so: presence, minimum size, DT_SONAME, DT_NEEDED chain, and that the public entry points (xsltApplyStylesheet, exsltRegisterAll) appear in .dynsym. * `package` / `verify-package` ship the static archives plus both .so files. Build-time dependency: this PR's shared link requires a libxml2.so release artifact to satisfy the DT_NEEDED edge. The companion libxml2 PR adding libxml2.so should land first; once it has a released artifact, this PR's CI can be re-pointed at that release in `.nanvix/nanvix.toml`. Runtime dependency: only useful once the loader changes in nanvix/nanvix#2473 (DT_RUNPATH + .init_array) and nanvix/nanvix#2478 (diamond DT_NEEDED handling) ship -- libexslt forms a diamond at runtime via the two paths into libxml2. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .nanvix/Makefile.nanvix | 105 ++++++++++++++++++++++++++-------------- .nanvix/z.py | 6 ++- 2 files changed, 73 insertions(+), 38 deletions(-) diff --git a/.nanvix/Makefile.nanvix b/.nanvix/Makefile.nanvix index e97d69e2..aa594df8 100644 --- a/.nanvix/Makefile.nanvix +++ b/.nanvix/Makefile.nanvix @@ -6,11 +6,20 @@ # NANVIX_TOOLCHAIN= [target] # # Targets: -# all Build libxslt.a, libexslt.a, and the test ELF, then stage via install -# install Stage built artifacts into LIB_OUT/INCLUDE_OUT/TEST_OUT +# all Build libxslt.{a,so}, libexslt.{a,so}, and the test ELF # test Verify functional-test prerequisites (build artifacts). # The runtime execution is driven by z.py on the host. # clean Remove build artifacts +# +# Dependencies: +# The shared-library build requires: +# * The Nanvix dynamic loader to honour `.init_array` constructors and +# DT_NEEDED resolution chains (esaurez/nanvix#27). +# * `libxml2.so` to be present in the buildroot, shipped by the +# companion esaurez/libxml2 release that ships libxml2.so. +# libxslt.so is linked with DT_NEEDED libxml2.so, and libexslt.so is +# linked with DT_NEEDED libxslt.so libxml2.so, so the loader pulls +# the lower-level libraries automatically at dlopen time. # =========================================================================== # Global Variables @@ -18,37 +27,27 @@ # Some assumptions: # 1. This makefile is expected to be invoked via z.py. -# 2. Build goals (all, install) require the cross-toolchain at -# $(NANVIX_TOOLCHAIN); other goals (test-*, clean) are pure host-side. +# 2. Build goals require the cross-toolchain at $(NANVIX_TOOLCHAIN); other +# goals (test-*, clean) are pure host-side. # 3. Build ALWAYS runs before test and release. -# 4. Release packaging is handled by the inherited ZScript.release(), which -# packages release_dir() (= .nanvix/out/release/) into dist_dir(); -# the install target below stages artifacts into that tree. +# 4. Release packaging is handled in z.py (pure-Python tarfile); no +# package/verify-package targets here. .DEFAULT_GOAL=all EXE = .elf STATICLIB_XSLT := libxslt/.libs/libxslt.a STATICLIB_EXSLT := libexslt/.libs/libexslt.a +SHAREDLIB_XSLT := libxslt/.libs/libxslt.so +SHAREDLIB_EXSLT := libexslt/.libs/libexslt.so TEST_SRC := .nanvix/test/test_libxslt.c TEST_ELF := test_libxslt$(EXE) -_NANVIX_DOCKER_BUILD_GOALS := all install $(STATICLIB_XSLT) $(STATICLIB_EXSLT) $(TEST_ELF) +_NANVIX_DOCKER_BUILD_GOALS := all $(STATICLIB_XSLT) $(STATICLIB_EXSLT) $(SHAREDLIB_XSLT) $(SHAREDLIB_EXSLT) $(TEST_ELF) _NANVIX_GOALS := $(or $(MAKECMDGOALS),$(.DEFAULT_GOAL)) # Ensure required variables are defined. -_REQUIRED := PLATFORM \ - PROCESS_MODE \ - MEMORY_SIZE \ - NANVIX_HOME \ - NANVIX_BUILDROOT \ - NANVIX_TOOLCHAIN \ - NANVIX_ROOT \ - OUT_DIR \ - DIST_DIR \ - LIB_OUT \ - INCLUDE_OUT \ - TEST_OUT +_REQUIRED := PLATFORM PROCESS_MODE MEMORY_SIZE NANVIX_HOME NANVIX_BUILDROOT NANVIX_TOOLCHAIN ifneq ($(filter-out clean,$(_NANVIX_GOALS)),) $(foreach v,$(_REQUIRED),\ $(if $($v),,$(error Required variable $v not set))\ @@ -79,8 +78,13 @@ endif # Build Targets # =========================================================================== -all: $(STATICLIB_XSLT) $(STATICLIB_EXSLT) $(TEST_ELF) install +all: $(STATICLIB_XSLT) $(STATICLIB_EXSLT) $(SHAREDLIB_XSLT) $(SHAREDLIB_EXSLT) $(TEST_ELF) +# Build the static archives with -fPIC so the same objects can be +# linked into position-independent .so files below. autotools' +# libtool shared-library detection does not know about i686-nanvix, +# so we keep --disable-shared and link the .so files ourselves from +# the .a archives. $(STATICLIB_XSLT) $(STATICLIB_EXSLT): sh -c '\ export PATH="$(NANVIX_TOOLCHAIN)/bin:$$PATH" && \ @@ -90,15 +94,46 @@ $(STATICLIB_XSLT) $(STATICLIB_EXSLT): --enable-static --disable-shared --disable-maintainer-mode \ --without-python --without-crypto \ --without-debugger --without-plugins \ + --with-pic \ --with-libxml-prefix="$(BUILDROOT_PATH)" \ --with-libxml-include-prefix="$(BUILDROOT_PATH)/include/libxml2" \ --with-libxml-libs-prefix="$(BUILDROOT_PATH)/lib" \ - CFLAGS="-I$(BUILDROOT_PATH)/include -I$(BUILDROOT_PATH)/include/libxml2" \ + CFLAGS="-I$(BUILDROOT_PATH)/include -I$(BUILDROOT_PATH)/include/libxml2 -fPIC" \ LDFLAGS="-L$(BUILDROOT_PATH)/lib" \ LIBS="-lxml2 -lz" && \ make -C libxslt -j$(NPROC) && \ make -C libexslt -j$(NPROC)' +# libxslt.so: links against libxml2.so (DT_NEEDED libxml2.so), with +# libxslt's own .o files embedded via --whole-archive. libxml2 itself +# is NOT embedded — the loader will dlopen libxml2.so transitively +# when libxslt.so is loaded, so the libxml2 code is shared across +# every consumer. libposix/libc/libm symbols stay unresolved and +# bind at dlopen time against the host executable's `.dynsym`. +$(SHAREDLIB_XSLT): $(STATICLIB_XSLT) + sh -c '\ + export PATH="$(NANVIX_TOOLCHAIN)/bin:$$PATH" && \ + i686-nanvix-gcc -shared -fPIC -nostdlib \ + -Wl,-soname,libxslt.so -Wl,-z,noexecstack \ + -L$(BUILDROOT_PATH)/lib \ + -Wl,--whole-archive $(STATICLIB_XSLT) -Wl,--no-whole-archive \ + -lxml2 \ + -o $(SHAREDLIB_XSLT)' + +# libexslt.so: links against libxslt.so (DT_NEEDED libxslt.so), which +# transitively brings in libxml2.so. Only libexslt's own .o files are +# embedded. libposix/libc/libm symbols stay unresolved and bind at +# dlopen time against the host executable. +$(SHAREDLIB_EXSLT): $(STATICLIB_EXSLT) $(SHAREDLIB_XSLT) + sh -c '\ + export PATH="$(NANVIX_TOOLCHAIN)/bin:$$PATH" && \ + i686-nanvix-gcc -shared -fPIC -nostdlib \ + -Wl,-soname,libexslt.so -Wl,-z,noexecstack \ + -L$(BUILDROOT_PATH)/lib -Llibxslt/.libs \ + -Wl,--whole-archive $(STATICLIB_EXSLT) -Wl,--no-whole-archive \ + -lxslt -lxml2 \ + -o $(SHAREDLIB_EXSLT)' + $(TEST_ELF): $(TEST_SRC) @test -f $(STATICLIB_XSLT) || { echo " FAIL: $(STATICLIB_XSLT) not found; run 'build' first"; exit 1; } @test -f $(STATICLIB_EXSLT) || { echo " FAIL: $(STATICLIB_EXSLT) not found; run 'build' first"; exit 1; } @@ -118,18 +153,6 @@ $(TEST_ELF): $(TEST_SRC) -Wl,--end-group \ -o $@ -# =========================================================================== -# Install (stage artifacts into release/test output tree) -# =========================================================================== - -install: $(STATICLIB_XSLT) $(STATICLIB_EXSLT) $(TEST_ELF) - @mkdir -p $(LIB_OUT) $(INCLUDE_OUT)/libxslt $(INCLUDE_OUT)/libexslt $(TEST_OUT) - cp -f $(STATICLIB_XSLT) $(LIB_OUT)/ - cp -f $(STATICLIB_EXSLT) $(LIB_OUT)/ - cp -f libxslt/*.h $(INCLUDE_OUT)/libxslt/ - cp -f libexslt/*.h $(INCLUDE_OUT)/libexslt/ - cp -f $(TEST_ELF) $(TEST_OUT)/ - # =========================================================================== # Test Targets # =========================================================================== @@ -138,7 +161,17 @@ test: @echo "=== libxslt functional tests ===" @test -f $(STATICLIB_XSLT) || { echo " FAIL: $(STATICLIB_XSLT) not found"; exit 1; } @test -f $(STATICLIB_EXSLT) || { echo " FAIL: $(STATICLIB_EXSLT) not found"; exit 1; } + @test -f $(SHAREDLIB_XSLT) || { echo " FAIL: $(SHAREDLIB_XSLT) not found"; exit 1; } + @test -f $(SHAREDLIB_EXSLT) || { echo " FAIL: $(SHAREDLIB_EXSLT) not found"; exit 1; } @test -s $(TEST_ELF) || { echo " FAIL: $(TEST_ELF) missing or empty"; exit 1; } + @$(NANVIX_TOOLCHAIN)/bin/i686-nanvix-readelf -d $(SHAREDLIB_XSLT) | grep -q 'SONAME.*libxslt.so' \ + || { echo " FAIL: SONAME=libxslt.so not set on $(SHAREDLIB_XSLT)"; exit 1; } + @$(NANVIX_TOOLCHAIN)/bin/i686-nanvix-readelf -d $(SHAREDLIB_EXSLT) | grep -q 'SONAME.*libexslt.so' \ + || { echo " FAIL: SONAME=libexslt.so not set on $(SHAREDLIB_EXSLT)"; exit 1; } + @$(NANVIX_TOOLCHAIN)/bin/i686-nanvix-nm -D $(SHAREDLIB_XSLT) | grep -q 'T xsltApplyStylesheet' \ + || { echo " FAIL: xsltApplyStylesheet missing from $(SHAREDLIB_XSLT) .dynsym"; exit 1; } + @$(NANVIX_TOOLCHAIN)/bin/i686-nanvix-nm -D $(SHAREDLIB_EXSLT) | grep -q 'T exsltRegisterAll' \ + || { echo " FAIL: exsltRegisterAll missing from $(SHAREDLIB_EXSLT) .dynsym"; exit 1; } @echo " OK: build artifacts present (runtime execution is driven by z.py on the host)" @echo "=== libxslt functional test prerequisites PASSED ===" @@ -149,6 +182,6 @@ test: clean: -$(MAKE) clean 2>/dev/null || true rm -f $(TEST_ELF) Makefile - rm -rf $(OUT_DIR) dist/ libxslt/.libs/ libexslt/.libs/ + rm -rf dist/ libxslt/.libs/ libexslt/.libs/ -.PHONY: all install clean test +.PHONY: all clean test diff --git a/.nanvix/z.py b/.nanvix/z.py index f72c448e..5fb69da5 100644 --- a/.nanvix/z.py +++ b/.nanvix/z.py @@ -5,7 +5,7 @@ Usage: ./z setup # Download Nanvix sysroot - ./z build # Cross-compile libxslt.a and libexslt.a + ./z build # Cross-compile libxslt.a/.so and libexslt.a/.so ./z test # Run test suite (smoke + integration + functional) ./z release # Package release tarball ./z clean # Remove build artifacts @@ -87,7 +87,9 @@ def _staged_output_files(self) -> list[str]: root = repo_root() return [ str((lib_out() / "libxslt.a").relative_to(root)), + str((lib_out() / "libxslt.so").relative_to(root)), str((lib_out() / "libexslt.a").relative_to(root)), + str((lib_out() / "libexslt.so").relative_to(root)), str((test_out() / "test_libxslt.elf").relative_to(root)), ] @@ -139,7 +141,7 @@ def translate(p: Path): return args def build(self) -> None: - """Cross-compile libxslt.a and libexslt.a for Nanvix.""" + """Cross-compile libxslt.a/.so and libexslt.a/.so for Nanvix.""" run(*self._make_args("all"), cwd=repo_root(), docker=self.docker) def test(self) -> None: