diff --git a/.github/workflows/nix-ci.yml b/.github/workflows/nix-ci.yml index 63d22798e29c..0917894dd061 100644 --- a/.github/workflows/nix-ci.yml +++ b/.github/workflows/nix-ci.yml @@ -7,13 +7,13 @@ on: - synchronize push: branches: [stable-ghc-9.14, stable-master] - # Tag pushes matching wasm32-wasi-* trigger the Cross: WASM jobs to - # additionally upload their bindist tarballs to the matching GitHub - # Release, populating the stable-haskell ghcup channel. - # multi-* trigger the Cross: MULTI job for the same reason (separate - # tag namespace, separate channel YAML). + # Tag pushes matching multi-* trigger the Cross: MULTI job to + # additionally upload its multi-target bindist tarball to the + # matching GitHub Release, populating the stable-haskell + # ghcup-multi-target-0.1.0.yaml channel. The single multi-target + # bindist replaces the previously-separate wasm32-wasi-* and + # JS standalone channels — both targets ship via multi-* now. tags: - - 'wasm32-wasi-*' - 'multi-*' workflow_dispatch: @@ -560,597 +560,21 @@ jobs: fi # --------------------------------------------------------------------------- - # Cross: JS backend (aarch64-darwin, uses static dist) - # --------------------------------------------------------------------------- - cross-js: - name: "Cross: JS / aarch64-darwin" - # Cross-builds only need the dist artifact from build. They run in parallel - # with tests — each VM is independent (separate nix daemon, separate disk). - needs: [build] - if: ${{ !cancelled() && contains(fromJSON('["success", "failure"]'), needs.build.result) }} - runs-on: [self-hosted, nix] - continue-on-error: true - - env: - EMSDK_VERSION: "3.1.74" - - steps: - - name: Clean workspace - run: | - echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH" - echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH" - echo "/usr/local/bin" >> "$GITHUB_PATH" - - echo "CABAL_DIR=$GITHUB_WORKSPACE/_build/cabal-dir" >> "$GITHUB_ENV" - - echo "=== Disk usage before cleanup ===" - df -h / || true - df -h "$GITHUB_WORKSPACE" || true - rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.??* || true - rm -rf ~/.cabal/store ~/.cabal-devx/store ~/.cabal-devx/packages || true - sudo rm -f /usr/local/bin/devx 2>/dev/null || true - export PATH="/nix/var/nix/profiles/default/bin:$PATH" - nix-collect-garbage -d 2>/dev/null || true - nix-store --gc 2>/dev/null || true - echo "=== Disk usage after cleanup ===" - df -h / || true - - - uses: actions/checkout@v4 - with: - submodules: "recursive" - fetch-depth: 1 - - # Minimize source tree BEFORE devx import to give devx more headroom. - - name: Minimize source tree - run: | - GIT_COMMIT_ID=$(git rev-parse HEAD) - echo "GIT_COMMIT_ID=$GIT_COMMIT_ID" >> "$GITHUB_ENV" - echo "Captured GIT_COMMIT_ID=$GIT_COMMIT_ID" - rm -rf .git - find . -name .git -type d -exec rm -rf {} + 2>/dev/null || true - rm -rf testsuite docs || true - echo "=== Disk after minimize ===" - df -h / || true - - - name: Ensure devx prerequisites - run: | - export PATH="$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH" - command -v zstd || nix-env -iA nixpkgs.zstd - sudo mkdir -p /usr/local/bin - - - uses: input-output-hk/actions/devx@latest - with: - platform: aarch64-darwin - compiler-nix-name: 'ghc98' - minimal: true - ghc: true - - - name: Download dist - uses: actions/download-artifact@v4 - with: - name: aarch64-darwin-dynamic0-dist - path: _build/dist - - - name: Set up stage2 from dist - run: | - chmod +x _build/dist/bin/* - mkdir -p _build/stage2/bin _build/stage2/lib - for exe in _build/dist/bin/*; do - ln -sf "$(pwd)/$exe" "_build/stage2/bin/$(basename $exe)" - done - if [[ -f _build/dist/lib/settings ]]; then - cp -rfp _build/dist/lib/settings _build/stage2/lib/ - fi - - - name: Update hackage - shell: devx {0} - run: | - set -eo pipefail - export CABAL_DIR="$GITHUB_WORKSPACE/_build/cabal-dir" - mkdir -p "$CABAL_DIR" - _build/dist/bin/cabal update - - - name: Free disk for cross build - run: | - rm -rf "${CABAL_DIR:-~/.cabal-devx}/packages" "${CABAL_DIR:-~/.cabal-devx}/logs" "${CABAL_DIR:-~/.cabal-devx}/store" || true - rm -rf ~/.cabal-devx/packages ~/.cabal-devx/logs ~/.cabal-devx/store || true - rm -rf ~/.cabal/store || true - echo "=== Disk before emscripten install ===" - df -h / || true - df -h /Volumes/WorkSpace || true - - - name: Install emscripten - shell: devx {0} - run: | - set -eux - export TMPDIR="$GITHUB_WORKSPACE/_build/tmp" - mkdir -p "$TMPDIR" - git clone --depth 1 --branch ${{ env.EMSDK_VERSION }} https://github.com/emscripten-core/emsdk.git - cd emsdk - ./emsdk install ${{ env.EMSDK_VERSION }} - ./emsdk activate ${{ env.EMSDK_VERSION }} - - - name: Build JS cross libraries - shell: devx {0} - run: | - set -eux - export CABAL_DIR="$GITHUB_WORKSPACE/_build/cabal-dir" - export TMPDIR="$GITHUB_WORKSPACE/_build/tmp" - mkdir -p "$TMPDIR" - source emsdk/emsdk_env.sh - make DIST_BUILD=1 \ - CABAL=$PWD/_build/dist/bin/cabal \ - GHC_TOOLCHAIN_BIN=$PWD/_build/dist/bin/ghc-toolchain-bin \ - DERIVE_CONSTANTS_BIN=$PWD/_build/dist/bin/deriveConstants \ - GENAPPLY_BIN=$PWD/_build/dist/bin/genapply \ - HAPPY_TEMPLATE_DIR=$PWD/_build/dist/share/happy-lib/data \ - stage3-javascript-unknown-ghcjs - - - name: Smoke test - shell: devx {0} - run: | - set -eo pipefail - source emsdk/emsdk_env.sh - echo 'main = putStrLn "Hello from JS backend"' > /tmp/hello.hs - # JS backend creates a .jsexe directory; the entry point is all.js - _build/dist/bin/javascript-unknown-ghcjs-ghc /tmp/hello.hs -o /tmp/hello - node /tmp/hello.jsexe/all.js - - - name: Upload JS cross artifacts - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: aarch64-darwin-cross-js - retention-days: 1 - path: _build/dist/lib/targets/javascript-unknown-ghcjs - - # --------------------------------------------------------------------------- - # Cross: WASM backend — matrixed across all build hosts so each one - # produces a host-specific bindist tarball that can be shipped through - # the stable-haskell ghcup channel. - # --------------------------------------------------------------------------- - cross-wasm: - name: "Cross: WASM / ${{ matrix.plat }}" - # Cross-builds only need the dist artifact from build. They run in parallel - # with tests — each VM/runner is independent (separate nix daemon, etc.). - needs: [build] - if: ${{ !cancelled() && contains(fromJSON('["success", "failure"]'), needs.build.result) }} - runs-on: ${{ fromJSON(matrix.runner) }} - continue-on-error: true - - strategy: - fail-fast: false - matrix: - include: - - { plat: aarch64-darwin, devx-plat: aarch64-darwin, runner: '["self-hosted", "nix"]' } - - { plat: x86_64-linux, devx-plat: x86_64-linux, runner: '"ubuntu-latest"' } - - { plat: aarch64-linux, devx-plat: aarch64-linux, runner: '"ubuntu-24.04-arm"' } - - steps: - - name: Clean workspace - run: | - echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH" - echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH" - echo "/usr/local/bin" >> "$GITHUB_PATH" - - echo "CABAL_DIR=$GITHUB_WORKSPACE/_build/cabal-dir" >> "$GITHUB_ENV" - - echo "=== Disk usage before cleanup ===" - df -h / || true - df -h "$GITHUB_WORKSPACE" || true - rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.??* || true - rm -rf ~/.cabal/store ~/.cabal-devx/store ~/.cabal-devx/packages || true - rm -rf ~/.ghc-wasm || true - sudo rm -f /usr/local/bin/devx 2>/dev/null || true - export PATH="/nix/var/nix/profiles/default/bin:$PATH" - nix-collect-garbage -d 2>/dev/null || true - nix-store --gc 2>/dev/null || true - echo "=== Disk usage after cleanup ===" - df -h / || true - - - uses: actions/checkout@v4 - with: - submodules: "recursive" - fetch-depth: 1 - - # Minimize source tree BEFORE devx import — same strategy as cross-js. - - name: Minimize source tree - run: | - GIT_COMMIT_ID=$(git rev-parse HEAD) - echo "GIT_COMMIT_ID=$GIT_COMMIT_ID" >> "$GITHUB_ENV" - echo "Captured GIT_COMMIT_ID=$GIT_COMMIT_ID" - rm -rf .git - find . -name .git -type d -exec rm -rf {} + 2>/dev/null || true - rm -rf testsuite docs || true - echo "=== Disk after minimize ===" - df -h / || true - - - name: Ensure devx prerequisites - run: | - export PATH="$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH" - command -v zstd || nix-env -iA nixpkgs.zstd - sudo mkdir -p /usr/local/bin - - - uses: input-output-hk/actions/devx@latest - with: - platform: ${{ matrix.devx-plat }} - compiler-nix-name: 'ghc98' - minimal: true - ghc: true - - - name: Download dist - uses: actions/download-artifact@v4 - with: - # dynamic1 stage2 (with .dyn_hi for native base/filepath/process/etc.) - # is needed for two reasons: - # 1. stage3 build-side: when happy-lib / alex etc. compile with - # shared:True (via DYNAMIC=1 settings), cabal asks this build - # compiler for native Prelude.dyn_hi. - # 2. wasm bindist runtime: bin/wasm32-unknown-wasi-ghc is a - # Makefile symlink to bin/ghc which `tar -czhf` dereferences; - # the dereferenced binary is dyn-linked against libHS*.so - # files from THIS stage2. The wasm tar rule (Makefile) also - # copies lib/$(HOST_PLATFORM)/ from THIS stage2 alongside, - # so the binary's @rpath resolves at end-user install time. - name: ${{ matrix.plat }}-dynamic1-dist - path: _build/dist - - - name: Set up stage2 from dist - run: | - chmod +x _build/dist/bin/* - mkdir -p _build/stage2/bin _build/stage2/lib - for exe in _build/dist/bin/*; do - ln -sf "$(pwd)/$exe" "_build/stage2/bin/$(basename $exe)" - done - if [[ -f _build/dist/lib/settings ]]; then - cp -rfp _build/dist/lib/settings _build/stage2/lib/ - fi - - - name: Update hackage - shell: devx {0} - run: | - set -eo pipefail - export CABAL_DIR="$GITHUB_WORKSPACE/_build/cabal-dir" - mkdir -p "$CABAL_DIR" - _build/dist/bin/cabal update - - - name: Free disk for cross build - run: | - rm -rf "${CABAL_DIR:-~/.cabal-devx}/packages" "${CABAL_DIR:-~/.cabal-devx}/logs" "${CABAL_DIR:-~/.cabal-devx}/store" || true - rm -rf ~/.cabal-devx/packages ~/.cabal-devx/logs ~/.cabal-devx/store || true - rm -rf ~/.cabal/store || true - echo "=== Disk before wasi-sdk install ===" - df -h / || true - df -h /Volumes/WorkSpace || true - - - name: Install wasi-sdk - shell: devx {0} - run: | - set -eux - export TMPDIR="$GITHUB_WORKSPACE/_build/tmp" - mkdir -p "$TMPDIR" - # ghc-wasm-meta's bootstrap.sh needs jq + unzip; the smoke test below - # also needs node + wasmtime + wasm-objdump. The devx shell scrubs the - # system PATH so we install or expose them per host kind. - # - # Pick the installer based on uname -s — devx scrubs /usr/bin on both - # macOS and Linux, so `command -v apt-get` would falsely return no on - # both. Also CRUCIAL: on darwin we must NOT add /usr/bin to PATH, or - # BSD `find` (no `-mindepth`) takes precedence over GNU find used in - # Makefile:476 / 485 and the dist-copy step breaks. - case "$(uname -s)" in - Linux) - export PATH="$HOME/.nix-profile/bin:/usr/local/bin:/usr/bin:/bin:$PATH" - sudo apt-get update -qq - sudo apt-get install -y --no-install-recommends \ - jq unzip zstd wabt - # post-link.mjs uses import.meta.filename (added Node 20.11+), - # so we need Node >= 20.11. Ubuntu noble's apt nodejs is - # 18.19.1, and runner-preinstalled Node 20.x at - # /opt/hostedtoolcache isn't visible inside devx for the smoke - # test. Install Node 22 from NodeSource unconditionally so we - # land /usr/bin/node — devx scrubs random tool caches but - # /usr/bin survives via the smoke step's PATH append. - curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - - sudo apt-get install -y --no-install-recommends nodejs - if ! command -v wasmtime >/dev/null 2>&1; then - curl -sSL https://wasmtime.dev/install.sh | bash - export PATH="$HOME/.wasmtime/bin:$PATH" - echo "$HOME/.wasmtime/bin" >> "$GITHUB_PATH" - fi - ;; - Darwin) - export PATH="$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH" - for tool in jq unzip zstd node wasmtime; do - if ! command -v "$tool" >/dev/null 2>&1; then - pkg="$tool" - [ "$tool" = "node" ] && pkg="nodejs" - echo "Installing $pkg via nix-env..." - /nix/var/nix/profiles/default/bin/nix-env -iA "nixpkgs.$pkg" - fi - done - if ! command -v wasm-objdump >/dev/null 2>&1; then - echo "Installing wabt (wasm-objdump) via nix-env..." - /nix/var/nix/profiles/default/bin/nix-env -iA nixpkgs.wabt - fi - ;; - esac - curl -fsSL https://gitlab.haskell.org/ghc/ghc-wasm-meta/-/raw/master/bootstrap.sh | \ - FLAVOUR=9.12 PREFIX=$HOME/.ghc-wasm sh - - - name: Build WASM cross libraries - shell: devx {0} - run: | - set -eux - export CABAL_DIR="$GITHUB_WORKSPACE/_build/cabal-dir" - export TMPDIR="$GITHUB_WORKSPACE/_build/tmp" - mkdir -p "$TMPDIR" - # Create wasm32-unknown-wasi-* symlinks for LLVM tools in wasi-sdk so they - # don't clash with devx's llvm-ar/llvm-nm/llvm-ranlib (which have - # different capabilities). Also symlink clang/clang++ from the - # wasm32-wasi- name bootstrap actually creates to the canonical autoconf - # triple wasm32-unknown-wasi- that ghc-toolchain-bin searches for — - # without these our build fails at "wasm32-unknown-wasi-clang not found - # in search path". - WASI_BIN="$HOME/.ghc-wasm/wasi-sdk/bin" - for tool in ar nm ranlib strip; do - ln -sf "$WASI_BIN/llvm-$tool" "$WASI_BIN/wasm32-unknown-wasi-$tool" - done - for tool in clang clang++; do - ln -sf "$WASI_BIN/wasm32-wasi-$tool" "$WASI_BIN/wasm32-unknown-wasi-$tool" - done - # wasi-sdk at END of PATH so native tools (cc, clang) resolve to the - # host compiler. WASM tools (wasm32-unknown-wasi-clang, etc.) have unique - # prefixed names that don't clash. - export PATH=$PATH:$WASI_BIN - # DYNAMIC=1 makes ./configure pass --enable-dynamic, which expands - # cabal.project.stage3.settings to: - # package * { shared:True; executable-dynamic:True } - # constraints: rts +dynamic - # …so the stage3 wasm build produces both .hi + .dyn_hi files AND - # ships .so libraries. Without it the bindist is static-only, and - # end-user TH-heavy apps (miso, jsaddle, aeson, …) hit - # error: [GHC-47808] Failed to load dynamic interface file for X - # because cabal sets `shared: True` for wasm32 user packages but - # the system base/ghc-internal/etc. ship no .dyn_hi to link against. - make DYNAMIC=1 DIST_BUILD=1 \ - CABAL=$PWD/_build/dist/bin/cabal \ - GHC_TOOLCHAIN_BIN=$PWD/_build/dist/bin/ghc-toolchain-bin \ - DERIVE_CONSTANTS_BIN=$PWD/_build/dist/bin/deriveConstants \ - GENAPPLY_BIN=$PWD/_build/dist/bin/genapply \ - HAPPY_TEMPLATE_DIR=$PWD/_build/dist/share/happy-lib/data \ - stage3-wasm32-unknown-wasi - - - name: Smoke test - if: success() - shell: devx {0} - run: | - set -eo pipefail - # devx scrubs the user-installed tool locations. Add them back after - # devx's $PATH so devx wins where there's overlap, but the per-OS - # installer outputs are still findable: - # Linux: apt installs node / wasm-objdump to /usr/bin; - # curl wasmtime installer drops into ~/.wasmtime/bin - # Darwin: nix-env -iA puts everything (wasmtime, wabt, node, …) - # into ~/.nix-profile/bin - # /usr/bin is not added on Darwin because it would shadow nix - # coreutils with BSD versions (no -mindepth on find, etc.). - case "$(uname -s)" in - Linux) export PATH="$PATH:$HOME/.wasmtime/bin:$HOME/.ghc-wasm/wasi-sdk/bin:/usr/bin" ;; - Darwin) export PATH="$PATH:$HOME/.nix-profile/bin:$HOME/.ghc-wasm/wasi-sdk/bin" ;; - esac - echo 'main = putStrLn "Hello from WASM backend"' > /tmp/hello.hs - _build/dist/bin/wasm32-unknown-wasi-ghc /tmp/hello.hs -o /tmp/hello.wasm - - # Verify the .wasm binary was produced - ls -lh /tmp/hello.wasm - file /tmp/hello.wasm - - # Diagnostic: inspect WASM imports to determine execution strategy. - # If ghc_wasm_jsffi imports are present, we need post-link.mjs + Node.js. - # If not, wasmtime can run it directly as a pure WASI module. - echo "=== WASM import sections ===" - wasm-objdump -x /tmp/hello.wasm | head -60 || true - echo "=== Checking for ghc_wasm_jsffi imports ===" - - if wasm-objdump -x /tmp/hello.wasm 2>/dev/null | grep -q 'ghc_wasm_jsffi'; then - echo "JSFFI imports detected → using post-link.mjs + Node.js" - - # GHC WASM executables with ghc_wasm_jsffi custom sections require - # JavaScript FFI glue at instantiation time. The post-link.mjs tool - # (installed in libdir, maintained upstream at utils/jsffi/post-link.mjs) - # parses these sections and generates a JavaScript ESM module providing - # the ghc_wasm_jsffi imports. - # - # Workflow (from docs/users_guide/wasm.rst §"JSFFI"): - # 1. Compile: wasm32-unknown-wasi-ghc hello.hs -o hello.wasm - # 2. Post-link: $(ghc --print-libdir)/post-link.mjs -i hello.wasm -o hello.mjs - # 3. Run: node runner.mjs - WASM_LIBDIR=_build/dist/lib/targets/wasm32-unknown-wasi/lib - - # Generate JavaScript FFI glue from WASM custom sections - node "$WASM_LIBDIR/post-link.mjs" -i /tmp/hello.wasm -o /tmp/hello.mjs - - # Instantiate WASM module with JSFFI + WASI imports, then run main. - # This mirrors the knot-tying pattern from docs/users_guide/wasm.rst: - # __exports starts empty, WASM is instantiated with JSFFI imports - # (which capture __exports by reference), then instance exports are - # assigned back into __exports before calling wasi.start(). - node --input-type=module -e ' - import { readFile } from "node:fs/promises"; - import { WASI } from "node:wasi"; - const mod = await WebAssembly.compile(await readFile("/tmp/hello.wasm")); - const jsffi = (await import("/tmp/hello.mjs")).default; - const wasi = new WASI({ version: "preview1", args: ["hello.wasm"] }); - let __exports = {}; - const instance = await WebAssembly.instantiate(mod, { - ghc_wasm_jsffi: jsffi(__exports), - wasi_snapshot_preview1: wasi.wasiImport, - }); - Object.assign(__exports, instance.exports); - wasi.start(instance); - ' - else - echo "No JSFFI imports → running with wasmtime (pure WASI module)" - - # Per docs/users_guide/wasm.rst: "the same toolchain still generates - # self-contained wasm32-unknown-wasi modules by default" — these can be run - # directly with any WASI-compatible runtime. - wasmtime run /tmp/hello.wasm - fi - - # Package the full ghcup-shippable bindist tarball — same recipe the - # Makefile uses locally (tar czhf -h dereferences the wasm-prefixed - # symlinks into real files, includes bin/ + lib/ + configure + Makefile - # + relocate.sh). Renamed with the host triple to disambiguate. - # - # NOTE: the tarball Makefile target depends on `stage3-$(plat)` which is - # .PHONY, so it re-runs ghc-toolchain-bin. That tool needs - # wasm32-unknown-wasi-clang on PATH — the symlinks were created by the - # previous step but PATH doesn't persist across CI steps, so we re-add - # the wasi-sdk bin dir here. - - name: Package bindist tarball - shell: devx {0} - if: ${{ !cancelled() }} - run: | - set -eux - export CABAL_DIR="$GITHUB_WORKSPACE/_build/cabal-dir" - export TMPDIR="$GITHUB_WORKSPACE/_build/tmp" - # Keep devx's $PATH first so `make` resolves to devx's GNU Make (4.4+) - # not Ubuntu's /usr/bin/make (4.3 — no $(let), silently returns empty - # which corrupts LIB_NAME_GLOB and breaks the package.conf copy phase). - export PATH="$PATH:$HOME/.ghc-wasm/wasi-sdk/bin" - rm -f _build/dist/ghc-wasm32-unknown-wasi.tar.gz - # The same five DIST_BUILD vars the previous "Build WASM cross - # libraries" step uses — stage3 phony deps invoke configure which - # otherwise can't find deriveConstants/genapply/happy templates. - # DYNAMIC=1 mirrors the Build step so configure re-runs with the - # same --enable-dynamic flag (stage3-$plat is PHONY → re-runs). - make DYNAMIC=1 DIST_BUILD=1 \ - CABAL=$PWD/_build/dist/bin/cabal \ - GHC_TOOLCHAIN_BIN=$PWD/_build/dist/bin/ghc-toolchain-bin \ - DERIVE_CONSTANTS_BIN=$PWD/_build/dist/bin/deriveConstants \ - GENAPPLY_BIN=$PWD/_build/dist/bin/genapply \ - HAPPY_TEMPLATE_DIR=$PWD/_build/dist/share/happy-lib/data \ - _build/dist/ghc-wasm32-unknown-wasi.tar.gz - # rename with the host triple so we ship one tarball per host - cp _build/dist/ghc-wasm32-unknown-wasi.tar.gz \ - _build/dist/ghc-wasm32-unknown-wasi-${{ matrix.plat }}.tar.gz - ls -lh _build/dist/ghc-wasm32-unknown-wasi-${{ matrix.plat }}.tar.gz - echo "SHA256:" - shasum -a 256 _build/dist/ghc-wasm32-unknown-wasi-${{ matrix.plat }}.tar.gz - - # The wasm cross-compiler is built inside `devx` (a nix-shell), so all - # ELF binaries' interpreter (`PT_INTERP`) is pinned to a `/nix/store/.../ - # ld-linux-*.so.*` path. On any non-nix Linux system that path doesn't - # exist, and the kernel fails to load the binary with ENOENT — manifesting - # as `cannot execute: required file not found`. The same broad class of - # problem `fixup-nix-deps` solves on Darwin (Mach-O dyld refs). - # - # Patchelf the interpreter back to the canonical system ld-linux path and - # drop the RPATH so dynamic libs resolve through ldconfig (libgmp10, - # libffi8, libc, libm — all stock on any modern Linux distro). - # - # Skipped on Darwin: Mach-O doesn't pin an interpreter path the way ELF - # does, so darwin bindists are portable as-is. - - name: Normalize ELF interpreters for portability (Linux only) - if: ${{ !cancelled() && contains(matrix.plat, 'linux') }} - shell: devx {0} - run: | - set -eux - # patchelf is in apt on ubuntu — keep PATH outside devx for sudo. - export PATH="$PATH:/usr/bin:/usr/sbin" - if ! command -v patchelf >/dev/null 2>&1; then - sudo apt-get install -y --no-install-recommends patchelf - fi - case "${{ matrix.plat }}" in - x86_64-linux) INTERP=/lib64/ld-linux-x86-64.so.2; HOST_DIR=x86_64-unknown-linux ;; - aarch64-linux) INTERP=/lib/ld-linux-aarch64.so.1; HOST_DIR=aarch64-unknown-linux ;; - *) echo "::error::unexpected matrix.plat=${{ matrix.plat }}"; exit 1 ;; - esac - - TGZ=_build/dist/ghc-wasm32-unknown-wasi-${{ matrix.plat }}.tar.gz - STAGE="$(mktemp -d)" - tar -C "$STAGE" -xzf "$TGZ" - - echo "═══ Patchelfing binaries to interpreter $INTERP ═══" - # Scan everything executable; patchelf only on dynamically-linked ELFs - # whose interpreter still points into /nix/store. Also set an - # @ORIGIN-relative rpath so the dyn-linked binary can locate the - # libHS*.so files we ship in lib/$HOST_DIR/ (mirrors the - # @executable_path/../lib/$(HOST_PLATFORM) rpath stage2 sets on - # Darwin via install_name_tool). - patched=0 - while IFS= read -r bin; do - if file -L "$bin" 2>/dev/null \ - | grep -q "ELF.*dynamically linked.*interpreter /nix/store"; then - echo " patching: ${bin#$STAGE/}" - patchelf --set-interpreter "$INTERP" "$bin" - patchelf --remove-rpath "$bin" - patchelf --force-rpath --set-rpath "\$ORIGIN/../lib/$HOST_DIR" "$bin" - patched=$((patched + 1)) - fi - done < <(find "$STAGE" -type f -executable) - echo "═══ Patched $patched binaries ═══" - test "$patched" -gt 0 || { echo "::error::no ELF binaries patched — something is wrong"; exit 1; } - - # Also fix rpath on the shipped host .so files (lib/$HOST_DIR/*.so): - # they reference each other (e.g. libHSghc.so needs libHSrts.so) - # and use $ORIGIN-relative rpath at stage2 build time. patchelf - # may have left a nix-store rpath in place; replace with the - # local-$ORIGIN form so cross-library lookups work from the - # bindist install location. - echo "═══ Setting rpath on lib/$HOST_DIR/*.so ═══" - so_patched=0 - for so in "$STAGE"/lib/"$HOST_DIR"/*.so; do - [ -f "$so" ] || continue - patchelf --force-rpath --set-rpath "\$ORIGIN" "$so" 2>/dev/null || true - so_patched=$((so_patched + 1)) - done - echo "═══ Set rpath on $so_patched shared libs ═══" - - # Repack — tar czhf was used to dereference symlinks originally; the - # staging dir is already real files (tar xzf preserves that), so plain - # tar czf is fine here. Keep the same filename for downstream uploads. - rm "$TGZ" - tar -C "$STAGE" -czf "$TGZ" . - ls -lh "$TGZ" - echo "SHA256 after patchelf:" - shasum -a 256 "$TGZ" - - - name: Upload WASM cross artifacts - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: ${{ matrix.plat }}-cross-wasm - retention-days: 30 - path: _build/dist/ghc-wasm32-unknown-wasi-${{ matrix.plat }}.tar.gz - - # On a tag push matching wasm32-wasi-*, also upload the bindist - # tarball directly to that GitHub Release so the ghcup channel can - # point at it. Other triggers (PR pushes, branch pushes) only produce - # workflow artifacts which is fine for review purposes. - - name: Upload bindist to release (on tag push) - if: ${{ startsWith(github.ref, 'refs/tags/wasm32-wasi-') }} - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - files: _build/dist/ghc-wasm32-unknown-wasi-${{ matrix.plat }}.tar.gz - fail_on_unmatched_files: true - - # --------------------------------------------------------------------------- - # Cross: MULTI — multi-target bindist combining native + wasm + JS. + # Cross: MULTI — the only Cross job. Builds the multi-target bindist + # combining native + wasm32-unknown-wasi + javascript-unknown-ghcjs in + # one tarball (argv[0] dispatched at runtime). Replaces the previously- + # separate Cross: WASM + Cross: JS jobs — those produced standalone + # bindists that we no longer ship; the multi-target bindist is the + # single end-user-facing artifact and covers both targets. # - # Reuses the Cross: WASM machinery (dynamic1 stage2 download, devx shell, - # wasi-sdk + Node install, patchelf for ELF interpreter + $ORIGIN rpath) - # and adds emscripten install for the JS target. The combined Makefile - # rule `_build/dist/ghc-multi-target.tar.gz` depends on both - # stage3-wasm32-unknown-wasi AND stage3-javascript-unknown-ghcjs, so - # this job builds BOTH cross trees + the multi-target tarball in one - # CI cycle. + # Per-host: dynamic1 stage2 download, devx shell, wasi-sdk + Node + # install, emscripten install, then `make stage3-wasm32-unknown-wasi + # stage3-javascript-unknown-ghcjs` + the combined `ghc-multi-target. + # tar.gz` Makefile rule. patchelf adjusts ELF interpreter + $ORIGIN + # rpath on the linux runners. # - # `continue-on-error: true` matches Cross: WASM — failures are - # informative but don't block the main pipeline. + # `continue-on-error: true` — failures are informative but don't + # block the main pipeline. # --------------------------------------------------------------------------- cross-multi: name: "Cross: MULTI / ${{ matrix.plat }}" @@ -1160,10 +584,8 @@ jobs: continue-on-error: true env: - # Pinned same as Cross: JS — emsdk's git tag, used in the - # `Install emscripten` step's `git clone --branch ${{ env.EMSDK_VERSION }}`. - # Workflow-level env doesn't exist on this workflow; each job - # needing EMSDK_VERSION declares it itself. + # emsdk's git tag — used by the `Install emscripten` step's + # `git clone --branch ${{ env.EMSDK_VERSION }}`. EMSDK_VERSION: "3.1.74" strategy: @@ -1225,7 +647,7 @@ jobs: - name: Download dist uses: actions/download-artifact@v4 with: - # dynamic1 stage2 (same rationale as Cross: WASM): + # dynamic1 stage2: # 1. happy-lib / alex etc. need native Prelude.dyn_hi at build-side # 2. the dyn-linked native binary becomes bin/ghc + bin/-ghc # in the multi-target bindist and needs lib/$(HOST_PLATFORM) @@ -1259,7 +681,7 @@ jobs: df -h / || true # Install wasi-sdk (wasm-target C toolchain) + Node 22 + wasmtime, same - # logic as Cross: WASM since this job builds the wasm half of the + # logic since this job builds the wasm half of the # multi-target bindist. - name: Install wasi-sdk + Node 22 shell: devx {0} @@ -1290,7 +712,7 @@ jobs: FLAVOUR=9.12 PREFIX=$HOME/.ghc-wasm sh # Install emscripten (JS-target C toolchain). EMSDK_VERSION is set in - # the workflow env (same as Cross: JS). + # the workflow env. - name: Install emscripten shell: devx {0} run: | @@ -1311,7 +733,7 @@ jobs: # Source emscripten env (provides emcc on PATH) source emsdk/emsdk_env.sh # Symlink wasi-sdk's wasm32-wasi-* tools as wasm32-unknown-wasi-* - # (same as Cross: WASM — autoconf canonical triple bridging). + #. WASI_BIN="$HOME/.ghc-wasm/wasi-sdk/bin" for tool in ar nm ranlib strip; do ln -sf "$WASI_BIN/llvm-$tool" "$WASI_BIN/wasm32-unknown-wasi-$tool" @@ -1320,7 +742,7 @@ jobs: ln -sf "$WASI_BIN/wasm32-wasi-$tool" "$WASI_BIN/wasm32-unknown-wasi-$tool" done export PATH=$PATH:$WASI_BIN - # DYNAMIC=1 for the same reason as Cross: WASM — both wasm and JS + # DYNAMIC=1 for both wasm and JS # targets need .dyn_hi for end-user TH builds (miso, aeson, ...). make DYNAMIC=1 DIST_BUILD=1 \ CABAL=$PWD/_build/dist/bin/cabal \ @@ -1329,7 +751,7 @@ jobs: GENAPPLY_BIN=$PWD/_build/dist/bin/genapply \ HAPPY_TEMPLATE_DIR=$PWD/_build/dist/share/happy-lib/data \ _build/dist/ghc-multi-target.tar.gz - # Rename to add platform suffix (matches Cross: WASM scheme). + # Rename to add platform suffix. cp _build/dist/ghc-multi-target.tar.gz \ _build/dist/ghc-multi-target-${{ matrix.plat }}.tar.gz ls -lh _build/dist/ghc-multi-target-${{ matrix.plat }}.tar.gz @@ -1373,7 +795,7 @@ jobs: echo "═══ Patched $patched binaries ═══" test "$patched" -gt 0 || { echo "::error::no ELF binaries patched"; exit 1; } - # Fix rpath on the shipped host .so files (same as Cross: WASM). + # Fix rpath on the shipped host .so files. echo "═══ Setting rpath on lib/$HOST_DIR/*.so ═══" so_patched=0 for so in "$STAGE"/lib/"$HOST_DIR"/*.so; do @@ -1448,7 +870,7 @@ jobs: path: _build/dist/ghc-multi-target-${{ matrix.plat }}.tar.gz # On a tag push matching multi-*, upload to that GitHub Release. - # (Separate tag namespace from wasm32-wasi-* so the two channels + # (Separate tag namespace from the (no-longer-shipped) wasm32-wasi-* so the channels # stay independent.) - name: Upload bindist to release (on tag push) if: ${{ startsWith(github.ref, 'refs/tags/multi-') }} diff --git a/Makefile b/Makefile index e24f99eb626a..e069f75b46ac 100644 --- a/Makefile +++ b/Makefile @@ -617,6 +617,12 @@ $(STAGE1_STAMP): $(CONFIGURE_SCRIPTS) $(CONFIGURED_FILES) cabal.project.stage1 c ifeq ($(DYNAMIC),1) $(SED) -i -e 's/"RTS ways","/"RTS ways","dyn debug_dyn thr_dyn thr_debug_dyn /' $(STORE_DIR)/host/$(HOST_PLATFORM)/lib/settings endif + @# Inject the four per-target dials into the native settings file + @# too — same keys as the stage3 cross targets (see the lengthy + @# comment above $(TARGET_DIR)/lib/settings injection). The native + @# target's dynamic state tracks our DYNAMIC=0/1 build flag; prof + @# is always NO because stage2 isn't built -prof. + $(SED) -i -e 's/\]$$/,("target is dynamic","$(if $(filter 1,$(DYNAMIC)),YES,NO)"),("target ships dynamic libraries","$(if $(filter 1,$(DYNAMIC)),YES,NO)"),("target is profiled","NO"),("target ships profiling libraries","NO")]/' $(STORE_DIR)/host/$(HOST_PLATFORM)/lib/settings $(call LOG,Creating packagedb in $(STORE_DIR)/host/$(HOST_PLATFORM)/lib/package.conf.d) @rm -rf $(STORE_DIR)/host/$(HOST_PLATFORM)/lib/package.conf.d @@ -919,6 +925,17 @@ STAGE3_javascript-unknown-ghcjs_NM = emnm STAGE3_javascript-unknown-ghcjs_RANLIB = emranlib STAGE3_javascript-unknown-ghcjs_STRIP = emstrip STAGE3_javascript-unknown-ghcjs_GHC_TOOLCHAIN_ARGS = $(GHC_TOOLCHAIN_ARGS) --disable-tables-next-to-code +# JS target overrides: NO across the board. +# * dyn: iserv runs vanilla (no dlopen in the JS runtime), lib tree +# ships no .dyn_hi (Path C doesn't apply to JS — see PR #187). +# * prof: stage2 isn't built -prof, no .p_hi shipped. +# All four dials NO → `ghc --info` for the JS target reports +# `GHC Dynamic: NO`, `GHC Profiled: NO`, `Support dynamic-too: NO`. +# End-user cabal-install correctly skips library-{dynamic,profiling}. +STAGE3_javascript-unknown-ghcjs_TARGET_IS_DYNAMIC = NO +STAGE3_javascript-unknown-ghcjs_TARGET_SHIPS_DYN_LIBS = NO +STAGE3_javascript-unknown-ghcjs_TARGET_IS_PROFILED = NO +STAGE3_javascript-unknown-ghcjs_TARGET_SHIPS_PROF_LIBS = NO STAGE3_wasm32-unknown-wasi_CC = wasm32-unknown-wasi-clang STAGE3_wasm32-unknown-wasi_CC_OPTS = -fno-strict-aliasing -Wno-error=int-conversion -Oz -msimd128 -mnontrapping-fptoint -msign-ext -mbulk-memory -mmutable-globals -mmultivalue -mreference-types @@ -1007,6 +1024,42 @@ ifeq ($(DYNAMIC),1) $(SED) -i -e 's/"RTS ways","/"RTS ways","dyn /' $$(TARGET_DIR)/lib/settings endif + @# Inject the per-target dials that drive `ghc --info`'s + @# `GHC Dynamic` and `GHC Profiled` values (cabal-install reads + @# these to decide whether to enable library-dynamic / + @# library-profiling by default): + @# + @# target is dynamic — GHC capable of -dynamic / + @# -dynamic-too output + @# target ships dynamic libraries — lib tree has .dyn_hi / .so + @# target is profiled — GHC capable of -prof output + @# target ships profiling libraries — lib tree has .p_hi / .p_a + @# + @# Reported pairs: + @# GHC Dynamic = (target is dynamic) && (target ships dynamic libraries) + @# GHC Profiled = (target is profiled) && (target ships profiling libraries) + @# + @# Per-target settings file completely controls these — the + @# shared stage2 GHC binary's RTS-baked-in dynamic/prof-ness is + @# no longer consulted. Two dials per way (is / ships) so a + @# target can be capable but not currently ship artifacts (or + @# vice versa) — keeps the axes orthogonal for slimming + @# experiments and matches what cabal really wants to know. + @# + @# Defaults for our bindists: dynamic dials YES (most targets + @# ship dyn libs), prof dials NO (stage2 isn't built -prof so + @# no target currently ships prof libs). + @# Override via STAGE3__TARGET_{IS_DYNAMIC,SHIPS_DYN_LIBS, + @# IS_PROFILED,SHIPS_PROF_LIBS}. + @# + @# Note: `$$$$` (four dollars) collapses through two layers of + @# Make expansion (define-template + recipe-time) to a literal `$` + @# at shell time, which is what sed needs as the end-of-line anchor. + @# `$$` would collapse to `$` after template expansion, then Make + @# would interpret the lone `$/` in the recipe as a variable lookup + @# and drop the anchor entirely (verified: PR #187 first attempt). + $(SED) -i -e 's/\]$$$$/,("target is dynamic","$(if $(STAGE3_$(1)_TARGET_IS_DYNAMIC),$(STAGE3_$(1)_TARGET_IS_DYNAMIC),YES)"),("target ships dynamic libraries","$(if $(STAGE3_$(1)_TARGET_SHIPS_DYN_LIBS),$(STAGE3_$(1)_TARGET_SHIPS_DYN_LIBS),YES)"),("target is profiled","$(if $(STAGE3_$(1)_TARGET_IS_PROFILED),$(STAGE3_$(1)_TARGET_IS_PROFILED),NO)"),("target ships profiling libraries","$(if $(STAGE3_$(1)_TARGET_SHIPS_PROF_LIBS),$(STAGE3_$(1)_TARGET_SHIPS_PROF_LIBS),NO)")]/' $$(TARGET_DIR)/lib/settings + $$(DIST_DIR)/bin/$(1)-ghc --info @rm -rf $$(TARGET_DIR)/lib/package.conf.d diff --git a/cabal.project.stage3.settings.in b/cabal.project.stage3.settings.in index 0be702118ae4..03bc97751120 100644 --- a/cabal.project.stage3.settings.in +++ b/cabal.project.stage3.settings.in @@ -1,8 +1,8 @@ -- cabal.project.stage3.settings - generated by configure from .in template -- Do not edit this file directly; edit cabal.project.stage3.settings.in instead. -- --- Multi-target stage3: applies dynamic library settings ONLY to wasm32 --- via cabal's `if arch(wasm32)` conditional, which cabal evaluates against +-- Multi-target stage3: applies dynamic library settings per-target +-- via cabal's `if arch(...)` conditional, which cabal evaluates against -- the --with-compiler's target arch per-invocation. -- -- * stage3-wasm32-unknown-wasi (--with-compiler=wasm32-...-ghc): @@ -11,10 +11,21 @@ -- (miso, jsaddle, aeson, …) -- -- * stage3-javascript-unknown-ghcjs (--with-compiler=javascript-...-ghc): --- arch=javascript → conditional FALSE → settings do NOT apply --- → no shared:True flowing into emcc/wasm-ld (which can't produce --- .so for the JS backend; the alternative breaks with --- `wasm-ld: error: unknown argument: -h`) +-- arch=javascript → conditional TRUE (ghc-options branch) +-- → ghc-options: -dynamic-too applied per-package +-- → produces .dyn_hi files without invoking cabal's library-dynamic +-- .so link step (which fails for JS with +-- `wasm-ld: error: unknown argument: -h`). +-- Path C symmetric: the shared stage2 GHC binary reports +-- GHC Dynamic=YES (it is) which makes cabal-install enable +-- library-dynamic by default for the JS target too. Without +-- .dyn_hi files cabal then fails reading e.g. Prelude.dyn_hi +-- when compiling miso. We don't want shared:True here (which +-- would also invoke wasm-ld for an .so we don't need), so +-- ghc-options is the surgical alternative: ask GHC to emit +-- .dyn_o + .dyn_hi alongside .o + .hi during compile, and let +-- cabal skip the library-dynamic link step. .so byproducts +-- are inert for JS (no dlopen). -- -- * Native build-side packages (compiled with --with-build-compiler=ghc, -- i.e. happy-lib, alex, deriveConstants, Setup.hs scripts): @@ -33,5 +44,23 @@ if arch(wasm32) shared: True executable-dynamic: True +-- NOTE: an earlier draft of #66 (PR #187) attempted Path C for the +-- JS target via: +-- if arch(javascript) +-- package * +-- ghc-options: -dynamic-too +-- but cabal-install applies `ghc-options` differently from `shared` +-- in the dual-compiler split: `shared` is properly per-target-arch +-- (only host packages see it; native build-side packages like alex, +-- happy-lib do NOT), but `ghc-options` leaks to build-side compiles +-- regardless of the arch conditional. The native stage2 is built +-- DYNAMIC=0 (no .dyn_hi for the host arch), so alex's first .hs +-- module failed with +-- Prelude.dyn_hi: does not exist (No such file or directory) +-- when compiled by the build compiler with -dynamic-too. +-- Path C for JS therefore needs deeper cabal work (or a Makefile- +-- side .dyn_hi copy after the fact); the GHC Dynamic settings dial +-- in the sibling commit (#67) is the proper long-term answer. + constraints: rts +dynamic diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs index 02afd8c91ce4..74e96790de0e 100644 --- a/compiler/GHC/Driver/Session.hs +++ b/compiler/GHC/Driver/Session.hs @@ -132,6 +132,10 @@ module GHC.Driver.Session ( sGhcWithInterpreter, sLibFFI, sTargetRTSLinkerOnlySupportsSharedLibs, + sTargetIsDynamic, + sTargetShipsDynLibs, + sTargetIsProfiled, + sTargetShipsProfLibs, GhcNameVersion(..), FileSettings(..), PlatformMisc(..), @@ -3548,8 +3552,16 @@ compilerInfo dflags ("Have native code generator", showBool $ platformNcgSupported platform), ("target has RTS linker", showBool $ platformHasRTSLinker platform), ("Target default backend", show $ platformDefaultBackend platform), - -- Whether or not we support @-dynamic-too@ - ("Support dynamic-too", showBool $ not isWindows), + -- Whether or not we support @-dynamic-too@ for this target. + -- Historically `not isWindows` (Windows tooling couldn't do + -- it). Now also gated on the per-target `sTargetIsDynamic` + -- dial — if the target isn't dynamic-capable, -dynamic-too + -- is meaningless. Keep the Windows guard as defence in depth + -- for pre-this-patch bindists on Windows that lack the key + -- and so default sTargetIsDynamic=True (the AND would + -- otherwise regress them). + ("Support dynamic-too", showBool $ not isWindows + && sTargetIsDynamic (settings dflags)), -- Whether or not we support the @-j@ flag with @--make@. ("Support parallel --make", "YES"), -- Whether or not we support "Foo from foo-0.1-XXX:Foo" syntax in @@ -3569,10 +3581,27 @@ compilerInfo dflags ("Uses package keys", "YES"), -- Whether or not we support the @-this-unit-id@ flag ("Uses unit IDs", "YES"), - -- Whether or not GHC was compiled using -dynamic - ("GHC Dynamic", showBool hostIsDynamic), - -- Whether or not GHC was compiled using -prof - ("GHC Profiled", showBool hostIsProfiled), + -- Reported as YES iff *both* per-target settings dials say so: + -- `target is dynamic` — the GHC for this target + -- can produce dynamic output + -- `target ships dynamic libraries` — the lib tree actually has + -- .dyn_hi / .so artifacts + -- cabal-install reads this to decide whether to enable + -- @library-dynamic@ by default. The target's per-target settings + -- file completely controls this value — no host-RTS dependency, + -- so on a multi-target bindist with one shared stage2 GHC binary + -- different targets can correctly disagree. Both keys default to + -- True if absent (matches pre-this-change behaviour). + ("GHC Dynamic", showBool (sTargetIsDynamic (settings dflags) + && sTargetShipsDynLibs (settings dflags))), + -- Profiling-way analogue of `GHC Dynamic`. Per-target dials + -- via `target is profiled` + `target ships profiling libraries` + -- settings keys. Drops the historical `hostIsProfiled` RTS- + -- baked-in for the same reason the dyn pair did: on a multi- + -- target bindist the shared stage2 GHC binary's prof-ness is + -- fixed but the lib trees can disagree per target. + ("GHC Profiled", showBool (sTargetIsProfiled (settings dflags) + && sTargetShipsProfLibs (settings dflags))), ("Debug on", showBool debugIsOn), ("LibDir", topDir dflags), -- This is always an absolute path, unlike "Relative Global Package DB" which is diff --git a/compiler/GHC/Platform.hs b/compiler/GHC/Platform.hs index 5375c243c6ef..972f3fb23fc1 100644 --- a/compiler/GHC/Platform.hs +++ b/compiler/GHC/Platform.hs @@ -291,6 +291,33 @@ data PlatformMisc = PlatformMisc , platformMisc_libFFI :: Bool , platformMisc_llvmTarget :: String , platformMisc_targetRTSLinkerOnlySupportsSharedLibs :: Bool + -- | Is the GHC for this target capable of producing dynamic + -- output (i.e. can it honour @-dynamic@ / @-dynamic-too@)? + -- Per-target settings key @"target is dynamic"@ in + -- @lib/targets/\/lib/settings@. On a multi-target bindist + -- the shared stage2 GHC binary's RTS-baked-in dynamic-ness is + -- not a per-target proxy — different targets in one binary may + -- need to disagree (e.g. a JS target whose iserv runs vanilla + -- and whose lib tree has no dyn artifacts). Combined with + -- 'platformMisc_targetShipsDynLibs' to drive @ghc --info@'s + -- @GHC Dynamic@ value, which cabal-install reads. + , platformMisc_targetIsDynamic :: Bool + -- | Does the target's installed library tree ship @.dyn_hi@ / + -- @.so@ files? Per-target settings key + -- @"target ships dynamic libraries"@. Set independently of + -- 'platformMisc_targetIsDynamic' so a target can be dynamic- + -- capable but not currently ship dyn artifacts (or vice versa). + , platformMisc_targetShipsDynLibs :: Bool + -- | Profiling-way analogue of 'platformMisc_targetIsDynamic'. + -- Per-target settings key @"target is profiled"@. Drives + -- @ghc --info@'s @GHC Profiled@ — cabal-install reads that to + -- decide whether to enable @library-profiling@. + , platformMisc_targetIsProfiled :: Bool + -- | Profiling-way analogue of 'platformMisc_targetShipsDynLibs'. + -- Per-target settings key @"target ships profiling libraries"@. + -- Combined with 'platformMisc_targetIsProfiled' for the + -- @GHC Profiled@ report. + , platformMisc_targetShipsProfLibs :: Bool } platformSOName :: Platform -> FilePath -> FilePath diff --git a/compiler/GHC/Settings.hs b/compiler/GHC/Settings.hs index b8ec884a94e4..db5653794314 100644 --- a/compiler/GHC/Settings.hs +++ b/compiler/GHC/Settings.hs @@ -67,6 +67,10 @@ module GHC.Settings , sGhcWithInterpreter , sLibFFI , sTargetRTSLinkerOnlySupportsSharedLibs + , sTargetIsDynamic + , sTargetShipsDynLibs + , sTargetIsProfiled + , sTargetShipsProfLibs ) where import GHC.Prelude @@ -314,3 +318,26 @@ sLibFFI = platformMisc_libFFI . sPlatformMisc sTargetRTSLinkerOnlySupportsSharedLibs :: Settings -> Bool sTargetRTSLinkerOnlySupportsSharedLibs = platformMisc_targetRTSLinkerOnlySupportsSharedLibs . sPlatformMisc + +-- | Is the GHC for this target capable of producing dynamic output? +-- Read from the per-target settings file key @"target is dynamic"@. +-- Combined with 'sTargetShipsDynLibs' to drive @ghc --info@'s +-- @GHC Dynamic@ value — the target's settings file completely +-- controls that, no host-RTS dependency. +sTargetIsDynamic :: Settings -> Bool +sTargetIsDynamic = platformMisc_targetIsDynamic . sPlatformMisc + +-- | Does this target's installed library tree ship .dyn_hi / .so files? +-- Per-target settings key @"target ships dynamic libraries"@. +sTargetShipsDynLibs :: Settings -> Bool +sTargetShipsDynLibs = platformMisc_targetShipsDynLibs . sPlatformMisc + +-- | Profiling-way analogue of 'sTargetIsDynamic'. Per-target settings +-- key @"target is profiled"@. +sTargetIsProfiled :: Settings -> Bool +sTargetIsProfiled = platformMisc_targetIsProfiled . sPlatformMisc + +-- | Profiling-way analogue of 'sTargetShipsDynLibs'. Per-target settings +-- key @"target ships profiling libraries"@. +sTargetShipsProfLibs :: Settings -> Bool +sTargetShipsProfLibs = platformMisc_targetShipsProfLibs . sPlatformMisc diff --git a/compiler/GHC/Settings/IO.hs b/compiler/GHC/Settings/IO.hs index 63964ff02ff8..2ede21587c6c 100644 --- a/compiler/GHC/Settings/IO.hs +++ b/compiler/GHC/Settings/IO.hs @@ -185,6 +185,34 @@ initSettings top_dir = do ghcWithInterpreter <- getBooleanSetting "Use interpreter" useLibFFI <- getBooleanSetting "Use LibFFI" + -- Per-target dial #1: is the GHC for THIS target capable of + -- producing dynamic output (i.e. honouring -dynamic / + -- -dynamic-too)? On a multi-target bindist with one shared stage2 + -- GHC binary, this can't be derived from the binary's compile- + -- time `hostIsDynamic`. Default True for backward compatibility + -- with older bindist settings files that predate the key. + targetIsDynamic <- either (const $ pure True) pure $ + getRawBooleanSetting settingsFile mySettings "target is dynamic" + + -- Per-target dial #2: does this target's installed lib tree + -- actually ship .dyn_hi / .so files? Independent of + -- `target is dynamic` so a dynamic-capable target can still + -- truthfully say it doesn't ship artifacts (e.g. a slimmed + -- bindist). cabal-install combines both via GHC Dynamic to + -- decide whether to enable library-dynamic by default. + targetShipsDynLibs <- either (const $ pure True) pure $ + getRawBooleanSetting settingsFile mySettings "target ships dynamic libraries" + + -- Profiling-way analogues of the two dyn dials above. Drive + -- `GHC Profiled` the same way (sTargetIsProfiled && sTargetShipsProfLibs). + -- Defaults to True for backward compatibility with bindists that + -- predate the keys — matches the historical hostIsProfiled + -- behaviour when GHC was prof-built. + targetIsProfiled <- either (const $ pure True) pure $ + getRawBooleanSetting settingsFile mySettings "target is profiled" + targetShipsProfLibs <- either (const $ pure True) pure $ + getRawBooleanSetting settingsFile mySettings "target ships profiling libraries" + baseUnitId <- getSetting_raw "base unit-id" return $ Settings @@ -267,6 +295,10 @@ initSettings top_dir = do , platformMisc_libFFI = useLibFFI , platformMisc_llvmTarget = llvmTarget , platformMisc_targetRTSLinkerOnlySupportsSharedLibs = targetRTSLinkerOnlySupportsSharedLibs + , platformMisc_targetIsDynamic = targetIsDynamic + , platformMisc_targetShipsDynLibs = targetShipsDynLibs + , platformMisc_targetIsProfiled = targetIsProfiled + , platformMisc_targetShipsProfLibs = targetShipsProfLibs } , sRawSettings = settingsList