1{
2 lib,
3 stdenvNoCC,
4 cctools,
5 clang-unwrapped,
6 ld64,
7 llvm,
8 llvm-manpages,
9 makeWrapper,
10 enableManpages ? stdenvNoCC.targetPlatform == stdenvNoCC.hostPlatform,
11}:
12
13let
14 inherit (stdenvNoCC) targetPlatform hostPlatform;
15 targetPrefix = lib.optionalString (targetPlatform != hostPlatform) "${targetPlatform.config}-";
16
17 llvm_cmds = [
18 "addr2line"
19 "ar"
20 "c++filt"
21 "dwarfdump"
22 "dsymutil"
23 "nm"
24 "objcopy"
25 "objdump"
26 "otool"
27 "size"
28 "strings"
29 "strip"
30 ];
31
32 cctools_cmds = [
33 "codesign_allocate"
34 "gprof"
35 "ranlib"
36 # Use the cctools versions because the LLVM ones can crash or fail when the cctools ones don’t.
37 # Revisit when LLVM is updated to LLVM 18 on Darwin.
38 "lipo"
39 "install_name_tool"
40 ];
41
42 linkManPages =
43 pkg: source: target:
44 lib.optionalString enableManpages ''
45 sourcePath=${pkg}/share/man/man1/${source}.1.gz
46 targetPath=''${!outputMan}/share/man/man1/${target}.1.gz
47
48 if [ -f "$sourcePath" ]; then
49 mkdir -p "$(dirname "$targetPath")"
50 ln -s "$sourcePath" "$targetPath"
51 fi
52 '';
53in
54stdenvNoCC.mkDerivation {
55 pname = "${targetPrefix}cctools-binutils-darwin";
56 inherit (cctools) version;
57
58 outputs = [ "out" ] ++ lib.optional enableManpages "man";
59
60 strictDeps = true;
61
62 nativeBuildInputs = [ makeWrapper ];
63
64 buildCommand = ''
65 mkdir -p $out/bin $out/include
66
67 for tool in ${toString llvm_cmds}; do
68 # Translate between LLVM and traditional tool names (e.g., `c++filt` versus `cxxfilt`).
69 cctoolsTool=''${tool//-/_}
70 llvmTool=''${tool//++/xx}
71
72 # Some tools aren’t prefixed (like `dsymutil`).
73 llvmPath="${lib.getBin llvm}/bin"
74 if [ -e "$llvmPath/llvm-$llvmTool" ]; then
75 llvmTool=llvm-$llvmTool
76 elif [ -e "$llvmPath/${targetPrefix}$llvmTool" ]; then
77 llvmTool=${targetPrefix}$llvmTool
78 fi
79
80 # Not all tools are included in the bootstrap tools. Don’t link them if they don’t exist.
81 if [ -e "$llvmPath/$llvmTool" ]; then
82 ln -s "$llvmPath/$llvmTool" "$out/bin/${targetPrefix}$cctoolsTool"
83 fi
84 ${linkManPages llvm-manpages "$llvmTool" "$cctoolsTool"}
85 done
86
87 for tool in ${toString cctools_cmds}; do
88 toolsrc="${lib.getBin cctools}/bin/${targetPrefix}$tool"
89 if [ -e "$toolsrc" ]; then
90 ln -s "${lib.getBin cctools}/bin/${targetPrefix}$tool" "$out/bin/${targetPrefix}$tool"
91 fi
92 ${linkManPages (lib.getMan cctools) "$tool" "$tool"}
93 done
94 ${
95 # These are unprefixed because some tools expect to invoke them without it when cross-compiling to Darwin:
96 # - clang needs `dsymutil` when building with debug information;
97 # - meson needs `lipo` when cross-compiling to Darwin; and
98 # - meson also needs `install_name_tool` and `otool` when performing rpath cleanup on installation.
99 lib.optionalString (targetPrefix != "") ''
100 for bintool in dsymutil install_name_tool lipo otool; do
101 ln -s "$out/bin/${targetPrefix}$bintool" "$out/bin/$bintool"
102 done
103 ''
104 }
105 # Use the clang-integrated assembler. `as` in cctools is deprecated upstream and no longer built in nixpkgs.
106 makeWrapper "${lib.getBin clang-unwrapped}/bin/clang" "$out/bin/${targetPrefix}as" \
107 --add-flags "-x assembler -integrated-as -c"
108
109 ln -s '${lib.getBin ld64}/bin/ld' "$out/bin/${targetPrefix}ld"
110 ${linkManPages (lib.getMan ld64) "ld" "ld"}
111 ${linkManPages (lib.getMan ld64) "ld-classic" "ld-classic"}
112 ${linkManPages (lib.getMan ld64) "ld64" "ld64"}
113 '';
114
115 __structuredAttrs = true;
116
117 passthru = {
118 inherit cctools_cmds llvm_cmds targetPrefix;
119 isCCTools = true; # The fact ld64 is used instead of lld is why this isn’t `isLLVM`.
120 };
121
122 meta = {
123 maintainers = with lib.maintainers; [ reckenrode ];
124 priority = 10;
125 };
126}