1{
2 callPackage,
3 fetchgit,
4 fontconfig,
5 git,
6 lib,
7 makeWrapper,
8 python3,
9 runCommand,
10 writeTextFile,
11
12 # Artifacts dependencies
13 fetchurl,
14 gcc,
15 glibc,
16 pkgs,
17 stdenv,
18
19 julia,
20
21 # Special registry which is equal to JuliaRegistries/General, but every Versions.toml
22 # entry is augmented with a Nix sha256 hash
23 augmentedRegistry ? callPackage ./registry.nix { },
24
25 # Other overridable arguments
26 extraLibs ? [ ],
27 juliaCpuTarget ? null,
28 makeTransitiveDependenciesImportable ? false, # Used to support symbol indexing
29 makeWrapperArgs ? "",
30 packageOverrides ? { },
31 precompile ? true,
32 setDefaultDepot ? true,
33}:
34
35packageNames:
36
37let
38 util = callPackage ./util.nix { };
39
40 # Some Julia packages require access to Python. Provide a Nixpkgs version so it
41 # doesn't try to install its own.
42 pythonToUse =
43 let
44 extraPythonPackages = (
45 (callPackage ./extra-python-packages.nix { inherit python3; }).getExtraPythonPackages packageNames
46 );
47 in
48 (
49 if extraPythonPackages == [ ] then
50 python3
51 else
52 util.addPackagesToPython python3 (map (pkg: lib.getAttr pkg python3.pkgs) extraPythonPackages)
53 );
54
55 # Start by wrapping Julia so it has access to Python and any other extra libs.
56 # Also, prevent various packages (CondaPkg.jl, PythonCall.jl) from trying to do network calls.
57 juliaWrapped =
58 runCommand "julia-${julia.version}-wrapped"
59 {
60 nativeBuildInputs = [ makeWrapper ];
61 inherit makeWrapperArgs;
62 }
63 ''
64 mkdir -p $out/bin
65 makeWrapper ${julia}/bin/julia $out/bin/julia \
66 --suffix LD_LIBRARY_PATH : "${lib.makeLibraryPath extraLibs}" \
67 --set FONTCONFIG_FILE ${fontconfig.out}/etc/fonts/fonts.conf \
68 --set PYTHONHOME "${pythonToUse}" \
69 --prefix PYTHONPATH : "${pythonToUse}/${pythonToUse.sitePackages}" \
70 --set PYTHON ${pythonToUse}/bin/python $makeWrapperArgs \
71 --set JULIA_CONDAPKG_OFFLINE yes \
72 --set JULIA_CONDAPKG_BACKEND Null \
73 --set JULIA_PYTHONCALL_EXE "@PyCall"
74 '';
75
76 # If our closure ends up with certain packages, add others.
77 packageImplications = {
78 # Because we want to put PythonCall in PyCall mode so it doesn't try to download
79 # Python packages
80 PythonCall = [ "PyCall" ];
81 };
82
83 # Invoke Julia resolution logic to determine the full dependency closure. Also
84 # gather information on the Julia standard libraries, which we'll need to
85 # generate a Manifest.toml.
86 packageOverridesRepoified = lib.mapAttrs util.repoifySimple packageOverrides;
87 closureYaml = callPackage ./package-closure.nix {
88 inherit
89 augmentedRegistry
90 julia
91 packageNames
92 packageImplications
93 ;
94 packageOverrides = packageOverridesRepoified;
95 };
96 stdlibInfos = callPackage ./stdlib-infos.nix {
97 inherit julia;
98 };
99
100 # Generate a Nix file consisting of a map from dependency UUID --> package info with fetchgit call:
101 # {
102 # "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" = {
103 # src = fetchgit {...};
104 # name = "...";
105 # version = "...";
106 # treehash = "...";
107 # };
108 # ...
109 # }
110 dependencies =
111 runCommand "julia-sources.nix"
112 {
113 buildInputs = [
114 (python3.withPackages (
115 ps: with ps; [
116 toml
117 pyyaml
118 ]
119 ))
120 git
121 ];
122 }
123 ''
124 python ${./python}/sources_nix.py \
125 "${augmentedRegistry}" \
126 '${lib.generators.toJSON { } packageOverridesRepoified}' \
127 "${closureYaml}" \
128 "$out"
129 '';
130
131 # Import the Nix file from the previous step (IFD) and turn each dependency repo into
132 # a dummy Git repository, as Julia expects. Format the results as a YAML map from
133 # dependency UUID -> Nix store location:
134 # {
135 # "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3":"/nix/store/...-NaNMath.jl-0877504",
136 # ...
137 # }
138 # This is also the point where we apply the packageOverrides.
139 dependencyUuidToInfo = import dependencies { inherit fetchgit; };
140 fillInOverrideSrc =
141 uuid: info:
142 if lib.hasAttr info.name packageOverrides then
143 (info // { src = lib.getAttr info.name packageOverrides; })
144 else
145 info;
146 dependencyUuidToRepo = lib.mapAttrs util.repoifyInfo (
147 lib.mapAttrs fillInOverrideSrc dependencyUuidToInfo
148 );
149 dependencyUuidToRepoYaml = writeTextFile {
150 name = "dependency-uuid-to-repo.yml";
151 text = lib.generators.toYAML { } dependencyUuidToRepo;
152 };
153
154 # Given the augmented registry, closure info yaml, and dependency path yaml, construct a complete
155 # Julia registry containing all the necessary packages
156 dependencyUuidToInfoYaml = writeTextFile {
157 name = "dependency-uuid-to-info.yml";
158 text = lib.generators.toYAML { } dependencyUuidToInfo;
159 };
160 fillInOverrideSrc' =
161 uuid: info:
162 if lib.hasAttr info.name packageOverridesRepoified then
163 (info // { src = lib.getAttr info.name packageOverridesRepoified; })
164 else
165 info;
166 overridesOnly = lib.mapAttrs fillInOverrideSrc' (
167 lib.filterAttrs (uuid: info: info.src == null) dependencyUuidToInfo
168 );
169 minimalRegistry =
170 runCommand "minimal-julia-registry"
171 {
172 buildInputs = [
173 (python3.withPackages (
174 ps: with ps; [
175 toml
176 pyyaml
177 ]
178 ))
179 git
180 ];
181 }
182 ''
183 python ${./python}/minimal_registry.py \
184 "${augmentedRegistry}" \
185 "${closureYaml}" \
186 '${lib.generators.toJSON { } overridesOnly}' \
187 "${dependencyUuidToRepoYaml}" \
188 "$out"
189 '';
190 project =
191 runCommand "julia-project"
192 {
193 buildInputs = [
194 (python3.withPackages (
195 ps: with ps; [
196 toml
197 pyyaml
198 ]
199 ))
200 git
201 ];
202 }
203 ''
204 python ${./python}/project.py \
205 "${closureYaml}" \
206 "${stdlibInfos}" \
207 '${lib.generators.toJSON { } overridesOnly}' \
208 "${dependencyUuidToRepoYaml}" \
209 "$out"
210 '';
211
212 # Next, deal with artifacts. Scan each artifacts file individually and generate a Nix file that
213 # produces the desired Overrides.toml.
214 artifactsNix =
215 runCommand "julia-artifacts.nix"
216 {
217 buildInputs = [
218 (python3.withPackages (
219 ps: with ps; [
220 toml
221 pyyaml
222 ]
223 ))
224 ];
225 }
226 ''
227 python ${./python}/extract_artifacts.py \
228 "${dependencyUuidToRepoYaml}" \
229 "${closureYaml}" \
230 "${juliaWrapped}/bin/julia" \
231 "${
232 if lib.versionAtLeast julia.version "1.7" then ./extract_artifacts.jl else ./extract_artifacts_16.jl
233 }" \
234 '${lib.generators.toJSON { } (import ./extra-libs.nix)}' \
235 '${lib.generators.toJSON { } (stdenv.hostPlatform.isDarwin)}' \
236 "$out"
237 '';
238
239 # Import the artifacts Nix to build Overrides.toml (IFD)
240 artifacts = import artifactsNix (
241 {
242 inherit
243 lib
244 fetchurl
245 pkgs
246 stdenv
247 ;
248 }
249 // lib.optionalAttrs (!stdenv.targetPlatform.isDarwin) {
250 inherit gcc glibc;
251 }
252 );
253 overridesJson = writeTextFile {
254 name = "Overrides.json";
255 text = lib.generators.toJSON { } artifacts;
256 };
257 overridesToml =
258 runCommand "Overrides.toml" { buildInputs = [ (python3.withPackages (ps: with ps; [ toml ])) ]; }
259 ''
260 python ${./python}/format_overrides.py \
261 "${overridesJson}" \
262 "$out"
263 '';
264
265 # Build a Julia project and depot under $out/project and $out/depot respectively
266 projectAndDepot = callPackage ./depot.nix {
267 inherit
268 closureYaml
269 extraLibs
270 juliaCpuTarget
271 overridesToml
272 packageImplications
273 precompile
274 ;
275 julia = juliaWrapped;
276 inherit project;
277 registry = minimalRegistry;
278 };
279
280in
281
282runCommand "julia-${julia.version}-env"
283 {
284 nativeBuildInputs = [ makeWrapper ];
285
286 passthru = {
287 inherit julia;
288 inherit juliaWrapped;
289 inherit (julia) pname version meta;
290
291 # Expose the steps we used along the way in case the user wants to use them, for example to build
292 # expressions and build them separately to avoid IFD.
293 inherit dependencies;
294 inherit closureYaml;
295 inherit dependencyUuidToInfoYaml;
296 inherit dependencyUuidToRepoYaml;
297 inherit minimalRegistry;
298 inherit artifactsNix;
299 inherit overridesJson;
300 inherit overridesToml;
301 inherit project;
302 inherit projectAndDepot;
303 inherit stdlibInfos;
304 };
305 }
306 (
307 ''
308 mkdir -p $out/bin
309 makeWrapper ${juliaWrapped}/bin/julia $out/bin/julia \
310 --suffix JULIA_DEPOT_PATH : "${projectAndDepot}/depot" \
311 --set-default JULIA_PROJECT "${projectAndDepot}/project" \
312 --set-default JULIA_LOAD_PATH '@:${projectAndDepot}/project/Project.toml:@v#.#:@stdlib'
313 ''
314 + lib.optionalString setDefaultDepot ''
315 sed -i '2 i\JULIA_DEPOT_PATH=''${JULIA_DEPOT_PATH-"$HOME/.julia"}' $out/bin/julia
316 ''
317 )