about summary refs log tree commit diff
path: root/pkgs/development/compilers/ghc/common-hadrian.nix
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/development/compilers/ghc/common-hadrian.nix')
-rw-r--r--pkgs/development/compilers/ghc/common-hadrian.nix220
1 files changed, 178 insertions, 42 deletions
diff --git a/pkgs/development/compilers/ghc/common-hadrian.nix b/pkgs/development/compilers/ghc/common-hadrian.nix
index 31d1e52ea95b..7bb2135779a4 100644
--- a/pkgs/development/compilers/ghc/common-hadrian.nix
+++ b/pkgs/development/compilers/ghc/common-hadrian.nix
@@ -10,8 +10,10 @@
 
 { lib
 , stdenv
+, stdenvNoCC
 , pkgsBuildTarget
 , pkgsHostTarget
+, buildPackages
 , targetPackages
 , fetchpatch
 
@@ -58,15 +60,23 @@
 , # If enabled, use -fPIC when compiling static libs.
   enableRelocatedStaticLibs ? stdenv.targetPlatform != stdenv.hostPlatform
 
-, enableProfiledLibs ? true
+  # Exceeds Hydra output limit (at the time of writing ~3GB) when cross compiled to riscv64.
+  # A riscv64 cross-compiler fits into the limit comfortably.
+, enableProfiledLibs ? !stdenv.hostPlatform.isRiscV64
 
 , # Whether to build dynamic libs for the standard library (on the target
   # platform). Static libs are always built.
   enableShared ? with stdenv.targetPlatform; !isWindows && !useiOSPrebuilt && !isStatic && !isGhcjs
 
 , # Whether to build terminfo.
+  # FIXME(@sternenseemann): This actually doesn't influence what hadrian does,
+  # just what buildInputs etc. looks like. It would be best if we could actually
+  # tell it what to do like it was possible with make.
   enableTerminfo ? !(stdenv.targetPlatform.isWindows
-                     || stdenv.targetPlatform.isGhcjs)
+                     || stdenv.targetPlatform.isGhcjs
+                     # terminfo can't be built for cross
+                     || (stdenv.buildPlatform != stdenv.hostPlatform)
+                     || (stdenv.hostPlatform != stdenv.targetPlatform))
 
 , # Libdw.c only supports x86_64, i686 and s390x as of 2022-08-04
   enableDwarf ? (stdenv.targetPlatform.isx86 ||
@@ -117,9 +127,8 @@
     -- no way to set this via the command line
     finalStage :: Stage
     finalStage = ${
-      # Always build the stage 2 compiler if possible. Note we can currently
-      # assume hostPlatform == buildPlatform.
-      # TODO(@sternenseemann): improve this condition when we can cross-compile GHC
+      # Always build the stage 2 compiler if possible.
+      # TODO(@sternensemann): unify condition with make-built GHCs
       if stdenv.hostPlatform.canExecute stdenv.targetPlatform
       then "Stage2" # native compiler or “native” cross e.g. pkgsStatic
       else "Stage1" # cross compiler
@@ -160,10 +169,11 @@
 
       patches =
         let
-          # Disable haddock generating pretty source listings to stay under 3GB on aarch64-linux
           enableHyperlinkedSource =
-            lib.versionAtLeast version "9.8" ||
-            !(stdenv.hostPlatform.isAarch64 && stdenv.hostPlatform.isLinux);
+            # Disable haddock generating pretty source listings to stay under 3GB on aarch64-linux
+            !(stdenv.hostPlatform.isAarch64 && stdenv.hostPlatform.isLinux)
+            # 9.8 and 9.10 don't run into this problem for some reason
+            || (lib.versionAtLeast version "9.8" && lib.versionOlder version "9.11");
         in
         [
           # Fix docs build with Sphinx >= 7 https://gitlab.haskell.org/ghc/ghc/-/issues/24129
@@ -178,6 +188,15 @@
             hash = "sha256-MpvTmFFsNiPDoOp9BhZyWeapeibQ77zgEV+xzZ1UAXs=";
           })
         ]
+        ++ lib.optionals (lib.versionAtLeast version "9.6" && lib.versionOlder version "9.8") [
+          # Fix unlit being installed under a different name than is used in the
+          # settings file: https://gitlab.haskell.org/ghc/ghc/-/issues/23317
+          (fetchpatch {
+            name = "ghc-9.6-fix-unlit-path.patch";
+            url = "https://gitlab.haskell.org/ghc/ghc/-/commit/8fde4ac84ec7b1ead238cb158bbef48555d12af9.patch";
+            hash = "sha256-3+CyRBpebEZi8YpS22SsdGQHqi0drR7cCKPtKbR3zyE=";
+          })
+        ]
         ++ lib.optionals (stdenv.targetPlatform.isDarwin && stdenv.targetPlatform.isAarch64) [
           # Prevent the paths module from emitting symbols that we don't use
           # when building with separate outputs.
@@ -189,18 +208,24 @@
            then ./Cabal-at-least-3.6-paths-fix-cycle-aarch64-darwin.patch
            else ./Cabal-3.12-paths-fix-cycle-aarch64-darwin.patch)
         ]
-        # Prevents passing --hyperlinked-source to haddock. This is a custom
-        # workaround as we wait for this to be configurable via userSettings or
-        # similar. https://gitlab.haskell.org/ghc/ghc/-/issues/23625
+        # Prevents passing --hyperlinked-source to haddock. Note that this can
+        # be configured via a user defined flavour now. Unfortunately, it is
+        # impossible to import an existing flavour in UserSettings, so patching
+        # the defaults is actually simpler and less maintenance intensive
+        # compared to keeping an entire flavour definition in sync with upstream
+        # manually. See also https://gitlab.haskell.org/ghc/ghc/-/issues/23625
         ++ lib.optionals (!enableHyperlinkedSource) [
-          # TODO(@sternenseemann): Doesn't apply for GHC >= 9.8
-          ../../tools/haskell/hadrian/disable-hyperlinked-source.patch
+          (if lib.versionOlder version "9.8"
+           then ../../tools/haskell/hadrian/disable-hyperlinked-source-pre-9.8.patch
+           else ../../tools/haskell/hadrian/disable-hyperlinked-source-extra-args.patch)
         ]
         # Incorrect bounds on Cabal in hadrian
         # https://gitlab.haskell.org/ghc/ghc/-/issues/24100
         ++ lib.optionals (lib.elem version [ "9.8.1" "9.8.2" ]) [
           ../../tools/haskell/hadrian/hadrian-9.8.1-allow-Cabal-3.10.patch
         ];
+
+      stdenv = stdenvNoCC;
     }
 
   # GHC's build system hadrian built from the GHC-to-build's source tree
@@ -222,15 +247,25 @@
 , # 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;
 
+# GHC does not support building when all 3 platforms are different.
+assert stdenv.buildPlatform == stdenv.hostPlatform || stdenv.hostPlatform == stdenv.targetPlatform;
+
+# It is currently impossible to cross-compile GHC with Hadrian.
+assert stdenv.buildPlatform == stdenv.hostPlatform;
+
 let
   inherit (stdenv) buildPlatform hostPlatform targetPlatform;
 
-  inherit (bootPkgs) ghc;
-
   # TODO(@Ericson2314) Make unconditional
   targetPrefix = lib.optionalString
     (targetPlatform != hostPlatform)
@@ -265,7 +300,12 @@ let
      else pkgsBuildTarget.targetPackages.stdenv.cc)
   ] ++ lib.optional useLLVM buildTargetLlvmPackages.llvm;
 
+  buildCC = buildPackages.stdenv.cc;
   targetCC = builtins.head toolsForTarget;
+  installCC =
+    if targetPlatform.isGhcjs
+    then pkgsHostTarget.emscripten
+    else pkgsHostTarget.targetPackages.stdenv.cc;
 
   # toolPath calculates the absolute path to the name tool associated with a
   # given `stdenv.cc` derivation, i.e. it picks the correct derivation to take
@@ -276,12 +316,13 @@ let
       tools = {
         "cc" = cc;
         "c++" = cc;
-        as = cc.bintools.bintools;
+        as = cc.bintools;
 
-        ar = cc.bintools.bintools;
-        ranlib = cc.bintools.bintools;
-        nm = cc.bintools.bintools;
-        readelf = cc.bintools.bintools;
+        ar = cc.bintools;
+        ranlib = cc.bintools;
+        nm = cc.bintools;
+        readelf = cc.bintools;
+        objdump = cc.bintools;
 
         ld = cc.bintools;
         "ld.gold" = cc.bintools;
@@ -300,6 +341,9 @@ let
           if stdenv.targetPlatform.isDarwin
           then cc.bintools
           else cc.bintools.bintools;
+
+        # clang is used as an assembler on darwin with the LLVM backend
+        clang = cc;
       }.${name};
     in
     "${tools}/bin/${tools.targetPrefix}${name}";
@@ -316,14 +360,34 @@ let
     (lib.optionalString enableNativeBignum "-native-bignum")
   ];
 
-in
+  # These libraries are library dependencies of the standard libraries bundled
+  # by GHC (core libs) users will link their compiled artifacts again. Thus,
+  # they should be taken from targetPackages.
+  #
+  # We need to use pkgsHostTarget if we are cross compiling a native GHC compiler,
+  # though (when native compiling GHC, pkgsHostTarget == targetPackages):
+  #
+  # 1. targetPackages would be empty(-ish) in this situation since we can't
+  #    execute cross compiled compilers in order to obtain the libraries
+  #    that would be in targetPackages.
+  # 2. pkgsHostTarget is fine to use since hostPlatform == targetPlatform in this
+  #    situation.
+  # 3. The core libs used by the final GHC (stage 2) for user artifacts are also
+  #    used to build stage 2 GHC itself, i.e. the core libs are both host and
+  #    target.
+  targetLibs = {
+    inherit
+      (if hostPlatform != targetPlatform then targetPackages else pkgsHostTarget)
+      elfutils
+      gmp
+      libffi
+      ncurses;
+  };
+
+  # Our Cabal compiler name
+  haskellCompilerName = "ghc-${version}";
 
-# 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 !targetPlatform.isGhcjs -> targetCC == pkgsHostTarget.targetPackages.stdenv.cc;
-assert buildTargetLlvmPackages.llvm == llvmPackages.llvm;
-assert stdenv.targetPlatform.isDarwin -> buildTargetLlvmPackages.clang == llvmPackages.clang;
+in
 
 stdenv.mkDerivation ({
   pname = "${targetPrefix}ghc${variantSuffix}";
@@ -346,6 +410,13 @@ stdenv.mkDerivation ({
     for env in $(env | grep '^TARGET_' | sed -E 's|\+?=.*||'); do
       export "''${env#TARGET_}=''${!env}"
     done
+    # No need for absolute paths since these tools only need to work during the build
+    export CC_STAGE0="$CC_FOR_BUILD"
+    export LD_STAGE0="$LD_FOR_BUILD"
+    export AR_STAGE0="$AR_FOR_BUILD"
+
+    # Stage0 (build->build) which builds stage 1
+    export GHC="${bootPkgs.ghc}/bin/ghc"
     # GHC is a bit confused on its cross terminology, as these would normally be
     # the *host* tools.
     export CC="${toolPath "cc" targetCC}"
@@ -358,6 +429,7 @@ stdenv.mkDerivation ({
     export RANLIB="${toolPath "ranlib" targetCC}"
     export READELF="${toolPath "readelf" targetCC}"
     export STRIP="${toolPath "strip" targetCC}"
+    export OBJDUMP="${toolPath "objdump" targetCC}"
   '' + lib.optionalString (stdenv.targetPlatform.linker == "cctools") ''
     export OTOOL="${toolPath "otool" targetCC}"
     export INSTALL_NAME_TOOL="${toolPath "install_name_tool" targetCC}"
@@ -366,7 +438,17 @@ stdenv.mkDerivation ({
     export OPT="${lib.getBin buildTargetLlvmPackages.llvm}/bin/opt"
   '' + lib.optionalString (useLLVM && stdenv.targetPlatform.isDarwin) ''
     # 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"
+    # The executable we specify via $CLANG is used as an assembler (exclusively, it seems, but this isn't
+    # clarified in any user facing documentation). As such, it'll be called on assembly produced by $CC
+    # which usually comes from the darwin stdenv. To prevent a situation where $CLANG doesn't understand
+    # the assembly it is given, we need to make sure that it matches the LLVM version of $CC if possible.
+    # It is unclear (at the time of writing 2024-09-01)  whether $CC should match the LLVM version we use
+    # for llc and opt which would require using a custom darwin stdenv for targetCC.
+    export CLANG="${
+      if targetCC.isClang
+      then toolPath "clang" targetCC
+      else "${buildTargetLlvmPackages.clang}/bin/${buildTargetLlvmPackages.clang.targetPrefix}clang"
+    }"
   '' +
   lib.optionalString (stdenv.hostPlatform.isLinux && hostPlatform.libc == "glibc") ''
     export LOCALE_ARCHIVE="${glibcLocales}/lib/locale/locale-archive"
@@ -433,14 +515,16 @@ stdenv.mkDerivation ({
   # `--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"
+  ] ++ lib.optionals enableTerminfo [
+    "--with-curses-includes=${lib.getDev targetLibs.ncurses}/include"
+    "--with-curses-libraries=${lib.getLib targetLibs.ncurses}/lib"
   ] ++ lib.optionals (libffi != null && !targetPlatform.isGhcjs) [
     "--with-system-libffi"
-    "--with-ffi-includes=${targetPackages.libffi.dev}/include"
-    "--with-ffi-libraries=${targetPackages.libffi.out}/lib"
+    "--with-ffi-includes=${targetLibs.libffi.dev}/include"
+    "--with-ffi-libraries=${targetLibs.libffi.out}/lib"
   ] ++ lib.optionals (targetPlatform == hostPlatform && !enableNativeBignum) [
-    "--with-gmp-includes=${targetPackages.gmp.dev}/include"
-    "--with-gmp-libraries=${targetPackages.gmp.out}/lib"
+    "--with-gmp-includes=${targetLibs.gmp.dev}/include"
+    "--with-gmp-libraries=${targetLibs.gmp.out}/lib"
   ] ++ lib.optionals (targetPlatform == hostPlatform && hostPlatform.libc != "glibc" && !targetPlatform.isWindows) [
     "--with-iconv-includes=${libiconv}/include"
     "--with-iconv-libraries=${libiconv}/lib"
@@ -454,14 +538,16 @@ stdenv.mkDerivation ({
     "--disable-large-address-space"
   ] ++ lib.optionals enableDwarf [
     "--enable-dwarf-unwind"
-    "--with-libdw-includes=${lib.getDev targetPackages.elfutils}/include"
-    "--with-libdw-libraries=${lib.getLib targetPackages.elfutils}/lib"
+    "--with-libdw-includes=${lib.getDev targetLibs.elfutils}/include"
+    "--with-libdw-libraries=${lib.getLib targetLibs.elfutils}/lib"
   ] ++ lib.optionals targetPlatform.isDarwin [
     # Darwin uses llvm-ar. GHC will try to use `-L` with `ar` when it is `llvm-ar`
     # but it doesn’t currently work because Cabal never uses `-L` on Darwin. See:
     # https://gitlab.haskell.org/ghc/ghc/-/issues/23188
     # https://github.com/haskell/cabal/issues/8882
     "fp_cv_prog_ar_supports_dash_l=no"
+  ] ++ lib.optionals enableUnregisterised [
+    "--enable-unregisterised"
   ];
 
   # Make sure we never relax`$PATH` and hooks support for compatibility.
@@ -471,11 +557,13 @@ stdenv.mkDerivation ({
   dontAddExtraLibs = true;
 
   nativeBuildInputs = [
-    perl ghc hadrian bootPkgs.alex bootPkgs.happy bootPkgs.hscolour
+    perl hadrian bootPkgs.alex bootPkgs.happy bootPkgs.hscolour
     # autoconf and friends are necessary for hadrian to create the bindist
     autoconf automake m4
     # Python is used in a few scripts invoked by hadrian to generate e.g. rts headers.
     python3
+    # Tool used to update GHC's settings file in postInstall
+    bootPkgs.ghc-settings-edit
   ] ++ lib.optionals (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64) [
     autoSignDarwinBinariesHook
   ] ++ lib.optionals enableDocs [
@@ -484,8 +572,25 @@ stdenv.mkDerivation ({
 
   # For building runtime libs
   depsBuildTarget = toolsForTarget;
+  # Everything the stage0 compiler needs to build stage1: CC, bintools, extra libs.
+  # See also GHC, {CC,LD,AR}_STAGE0 in preConfigure.
+  depsBuildBuild = [
+    # N.B. We do not declare bootPkgs.ghc in any of the stdenv.mkDerivation
+    # dependency lists to prevent the bintools setup hook from adding ghc's
+    # lib directory to the linker flags. Instead we tell configure about it
+    # via the GHC environment variable.
+    buildCC
+    # stage0 builds terminfo unconditionally, so we always need ncurses
+    ncurses
+  ];
+
+  # Prevent stage0 ghc from leaking into the final result. This was an issue
+  # with GHC 9.6.
+  disallowedReferences = [
+    bootPkgs.ghc
+  ];
 
-  buildInputs = [ perl bash ] ++ (libDeps hostPlatform);
+  buildInputs = [ bash ] ++ (libDeps hostPlatform);
 
   depsTargetTarget = map lib.getDev (libDeps targetPlatform);
   depsTargetTargetPropagated = map (lib.getOutput "out") (libDeps targetPlatform);
@@ -553,19 +658,49 @@ stdenv.mkDerivation ({
     # leave bindist directory
     popd
 
+    settingsFile="$out/lib/${targetPrefix}${haskellCompilerName}/lib/settings"
+
+    # Make the installed GHC use the host->target tools.
+    ghc-settings-edit "$settingsFile" \
+      "C compiler command" "${toolPath "cc" installCC}" \
+      "Haskell CPP command" "${toolPath "cc" installCC}" \
+      "C++ compiler command" "${toolPath "c++" installCC}" \
+      "ld command" "${toolPath "ld${lib.optionalString useLdGold ".gold"}" installCC}" \
+      "Merge objects command" "${toolPath "ld${lib.optionalString useLdGold ".gold"}" installCC}" \
+      "ar command" "${toolPath "ar" installCC}" \
+      "ranlib command" "${toolPath "ranlib" installCC}"
+  ''
+  + lib.optionalString (stdenv.targetPlatform.linker == "cctools") ''
+    ghc-settings-edit "$settingsFile" \
+      "otool command" "${toolPath "otool" installCC}" \
+      "install_name_tool command" "${toolPath "install_name_tool" installCC}"
+  ''
+  + lib.optionalString useLLVM ''
+    ghc-settings-edit "$settingsFile" \
+      "LLVM llc command" "${lib.getBin llvmPackages.llvm}/bin/llc" \
+      "LLVM opt command" "${lib.getBin llvmPackages.llvm}/bin/opt"
+  ''
+  + lib.optionalString (useLLVM && stdenv.targetPlatform.isDarwin) ''
+    ghc-settings-edit "$settingsFile" \
+      "LLVM clang command" "${
+        # See comment for CLANG in preConfigure
+        if installCC.isClang
+        then toolPath "clang" installCC
+        else "${llvmPackages.clang}/bin/${llvmPackages.clang.targetPrefix}clang"
+      }"
+  ''
+  + ''
+
     # Install the bash completion file.
     install -Dm 644 utils/completion/ghc.bash $out/share/bash-completion/completions/${targetPrefix}ghc
   '';
 
   passthru = {
-    inherit bootPkgs targetPrefix;
+    inherit bootPkgs targetPrefix haskellCompilerName;
 
     inherit llvmPackages;
     inherit enableShared;
 
-    # Our Cabal compiler name
-    haskellCompilerName = "ghc-${version}";
-
     # Expose hadrian used for bootstrapping, for debugging purposes
     inherit hadrian;
 
@@ -581,7 +716,8 @@ stdenv.mkDerivation ({
       guibou
     ] ++ lib.teams.haskell.members;
     timeout = 24 * 3600;
-    inherit (ghc.meta) license platforms;
+    platforms = lib.platforms.all;
+    inherit (bootPkgs.ghc.meta) license;
   };
 
   dontStrip = targetPlatform.useAndroidPrebuilt || targetPlatform.isWasm;