Skip to content

Graft stable-haskell cross work onto cross-compile-2 - #528

Open
angerman wants to merge 83 commits into
stable-haskell/feature/cross-compile-2from
sh/graft-on-cross-compile-2
Open

angerman wants to merge 83 commits into
stable-haskell/feature/cross-compile-2from
sh/graft-on-cross-compile-2

Conversation

@angerman

Copy link
Copy Markdown

Summary

Graft the useful stable-haskell cross-compilation work onto Andrea Bedini's dual-compiler line.

Base is stable-haskell/feature/cross-compile-2 at 805164bc45fb64783c343a44d468c42196d511d2, the same commit as haskell/cabal#12356 (andrea/feature/cross-compilation-2). This PR does not merge stable-haskell/master and does not replay the pre-Machine history that the October split stack still carries.

The October stack (PRs #527, #517–#524) was cut from the older Stage-era cross-compile-2. Thirty-six of its commits replay history that haskell#12356 already replaced. Only the stable-haskell-only commits were brought across, retargeted from Stage / Staged to Machine / PerMachine. onMachine Build is not used unless the plan is actually cross-compiling; the build compiler is fromMaybe (onHost toolchains) (onBuild toolchains). plan.json keeps the machine field (what this base emits) and gains dist-dir / build-info selection. projectConfigInstallPackages is kept.

No full compile has been run on this branch. Do not treat CI as green until it has actually run.

Harvested

Source Graft commit What landed
#385 rpath trio 9f09d43b2, e16ae1130, 95679d789 Relativize absolute rpaths, drop the relocatable gate, add $ORIGIN / @loader_path for musl sibling deps. Cabal/src/Distribution/Simple/GHC/Build/Link.hs only.
#524 fcefcaa1a Retry Windows temporary-directory cleanup.
#527 a824795f6 Install and toolchain groundwork, including the internal install-package overlay.
#517 8ed479b4a Profiling as a target ABI. BuildWay is StaticWay | DynWay; a profiled compiler is compilerTargetAbi == "profiling".
#518 7295e48b5 Qualified target identity (package build:* / package host:* on Machine).
#519 13e10366c Full machine triples through configure. Build triple uses the host toolchain when not cross-compiling.
#520 e5feb13f3 Configured toolchain metadata and build package DBs, kept alongside the base's build-program paths and args.
#521 c30f40f97 Staged solver qualification and diagnostics, on Machine.
#522 e8c7b9887 Staged client actions and fixtures.
#523 3dd7d632d Test harness, plan.json reader (planDistDirForMachine, buildInfoFileForMachine), haddock fixtures. The reader parses machine, not stage.
#361 38aa45721 Hand-port of faa232267 (head branch was deleted). guessToolFromGhcPath tries the target prefix (wasm32-unknown-wasi-ghc-pkg) before the unprefixed tool. Applied after the commits that touch GHC.hs.

Already on this base, not reapplied

Left out on purpose

Test plan

  • CI on this PR
  • A local cabal build of Cabal and cabal-install against the two-compiler project tests
  • cabal-testsuite plan.json reader and the BuildCompilerTool tests (they select Machine Build, not Stage)

andreabedini and others added 30 commits October 27, 2026 15:57
'StoreDirLayout' keyed every location on the 'Compiler' alone:

    store/<compiler-id>-<abi>/<unit-id>

The compiler's id and ABI tag record which compiler built a package, but
not what that compiler produces. With a build stage and a host stage in
one build the two compilers can agree on both while targeting different
architectures, and their outputs are then filed in one directory, under
one package database. The 'UnitId's differ -- cabal hashes the platform
along with the compiler id, see 'PackageHashInputs' -- so the two builds
do not overwrite each other. What they share is the directory, left
holding binaries for two architectures, and the package database, left
holding entries for both and read back by two different ghc-pkgs.

Take the target 'Platform' alongside the 'Compiler' in every
'StoreDirLayout' location, and name it first in the path:

    store/<platform>/<compiler-id>-<abi>/<unit-id>

which is the segment order 'distBuildDirectory' has always used. Each
caller passes the platform belonging to the package at hand: the stage's
toolchain for a per-stage database or store entry, 'pkgConfigPlatform'
for the project as a whole, and 'configureCompiler''s platform in 'cabal
path'.

'distPackageDB', the database inplace packages are registered in, takes
the platform too:

    dist-newstyle/packagedb/<platform>/<compiler-id>

and for a sharper reason than the store. An inplace 'UnitId' does not
mention the compiler at all, so where the store's two copies merely
shared a directory, the two copies of a local package built at both
stages are registered over one another.

The test harness learns the same layout. 'findDependencyInStore' and the
'--intree-cabal-lib' package-db lookup both descend the extra segment,
and both tolerate a store without it: those libraries are built by the
cabal on PATH rather than the one under test, so they may still be
written in the old layout.

Existing store entries under the old layout are neither migrated nor
read. They are left in place, and packages are rebuilt into the new
layout on first use. The same goes for the inplace package database.
…igShared

'elaborateInstallPlan' called its 'ProjectConfigShared' argument
'sharedPackageConfig', which is the name ProjectBuilding.hs uses for an
'ElaboratedSharedConfig' -- a different type. So the same identifier meant
two things depending on the module.

Call it 'projectConfigShared', as the field and every other binding of
that config are named, which leaves 'sharedPackageConfig' to mean the
elaborated config and nothing else.

Rename only.
The type is not a path. It records at most one qualifier, not the
route by which a goal was reached, and the stage it carries is a
summary of that route (how many tool boundaries were crossed, capped
at one) rather than an independent coordinate. The old name and the
"orthogonal axes" comment suggested otherwise, which made a value like
`PackagePath Host ns (QualSetup p)` look contradictory when it is the
ordinary shape of every setup dependency in a non-cross build.

Rename the type, constructor and module to GoalScope, the twin of the
existing ConstraintScope that matches it, and rewrite the comments to
state what each field answers and the invariant that ties the stage
to the qualifier.

No behaviour change.
prevStage used to step Host -> Build unconditionally, inventing a
Build stage in builds that have no distinct build toolchain. Every
caller then had to undo that by hand: the solver guarded it with
qoCross, ProjectBuilding folded the stage back with a local
activeStage helper, and the planning call sites relied on getStage
aliasing Build to the host value.

Give prevStage the toolchains (any Staged) so the clamp happens where
the information lives: prevStage tc Host is Build only when isCross tc
holds, and Host otherwise. Changing the signature rather than adding a
second function makes every stale call a type error.

With that, the solver index becomes a Staged StageIndex instead of a
Map Stage StageIndex, QualifyOptions carries the active stages instead
of a Bool, and the ProjectBuilding collapse helper goes away. A Build
stage now exists only when there is a distinct build toolchain, by
construction rather than by convention.

No behaviour change.
Now that prevStage only produces a Build stage when a distinct build
toolchain exists, a plan node is never at Build in a non-cross build.
Two places still guarded against that:

- elabActiveStage folded a package's stage onto Host for non-cross
  builds before recording it in the running InstalledPackageIndex.
  With the invariant in place it is the identity on elabStage, so use
  elabStage directly.
- projectPackageDbsFor only emptied the package-db list for Build when
  isCross held. Build implies cross now, so the check and the
  Toolchains parameter go.

The projectPackageDbsFor unit tests are reshaped accordingly: the
"non-cross Build keeps the databases" case becomes a test that
prevStage keeps a non-cross setup script at Host, and two tests pin
prevStage itself (Host -> Build under cross, Build clamps).

No behaviour change.
Add doc/internal/solver.md, a contributor-facing explanation of how the
modular solver works: the lazily built search tree, the pruning,
validation and reordering passes, goal scopes and linking, exploration
with conflict sets and backjumping, how a solution becomes an install
plan, and how error messages are produced.

It draws on the HIW 2011 talk that introduced the solver and on the
Well-Typed posts about qualified goals and setup dependencies, and notes
where the code has moved on from them.

Point to the new page from the Distribution.Solver.Modular module
header and from 'solve', and fix a comment in Validate that referred to
the removed preferEasyGoalChoices.
Make the limitation visible where the toolchains are configured: the
program-locations sections, --with-PROG and extra-prog-path apply to
both stages, so host and build resolve the same pkg-config and other
auxiliary tools. The per-stage pkg-config database read is correct but
reads the same tool twice until stage-qualified configuration exists.
…compilation

prevStage now yields Build only when there is a distinct build
toolchain, so nothing should ever ask a Staged value for a build stage
it does not have. Make that an error in getStage instead of a silent
fallback to the host value, and remove the two APIs that existed only
to paper over the old aliasing:

- activeStages, whose last caller iterated the stages to read every
  store; a traversal of the Staged toolchains does the same without a
  list-of-stages view.
- pkgConfigBuildToolchain and its compiler, platform and program-db
  projections, literal-Build accessors documented as "equal to the
  host toolchain unless cross-compiling", with no callers.

Reword the Staged documentation to say there is no build stage in an
ordinary build, rather than a build stage that reuses the host value,
and fix the QualifyOptions comment that still credited convPIs with
deciding whether a build stage exists.
… and custom Setup

BuildCompilerToolFromSource crosses one tool boundary (host -> build).
Nothing covered the second boundary (build -> build), where prevStage
has to clamp at the build stage: the build-tool-depends and setup
dependencies of a build-stage package. A bug that resolved those at
the host stage passed every existing two-stage test.

Add BuildCompilerToolOfTool: app (host) has the build tool tool, which
has the build tool gen and a custom Setup.hs. The test checks that gen
has a build-stage copy in the plan and that gen, tool and tool's Setup
all report the build compiler, while app reports the host compiler.
hlint flags `() <$ idx` with "Use void"; the lint CI job runs with
fail-on: suggestion, so this fails the PR.
GoalScope already carries the Stage, so keying LinkingState by
(PN, I, Stage) stored it twice. Restore the trunk type
Map (PN, I) [GoalScope] and filter link targets by stage in
linkChoices instead. Behaviour is unchanged: a goal still only links
to goals of its own stage.
Under cross-compilation the programs of the build stage can now be
configured separately from those of the host stage. --with-PROG,
--PROG-options, program-locations and program-options apply to the host
stage only, like --with-compiler; the build stage takes its own settings:

- the command-line flags --with-build-PROG, --build-PROG-option and
  --build-PROG-options;
- the top-level build-program-locations and build-program-options
  stanzas;
- the build-PROG-location and build-PROG-options fields of a package
  stanza.

Neither stage inherits the other's settings: --with-ar is the ar for host
binaries and must never produce build-machine binaries. Each stage's
toolchain is configured with its own program locations, and elaboration
picks the locations and arguments of the package's stage. The per-package
compiler-path check runs per stage against that stage's compiler.

Without cross-compilation there is no build stage, so the settings have
no effect; cabal warns that they are ignored.

This lifts the limitation that program locations were not per stage.
extra-prog-path is still shared by both stages.
…nts (C5, part A2)

Give users a way to write the stage-scoped constraints the solver has
supported internally since "make constraint scopes stage-aware":

  - UserConstraintScope now pairs an optional build Stage with a
    UserConstraintQualifier (the former scope constructors). The common
    unstaged case keeps the plain 'UserConstraint qualifier prop' shape
    through a pattern synonym, with UserConstraintStaged for the staged
    case (COMPLETE pragma provided).
  - The constraint parser accepts an optional 'build:' prefix (e.g.
    'build:any.rts installed'). The Host stage is the invisible default:
    it is never rendered, so non-cross output is unchanged, and
    correspondingly there is no 'host:' prefix (it could not round-trip).
    Package-qualified forms are tried before the stage prefix, so a package
    literally named "build" keeps its meaning ('build:setup.foo' is the
    setup dependency of package build).
  - The solver's pretty-printer renders a Just Build stage as the same
    'build:' prefix, so constraints written back to cabal.project.local
    parse again.

Only setup and build-tool dependencies are ever solved for the build
stage, so 'build:' is only meaningful with the any/setup qualifiers; this
is documented in setup-commands.rst. In a non-cross build there are no
build-stage goals, so a 'build:' constraint matches nothing there.

Kept out of the cross-compilation PR proper: the syntax for scoping
constraints to qualified goals is still being settled (haskell#3502), and this
form should be agreed there before it becomes something we have to keep
parsing.
…C5, part B)

Add an executable qualifier alongside the setup one, so a constraint can
target a package as it is depended on via build-tools:

  * solver: ScopeAnyExeQualifier, matching goals with a QualExe path;
    rendered as 'exe.<pkg>'.
  * cabal-install: UserAnyExeQualifier plus parser support for the
    'exe.<pkg>' shorthand and the fully-qualified '<pkg>:<tool>:exe.<dep>'
    form (mapping to UserQualExe). The latter is the form dispQualifier
    already prints for QualExe, so constraints written back to
    cabal.project.local parse again; UserQualExe is re-enabled in the
    Arbitrary instance and the print/parse round-trip covers it.

Extend the UserConstraint grammar, re-enable the previously-disabled
UserQualExe example, and document both forms in setup-commands.rst
(replacing the long-standing TODO there).
…ackage host:* (C5, part C)

Under cross-compilation a package can be built twice, for the build
machine and for the host, and a project file has no way to configure
the two differently: "package *" applies to both copies, and a stanza
naming the package applies to both as well. GHC's own build needs the
distinction (its build-stage tools are linked statically against the
bootstrap compiler while the host stage ships shared libraries), and
cabal.project.stage2.dynamic in stable-haskell/ghc already writes it.

Add "package build:*" and "package host:*" stanzas, applying to every
package built for that stage. In the layering of per-package options
they sit between the local-package fields and the named-package
stanza:

    package *  <  local packages  <  package <stage>:*  <  package <name>

The lookup (lookupPerPkgOption) takes the stage of the package being
elaborated; elaborateSolverToCommon fixes it to the package's own
stage for the many per-package option reads, and the library-way
selection consults the config of each node's own stage.

The stage names are spelled out in the parser rather than parsed as a
Stage: the user only ever writes them here and in constraints, where
"build:" is a prefix as well, and Host stays the invisible default
elsewhere.

To let "build:*" through, section arguments in the fields parser may
now contain colons (adjacent argument tokens joined by ":"). This
accepts strictly more input; no consumer of a .cabal file accepts an
argument with a colon (the library/executable/common names reject it),
so .cabal files gain no new syntax and no cabal-version gate is needed.
The legacy project-file parser does not know the stanzas.

Tests: parser-tests project-config-stage-packages; the ProjectConfig
Arbitrary instance leaves the field empty (neither the legacy format
nor the printer round-trips it). Documented under the package stanza in
the project file reference.

Program locations given in a stage stanza also apply to the tools cabal itself
runs for that stage: configureToolchains layers the stage's package config on
the global and local program locations and extra search paths when it builds
that stage's program database. That is what makes a cross-compilation able to
point the host stage at its own pkg-config (and so at its own pkg-config
database, now read per stage) while the build stage keeps the native one.
`Stage` read as a phase of the build (GHC's stage0/1/2 bootstrap
sequence), but the type names which toolchain a goal or plan node is
compiled with. Its constructors were already autoconf's names for that:
the *build* machine is where the build runs, the *host* machine is where
the artifacts will run. Nothing is ordered by it; it only selects a
toolchain.

Within the solver the value is still route-derived: only `qualifyDeps`
ever yields `Build`, on `QualExe`/`QualSetup` edges, via what is now
`toolMachine` (tools run where the build runs). It is carried in
`GoalScope` because the flattened qualifier cannot always recover it.

Mechanical rename, no behaviour change:

- `Stage`/`Staged`/`getStage`/`prevStage`/`showStage` become
  `Machine`/`PerMachine`/`onMachine`/`toolMachine`/`showMachine`;
  `Distribution.Solver.Types.Stage` is now `.Machine`
- cabal-install: `WithStage`/`HasStage`/`elabStage` and the rest follow;
  `ProjectPlanning.Stage` is now `ProjectPlanning.Machine`
- plan.json: the per-entry key is `machine` (was `stage`); the values
  `host`/`build` are unchanged, as are all other user-facing spellings
  (`build:` constraints, `package build:` stanzas, `--with-build-PROG`,
  the store path segment)
- user guide, changelog entries and internal solver doc say "build
  machine"/"host machine"; the sequence wording ("earliest stage",
  "clamps", "previous stage") is gone
- `doc/cmd-*-help/*.txt` regenerated from `cabal --help`; this also
  picks up the `--with-build-PROG`/`--build-PROG-options` entries that
  were missing from the captures
Conditionals in a project file are resolved once, against the host
compiler, before the build and host machines are told apart, so there is
no way to make a setting depend on the build compiler. Document this as a
known limitation, with work-arounds: an unconditional build:-prefixed
constraint (which matches nothing in a non-cross build), a per-build-
compiler project file, or cabal.project.local / command-line flags.
--with-PROG and --with-build-PROG are chosen by the machine the package
being built belongs to (elabProgramPaths uses machineProgramPaths
elabMachine), not by the machine the program runs on. A host package that
calls alex, which runs on the build machine, still gets --with-alex.
Say so in the build-machine programs section.
Bring the user guide in line with the changelog entries of the
cross-compilation series:

- the build: constraint prefix also combines with the exe qualifier, and
  matches nothing without cross-compilation;
- package-dbs / --package-db apply to the host machine only;
- the store is keyed by <platform>/<compiler-id>-<abi>;
- the legacy project file parser rejects package build:* and host:*.
depLibraryPaths returns absolute library paths whenever the dep's
libdir is not under the package's own install prefix (i.e. almost
always, in a cabal-store layout where each package gets its own
hash-suffixed subdir). The previous getRPaths only prefixed
`@loader_path` / `$ORIGIN` to already-relative paths, so those
absolute store paths were baked directly into LC_RPATH (Mach-O)
and DT_RUNPATH (ELF).

On macOS up to and including Sonoma (macOS 14) dyld silently falls
through to subsequent rpath entries when an absolute rpath does
not exist. Sequoia (macOS 15) dyld treats the same condition as
fatal and aborts the binary on launch. This affected the
stable-haskell GHC bindist: a `/Volumes/WorkSpace/_work/ghc/ghc/
_build/stage2/store/host/aarch64-apple-darwin/lib` rpath baked in
by the build runner caused `ghc --numeric-version` to SIGABRT
when the bindist was installed on any macos-15 host.

When the user has opted into relocatable mode
(`--enable-relocatable`, or `relocatable: True` in a cabal
project), this commit replaces such absolute rpaths with a
`shortRelativePath`-computed relative form against the artifact's
install directory. The relative form is well-formed (no `/Volumes`
prefix to abort dyld on), and harmless when the bindist layout no
longer matches the build store (dyld treats it as a normal missing
rpath and falls through to subsequent entries).

Non-relocatable builds (the default) are unchanged: absolute paths
still pass through to preserve the existing semantics for
non-relocated installs.

The PackageDescription, InstallDirs, and shortRelativePath utility
this needs are all already in scope or come from already-imported
modules; only `bindir` and `libdir` field accessors are pulled in
freshly from `Distribution.Simple.InstallDirs`.

Companion to commit 010b365582c in stable-haskell/ghc, which strips
the same leaked rpaths post-build with `install_name_tool` while
this Cabal-side fix propagates through bindist rebuilds.

(cherry picked from commit 39bd62a)
Initial version of this fix gated the new relativization on
`relocatable lbi` to keep non-relocatable builds byte-identical. The
gate doesn't help in practice:

  * the stable-haskell GHC bindist build requires the new behavior
    (else the darwin host binaries abort-trap on macOS 15);

  * setting `relocatable: True` to flip the gate also triggers
    `checkRelocatable` (which refuses cabal-store layouts whose deps
    live in sibling prefixes) and changes how `library-dirs` are
    written into .conf files — neither change is desirable for the
    rpath fix, and the latter actively breaks the bindist
    post-stage2 .conf rewriting in our Makefile pipeline.

The new always-on behavior is a strict improvement for every
relocatable scenario (cabal-store relocation, bindist relocation,
nix-style closure moves) and only a theoretical regression for the
"copy a single executable to an unrelated host and expect the same
absolute path to still resolve" case, which has never been part of
cabal's documented contract.

Companion to stable-haskell/ghc commit that drops the matching
`relocatable: True` flag from configure.ac.

(cherry picked from commit e7941cd)
…deps)

The rpath relativization (added for the darwin LC_RPATH-leak fix) never adds the
artifact's own directory to the runpath. depLibraryPaths can return the parent
directory for a dependency installed in the same directory as the artifact, so
that entry relativizes to "$ORIGIN/.." and the dependency is left unfound.

glibc papers over this because GHC sets LD_LIBRARY_PATH at runtime before
dlopen and glibc re-reads it per dlopen; musl reads LD_LIBRARY_PATH only at
process startup and ignores the runtime change, so the missing self-rpath is
fatal there. Concretely, a Backpack signature implementation (libHSp)
installed next to the instantiated unit (libHSindef) fails to load under musl
(GHC testsuite backpack/cabal/T14304 on Alpine), though it passes on every
glibc platform.

Always prepend the loader-relative self directory ($ORIGIN, or @loader_path on
macOS) to the rpaths so a same-directory sibling is found. This is a relative
entry, so it does not reintroduce the absolute build-host path the
relativization removed (no macOS regression).

(cherry picked from commit 5c09786)
Port 468b554d0 onto the cross2 base (applies cleanly).

Remove temporary directories with bounded retries on permission errors:
Windows file handles can remain locked briefly after a waited child
exits; retry up to three times with a one-second delay and preserve
the final error if removal still fails.

(cherry picked from commit 5a4fd8f)
Port of 19c5938 (persistent store for installed libraries) and
c20f160 (in-library compiler metadata with the toolchain) onto the
cross-compilation-2 tip. CmdInstall.hs keeps cross2's stage-aware
getPackageDbStack helper and compiler-ex API; only the persistent
store layout binding is added for the planner's registration DB.

(cherry picked from commit 1620840)

Retargeted onto Machine: pass elabToolchain into libraryConfigureInputsFromElabPackage, and use PerMachine in the in-library unit test.
Port 5f97030 (Make profiling an intrinsic target ABI and remove
legacy ways) onto the cross-compilation-2 base, keeping cross2's
staged architecture instead of PR501's pure-common refactor:

- elaborateInstallPlan takes stage/install package configs and merges
  them via a stage-aware lookupPerPkgOptionStaged (stage + install
  overlays), instead of replacing the lookup architecture.
- PerPkgConfigFlag and the needs*/libraryWayWarnings predicates take
  the staged lookup so install-target profiling reaches way selection.
- withProfExe honours Prof+ProfExe via the staged lookup; fix the
  swapped withProfLibDetail/withProfExeDetail.
- checkBadPerPackageCompilerPaths runs over all toolchains and also
  covers install-target packages.
- Add normaliseUnscopedPackageConfig (exported); keep cross2's
  shouldBeLocal, elaborateSolverToCommon, instantiate flow.
- Tests: extend ProjectConfig Arbitrary/shrink for the two new maps;
  keep the branch-native coverage test; wire in the install-promotion
  and profiling-layer test groups.

(cherry picked from commit 457d660)

Retargeted onto Machine. Kept projectConfigMachinePackages and added the install-target overlay. Profiling defaults follow compilerTargetsProfiling.
Port f405dfa (Use qualified target identity across Cabal planning
and paths) onto the cross-compilation-2 base, keeping cross2's
per-machine architecture instead of PR501's single-toolchain refactor:

- Keep cross2's (Compiler, Platform) store API and DistDirLayout paths;
  port betterPlatform as a canonical-name helper (exported) with test
  coverage instead of rewiring store/dist layouts.
- Library ways stay in cross2's elaborateLibraryWays; port L3's
  profiling-aware canBuildSharedLibs into
  compilerShouldUseSharedLibByDefault, and L3's False profiling
  defaults into the module-level needsProfilingLib/Shared predicates.
- Package hash gains pkgHashTargetName from the package's own machine
  toolchain (elabCompiler/elabPlatform), not a single global toolchain.
- Plan JSON and ghc-env rendering use the machine toolchain's canonical
  target name; unsupported profiling variants are reported explicitly
  via profilingTargetVariantError.
- Adapt the Store and ProjectPlanning unit tests to branch APIs.

Retargeted onto Machine/PerMachine. Build-machine plan JSON fields are
emitted only when cross-compiling, because onMachine Build errors
otherwise.

(cherry picked from commit 9e78589)
…dfdf)

Port 5d6dfdf (Split L4: preserve full machine triples through
configure) onto the cross-compilation-2 base, keeping cross2's
per-machine architecture and runner behavior:

- Classify Linux target triples with libc-ABI suffixes without losing
  ABI identity (System.hs), with unit coverage.
- configureTargetArgs/configureTargetDefaults select full machine
  triples per machine from the compiler Target platform properties;
  setupHsConfigureFlags keeps its plan argument (live on cross2 via
  determineCoverageFor) alongside the shared config.
- runConfigureScriptWithCompiler threads the selecting compiler (and
  cross2's program database) into the configure-script runner, which
  validates --host/--build against profiling target names.
- Adapt the configure-target unit tests to cross2 APIs (plan-taking
  setupHsConfigureFlags, PerMachine toolchains, constructor imports);
  the synthetic-configure check asserts ordered presence of the triples
  alongside the runner's own compiler selections.

Retargeted onto Machine/PerMachine. The Autoconf build alias uses the
build toolchain when one exists and the host toolchain otherwise, so a
non-cross plan does not call onMachine Build.

(cherry picked from commit 1319a80)
…7e3f8d)

Port 37e3f8d (Split L5: preserve configured toolchains and library
metadata) onto the cross-compilation-2 base, keeping cross2's
per-machine runner and program-database flow:

- Configure-script runner keeps its single entry point and program
  database; port L5's toolchain preservation as GHC/GHC_PKG environment
  entries and export canonicalisePathSeparator.
- Internal build tools propagate the package data directory to the
  configured GHC program for compile-time child processes.
- Install-from-source paths and package registration align internal
  library Haddock/HTML directories; exe install resolves -o targets.
- Add the missing build-package-dbs layer (legacy field, ProjectConfig
  field, lens, both grammars, combine) so build-machine package databases
  round-trip through legacy and Parsec project configuration.
- Adapt the toolchain roundtrip test to the flat cross2 shared config.

Kept cross2's build-machine program paths and arguments alongside the
new build-package-dbs fields.

(cherry picked from commit edf6fd4)
@andreabedini
andreabedini force-pushed the stable-haskell/feature/cross-compile-2 branch from 805164b to 278a24d Compare October 29, 2026 14:44

Copy link
Copy Markdown
Member

Hey Moritz, thanks for pulling this together. Heads up: the base has moved under you.

I've dropped the platform segment from the store in haskell#12356. The store is back to upstream's store/<compiler-id>-<abi>/. Only the inplace package db stays keyed by platform (dist-newstyle/packagedb/<platform>/<compiler-id>). Unit ids already hash the platform and the ABI, so nothing gets overwritten. Changing the store layout would hit every user (full rebuild, cabal path output, external tools) for a cross-only tidy-up. The new tip is 278a24d, and I've force-pushed stable-haskell/feature/cross-compile-2 there.

I don't think this hurts the graft:

  • 7295e48 already kept the existing store API rather than rewiring layouts.
  • The profiling split comes from the ABI tag (store/ghc-9.14.1-profiling), which survives.
  • betterPlatform only feeds plan JSON and tests.
  • Your harness lookup already accepts both layouts.

I did a trial rebase of the whole branch onto 278a24d, up to ebb2865:

  • Mostly mechanical: storeIncomingLock loses its Platform argument in each Split-x commit, and L7x's CmdPath stops passing toolchainPlatform to mkCompilerInfo.
  • One fix on top: testTargetAbi still passed a platform to the store functions, and the rebase silently restored a Prelude.hs return expression that refers to an unbound variable.
  • With that, the -Werror validate build is green, Store unit tests pass 3/3, and 25 testsuite tests pass with --with-build-compiler. ProfilingTargetAbi was skipped because it needs a profiling target. (Those runs predate ebb2865, which only touches the HaddockProject test.)

The trial is pushed as andrea/wip/graft-on-cross-compile-2-rebased (78774f0), so you can take it rather than redo it.

Before you rebase:

  1. Drop 9f09d43, e16ae11 and 95679d7 (rpaths), and 38aa457 (companion tools). They are already in the merge as andrea/wip/fork/relative-rpaths and andrea/wip/prep/target-prefixed-tools (same changes), and keeping them would collide there.
  2. Leave out adbcdb5, your fixup to the rename commit. It is a real fix (prs: 12356, RST underline), so I'll add it to Cross-compilation support - take two haskell/cabal#12356 myself.
  3. Autosquash the fixups.
  4. Retarget onto 278a24d.

stable-haskell/merge is already on the new base (b952b65, GHC harness green). stable-haskell/ghc#200 gets repinned to it next.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants