Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

haskell.compiler.ghc*: fix cross-built native GHC #243619

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 57 additions & 21 deletions pkgs/development/compilers/ghc/8.10.7.nix
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{ lib, stdenv, pkgsBuildTarget, pkgsHostTarget, targetPackages
{ lib, stdenv, pkgsBuildTarget, pkgsHostTarget, buildPackages, targetPackages

# build-tools
, bootPkgs
, autoconf, automake, coreutils, fetchpatch, fetchurl, perl, python3, m4, sphinx
, autoreconfHook, autoconf, automake, coreutils, fetchpatch, fetchurl, perl, python3, m4, sphinx
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you split out "add autoreconfHook" as a separate commit? That's a no-brainer, easy to approve.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest haskell-updates has dropped 8.8.4, so I don't think there will be much reason to do this once I've rebased. After rebasing, all the commit will do is add autoreconfHook and a patch for configure.ac.

, xattr, autoSignDarwinBinariesHook
, bash

Expand Down Expand Up @@ -44,25 +44,35 @@

, # Whether to build sphinx documentation.
enableDocs ? (
# Docs disabled if we are building on musl because it's a large task to keep
# all `sphinx` dependencies building in this environment.
!stdenv.buildPlatform.isMusl
# Docs disabled if we are building on musl or cross-building because it's a
# large task to keep all `sphinx` dependencies building in this environment.
(stdenv.buildPlatform == stdenv.hostPlatform && stdenv.targetPlatform == stdenv.hostPlatform)
&& !stdenv.buildPlatform.isMusl
)

, enableHaddockProgram ?
# Disabled for cross; see note [HADDOCK_DOCS].
(stdenv.targetPlatform == stdenv.hostPlatform)
(stdenv.buildPlatform == stdenv.hostPlatform && stdenv.targetPlatform == stdenv.hostPlatform)

, # Whether to disable the large address space allocator
# necessary fix for iOS: https://www.reddit.com/r/haskell/comments/4ttdz1/building_an_osxi386_to_iosarm64_cross_compiler/d5qvd67/
disableLargeAddressSpace ? stdenv.targetPlatform.isiOS

, # Whether to build an unregisterised version of GHC.
# GHC will normally auto-detect whether it can do a registered build, but this
# option will force it to do an unregistered build when set to true.
# See https://gitlab.haskell.org/ghc/ghc/-/wikis/building/unregisterised
enableUnregisterised ? false
}:

assert !enableIntegerSimple -> gmp != null;

# Cross cannot currently build the `haddock` program for silly reasons,
# see note [HADDOCK_DOCS].
assert (stdenv.targetPlatform != stdenv.hostPlatform) -> !enableHaddockProgram;
assert (stdenv.buildPlatform != stdenv.hostPlatform || stdenv.targetPlatform != stdenv.hostPlatform) -> !enableHaddockProgram;

# GHC does not support building when all 3 platforms are different.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nixpkgs doesn't either (but I guess we can keep the comment in case it does at some point in the future).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, though I agree about the future-proofing (for both the comment and the asserts).

assert stdenv.buildPlatform == stdenv.hostPlatform || stdenv.hostPlatform == stdenv.targetPlatform;

let
inherit (stdenv) buildPlatform hostPlatform targetPlatform;
Expand Down Expand Up @@ -129,7 +139,9 @@ let
pkgsBuildTarget.targetPackages.stdenv.cc
] ++ lib.optional useLLVM buildTargetLlvmPackages.llvm;

buildCC = buildPackages.stdenv.cc;
targetCC = builtins.head toolsForTarget;
installCC = pkgsHostTarget.targetPackages.stdenv.cc;

# Sometimes we have to dispatch between the bintools wrapper and the unwrapped
# derivation for certain tools depending on the platform.
Expand Down Expand Up @@ -161,14 +173,11 @@ let
(lib.optionalString enableIntegerSimple "-integer-simple")
];

in
targetLibffi = if hostPlatform != targetPlatform
then targetPackages.libffi
else pkgsHostTarget.libffi;

# C compiler, bintools and LLVM are used at build time, but will also leak into
# the resulting GHC's settings file and used at runtime. This means that we are
# currently only able to build GHC if hostPlatform == buildPlatform.
assert targetCC == pkgsHostTarget.targetPackages.stdenv.cc;
assert buildTargetLlvmPackages.llvm == llvmPackages.llvm;
assert stdenv.targetPlatform.isDarwin -> buildTargetLlvmPackages.clang == llvmPackages.clang;
in

stdenv.mkDerivation (rec {
version = "8.10.7";
Expand Down Expand Up @@ -223,6 +232,12 @@ stdenv.mkDerivation (rec {
stripLen = 3;
extraPrefix = "libraries/Cabal/Cabal/";
})

# We need to be able to set AR_STAGE0 and LD_STAGE0 when cross-compiling
(fetchpatch {
url = "https://gitlab.haskell.org/ghc/ghc/-/commit/8f7dd5710b80906ea7a3e15b7bb56a883a49fed8.patch";
hash = "sha256-C636Nq2U8YOG/av7XQmG3L1rU0bmC9/7m7Hty5pm5+s=";
})
sternenseemann marked this conversation as resolved.
Show resolved Hide resolved
] ++ lib.optionals stdenv.isDarwin [
# Make Block.h compile with c++ compilers. Remove with the next release
(fetchpatch {
Expand All @@ -241,6 +256,9 @@ stdenv.mkDerivation (rec {

postPatch = "patchShebangs .";

# GHC is unable to build a cross-compiler without this set.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more specific why this is needed? I'm guessing the build->build CC needs to be exposed as $CC?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I have no idea myself.

I figured out it was needed by diffing the environment variables between stdenv.mkDerivation and pkgsBuildTarget.stdenv.mkDerivation (IIRC).

"NIX_CC_WRAPPER_TARGET_HOST_${buildCC.suffixSalt}" = 1;

# GHC is a bit confused on its cross terminology.
# TODO(@sternenseemann): investigate coreutils dependencies and pass absolute paths
preConfigure = ''
Expand Down Expand Up @@ -269,6 +287,9 @@ stdenv.mkDerivation (rec {
# LLVM backend on Darwin needs clang: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/codegens.html#llvm-code-generator-fllvm
export CLANG="${buildTargetLlvmPackages.clang}/bin/${buildTargetLlvmPackages.clang.targetPrefix}clang"
'' + ''
export CC_STAGE0="${buildCC}/bin/${buildCC.targetPrefix}cc"
export LD_STAGE0="${buildCC.bintools}/bin/${buildCC.bintools.targetPrefix}ld"
export AR_STAGE0="${buildCC.bintools.bintools}/bin/${buildCC.bintools.targetPrefix}ar"

echo -n "${buildMK}" > mk/build.mk
sed -i -e 's|-isysroot /Developer/SDKs/MacOSX10.5.sdk||' configure
Expand Down Expand Up @@ -299,18 +320,22 @@ stdenv.mkDerivation (rec {
done
'';

# Although it is usually correct to pass --host, we don't do that here because
# GHC's usage of build, host, and target is non-standard.
# See https://gitlab.haskell.org/ghc/ghc/-/wikis/building/cross-compiling
# TODO(@Ericson2314): Always pass "--target" and always prefix.
configurePlatforms = [ "build" "host" ]
++ lib.optional (targetPlatform != hostPlatform) "target";
configurePlatforms = [ "build" ]
++ lib.optional (buildPlatform != hostPlatform || targetPlatform != hostPlatform) "target";

# `--with` flags for libraries needed for RTS linker
configureFlags = [
"--datadir=$doc/share/doc/ghc"
"--with-curses-includes=${ncurses.dev}/include" "--with-curses-libraries=${ncurses.out}/lib"
"--with-curses-includes=${buildPackages.ncurses.dev}/include"
"--with-curses-libraries=${buildPackages.ncurses.out}/lib"
] ++ lib.optionals (libffi != null) [
"--with-system-libffi"
"--with-ffi-includes=${targetPackages.libffi.dev}/include"
"--with-ffi-libraries=${targetPackages.libffi.out}/lib"
"--with-ffi-includes=${targetLibffi.dev}/include"
"--with-ffi-libraries=${targetLibffi.out}/lib"
] ++ lib.optionals (targetPlatform == hostPlatform && !enableIntegerSimple) [
"--with-gmp-includes=${targetPackages.gmp.dev}/include"
"--with-gmp-libraries=${targetPackages.gmp.out}/lib"
Expand All @@ -325,6 +350,8 @@ stdenv.mkDerivation (rec {
"CONF_GCC_LINKER_OPTS_STAGE2=-fuse-ld=gold"
] ++ lib.optionals (disableLargeAddressSpace) [
"--disable-large-address-space"
] ++ lib.optionals enableUnregisterised [
"--enable-unregisterised"
];

# Make sure we never relax`$PATH` and hooks support for compatibility.
Expand All @@ -334,7 +361,7 @@ stdenv.mkDerivation (rec {
dontAddExtraLibs = true;

nativeBuildInputs = [
perl autoconf automake m4 python3
perl autoreconfHook autoconf automake m4 python3
ghc bootPkgs.alex bootPkgs.happy bootPkgs.hscolour
] ++ lib.optionals (stdenv.isDarwin && stdenv.isAarch64) [
autoSignDarwinBinariesHook
Expand Down Expand Up @@ -372,6 +399,14 @@ stdenv.mkDerivation (rec {
requiredSystemFeatures = [ "big-parallel" ];

postInstall = ''
# Make the installed GHC use the host platform's tools.
sed -i $out/lib/${targetPrefix}${passthru.haskellCompilerName}/settings \
-e "s!$CC!${installCC}/bin/${installCC.targetPrefix}cc!g" \
-e "s!$CXX!${installCC}/bin/${installCC.targetPrefix}c++!g" \
-e "s!$LD!${installCC.bintools}/bin/${installCC.bintools.targetPrefix}ld${lib.optionalString useLdGold ".gold"}!g" \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here technically a separate useGold condition makes sense, but I don't think bintools can in practice differ between pkgsHostTarget and pkgsBuildTarget.

-e "s!$AR!${installCC.bintools.bintools}/bin/${installCC.bintools.targetPrefix}ar!g" \
-e "s!$RANLIB!${installCC.bintools.bintools}/bin/${installCC.bintools.targetPrefix}ranlib!g"

# Install the bash completion file.
install -D -m 444 utils/completion/ghc.bash $out/share/bash-completion/completions/${targetPrefix}ghc
'';
Expand All @@ -397,7 +432,8 @@ stdenv.mkDerivation (rec {
guibou
] ++ lib.teams.haskell.members;
timeout = 24 * 3600;
inherit (ghc.meta) license platforms;
platforms = lib.platforms.all;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is right, but means we need to find a new mechanism to implement this: #240348. Not sure how that should look tbh, there is not a lot of prior art on detecting if something can be boostrapped in nixpkgs…

Copy link
Contributor Author

@AlexandreTunstall AlexandreTunstall Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I think we'd want a way of recursively checking that the entire closure supports the build platform and isn't marked broken. I'm not aware of an existing function to check that.

Perhaps it'd be easier to give users the option to override writeShellApplication to disable the usage of ShellCheck.

If we still want to automatically choose whether to use it or not, how about this?

  1. Check if ShellCheck is marked broken.
  2. Check if the GHC used for ShellCheck is marked broken or unavailable.
  3. Check if the boot GHC used is marked broken or unavailable (repeat until we get to a pre-compiled GHC).

It's not general enough to be usable on everything in Nixpkgs, but it should at the very least prevent a regression here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I think we'd want a way of recursively checking that the entire closure supports the build platform and isn't marked broken

You can't do that in nixpkgs. This is the PR which adds that capability:

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
platforms = lib.platforms.all;
hydraPlatforms = lib.platforms.all;

As long as this is being changed, let's switch to the non-deprecated form...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there documentation about this? git grep -wne 'hydraPlatform' -- '*.md' doesn't find anything and looking for "platform" gives far too many matches.

inherit (ghc.meta) license;
};

} // lib.optionalAttrs targetPlatform.useAndroidPrebuilt {
Expand Down
68 changes: 49 additions & 19 deletions pkgs/development/compilers/ghc/9.0.2.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{ lib, stdenv, pkgsBuildTarget, pkgsHostTarget, targetPackages
{ lib, stdenv, pkgsBuildTarget, pkgsHostTarget, buildPackages, targetPackages

# build-tools
, bootPkgs
Expand Down Expand Up @@ -46,25 +46,35 @@

, # Whether to build sphinx documentation.
enableDocs ? (
# Docs disabled if we are building on musl because it's a large task to keep
# all `sphinx` dependencies building in this environment.
!stdenv.buildPlatform.isMusl
# Docs disabled if we are building on musl or cross-building because it's a
# large task to keep all `sphinx` dependencies building in this environment.
(stdenv.buildPlatform == stdenv.hostPlatform && stdenv.targetPlatform == stdenv.hostPlatform)
&& !stdenv.buildPlatform.isMusl
)

, enableHaddockProgram ?
# Disabled for cross; see note [HADDOCK_DOCS].
(stdenv.targetPlatform == stdenv.hostPlatform)
(stdenv.buildPlatform == stdenv.hostPlatform && stdenv.targetPlatform == stdenv.hostPlatform)

, # Whether to disable the large address space allocator
# necessary fix for iOS: https://www.reddit.com/r/haskell/comments/4ttdz1/building_an_osxi386_to_iosarm64_cross_compiler/d5qvd67/
disableLargeAddressSpace ? stdenv.targetPlatform.isiOS

, # Whether to build an unregisterised version of GHC.
# GHC will normally auto-detect whether it can do a registered build, but this
# option will force it to do an unregistered build when set to true.
# See https://gitlab.haskell.org/ghc/ghc/-/wikis/building/unregisterised
enableUnregisterised ? false
}:

assert !enableNativeBignum -> gmp != null;

# Cross cannot currently build the `haddock` program for silly reasons,
# see note [HADDOCK_DOCS].
assert (stdenv.targetPlatform != stdenv.hostPlatform) -> !enableHaddockProgram;
assert (stdenv.buildPlatform != stdenv.hostPlatform || stdenv.targetPlatform != stdenv.hostPlatform) -> !enableHaddockProgram;

# GHC does not support building when all 3 platforms are different.
assert stdenv.buildPlatform == stdenv.hostPlatform || stdenv.hostPlatform == stdenv.targetPlatform;

let
inherit (stdenv) buildPlatform hostPlatform targetPlatform;
Expand Down Expand Up @@ -129,7 +139,9 @@ let
pkgsBuildTarget.targetPackages.stdenv.cc
] ++ lib.optional useLLVM buildTargetLlvmPackages.llvm;

buildCC = buildPackages.stdenv.cc;
targetCC = builtins.head toolsForTarget;
installCC = pkgsHostTarget.targetPackages.stdenv.cc;

# Sometimes we have to dispatch between the bintools wrapper and the unwrapped
# derivation for certain tools depending on the platform.
Expand Down Expand Up @@ -161,14 +173,11 @@ let
(lib.optionalString enableNativeBignum "-native-bignum")
];

in
targetLibffi = if hostPlatform != targetPlatform
then targetPackages.libffi
else pkgsHostTarget.libffi;

# C compiler, bintools and LLVM are used at build time, but will also leak into
# the resulting GHC's settings file and used at runtime. This means that we are
# currently only able to build GHC if hostPlatform == buildPlatform.
assert targetCC == pkgsHostTarget.targetPackages.stdenv.cc;
assert buildTargetLlvmPackages.llvm == llvmPackages.llvm;
assert stdenv.targetPlatform.isDarwin -> buildTargetLlvmPackages.clang == llvmPackages.clang;
in

stdenv.mkDerivation (rec {
version = "9.0.2";
Expand Down Expand Up @@ -224,6 +233,9 @@ stdenv.mkDerivation (rec {
# GHC needs the locale configured during the Haddock phase.
LANG = "en_US.UTF-8";

# GHC is unable to build a cross-compiler without this set.
"NIX_CC_WRAPPER_TARGET_HOST_${buildCC.suffixSalt}" = 1;

# GHC is a bit confused on its cross terminology.
# TODO(@sternenseemann): investigate coreutils dependencies and pass absolute paths
preConfigure = ''
Expand Down Expand Up @@ -252,6 +264,9 @@ stdenv.mkDerivation (rec {
# LLVM backend on Darwin needs clang: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/codegens.html#llvm-code-generator-fllvm
export CLANG="${buildTargetLlvmPackages.clang}/bin/${buildTargetLlvmPackages.clang.targetPrefix}clang"
'' + ''
export CC_STAGE0="${buildCC}/bin/${buildCC.targetPrefix}cc"
export LD_STAGE0="${buildCC.bintools}/bin/${buildCC.bintools.targetPrefix}ld"
export AR_STAGE0="${buildCC.bintools.bintools}/bin/${buildCC.bintools.targetPrefix}ar"

echo -n "${buildMK}" > mk/build.mk
sed -i -e 's|-isysroot /Developer/SDKs/MacOSX10.5.sdk||' configure
Expand Down Expand Up @@ -281,18 +296,22 @@ stdenv.mkDerivation (rec {
done
'';

# Although it is usually correct to pass --host, we don't do that here because
# GHC's usage of build, host, and target is non-standard.
# See https://gitlab.haskell.org/ghc/ghc/-/wikis/building/cross-compiling
# TODO(@Ericson2314): Always pass "--target" and always prefix.
configurePlatforms = [ "build" "host" ]
++ lib.optional (targetPlatform != hostPlatform) "target";
configurePlatforms = [ "build" ]
++ lib.optional (buildPlatform != hostPlatform || targetPlatform != hostPlatform) "target";

# `--with` flags for libraries needed for RTS linker
configureFlags = [
"--datadir=$doc/share/doc/ghc"
"--with-curses-includes=${ncurses.dev}/include" "--with-curses-libraries=${ncurses.out}/lib"
"--with-curses-includes=${buildPackages.ncurses.dev}/include"
"--with-curses-libraries=${buildPackages.ncurses.out}/lib"
] ++ lib.optionals (libffi != null) [
"--with-system-libffi"
"--with-ffi-includes=${targetPackages.libffi.dev}/include"
"--with-ffi-libraries=${targetPackages.libffi.out}/lib"
"--with-ffi-includes=${targetLibffi.dev}/include"
"--with-ffi-libraries=${targetLibffi.out}/lib"
] ++ lib.optionals (targetPlatform == hostPlatform && !enableNativeBignum) [
"--with-gmp-includes=${targetPackages.gmp.dev}/include"
"--with-gmp-libraries=${targetPackages.gmp.out}/lib"
Expand All @@ -307,6 +326,8 @@ stdenv.mkDerivation (rec {
"CONF_GCC_LINKER_OPTS_STAGE2=-fuse-ld=gold"
] ++ lib.optionals (disableLargeAddressSpace) [
"--disable-large-address-space"
] ++ lib.optionals enableUnregisterised [
"--enable-unregisterised"
];

# Make sure we never relax`$PATH` and hooks support for compatibility.
Expand Down Expand Up @@ -358,6 +379,14 @@ stdenv.mkDerivation (rec {
requiredSystemFeatures = [ "big-parallel" ];

postInstall = ''
# Make the installed GHC use the host platform's tools.
sed -i $out/lib/${targetPrefix}${passthru.haskellCompilerName}/settings \
-e "s!$CC!${installCC}/bin/${installCC.targetPrefix}cc!g" \
-e "s!$CXX!${installCC}/bin/${installCC.targetPrefix}c++!g" \
-e "s!$LD!${installCC.bintools}/bin/${installCC.bintools.targetPrefix}ld${lib.optionalString useLdGold ".gold"}!g" \
-e "s!$AR!${installCC.bintools.bintools}/bin/${installCC.bintools.targetPrefix}ar!g" \
-e "s!$RANLIB!${installCC.bintools.bintools}/bin/${installCC.bintools.targetPrefix}ranlib!g"

# Install the bash completion file.
install -D -m 444 utils/completion/ghc.bash $out/share/bash-completion/completions/${targetPrefix}ghc
'';
Expand All @@ -383,7 +412,8 @@ stdenv.mkDerivation (rec {
guibou
] ++ lib.teams.haskell.members;
timeout = 24 * 3600;
inherit (ghc.meta) license platforms;
platforms = lib.platforms.all;
inherit (ghc.meta) license;
};

} // lib.optionalAttrs targetPlatform.useAndroidPrebuilt {
Expand Down
Loading