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

Julia build system #208379

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d035d11
julia: Add Julia package build system as 'withPackages' passthru
fedeinthemix Dec 23, 2022
0f6a870
julia2nix: Init at 20221231
fedeinthemix Dec 30, 2022
f460f6e
julia: Add a NixOS release-note entry about the addition of withPacka…
fedeinthemix Dec 31, 2022
1855c80
julia: use 'isELF' and 'stdenv.cc.bintools.dynamicLinker' in build-ju…
fedeinthemix Dec 31, 2022
c88332d
julia: Document use of withPackages and juliaPackages attributes
fedeinthemix Dec 31, 2022
98dc845
julia2nix.jl: Add pkg_slug function
fedeinthemix Jan 1, 2023
014bcbb
julia2nix.jl: Allow to specify package version with 'Pkg' syntax
fedeinthemix Jan 1, 2023
5053486
julia2nix.jl: Add option to use Project.toml file
fedeinthemix Jan 1, 2023
83ba3f8
julia2nix.jl: Add short varsion of options and improve error handling
fedeinthemix Jan 2, 2023
36c2e7a
julia: Refine build system
fedeinthemix Jan 2, 2023
d6e4c7b
julia2nix.jl: include lazy artifacts
fedeinthemix Jan 2, 2023
9d82df7
julia: Add julia to passthru attributes
fedeinthemix Jan 3, 2023
8854488
julia: update buildPhase to check for 'deps/build.jl` and add use of …
fedeinthemix Jan 3, 2023
c47be78
julia: Make Julia's fontonfig find system files
fedeinthemix Jan 6, 2023
92ea844
julia2nix: Add options to generate 'flake.nix' and 'shell.nix'
fedeinthemix Jan 9, 2023
4140d82
julia: Add configurePhase
fedeinthemix Jan 10, 2023
8918dcc
julia: Separate nixpkgs julia package collection from upstream one to…
fedeinthemix Jan 11, 2023
2fbc9d8
julia: Make 'Pkg' prefer local packages by setting it "offline"
fedeinthemix Jan 12, 2023
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
1 change: 1 addition & 0 deletions doc/languages-frameworks/index.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<xi:include href="java.section.xml" />
<xi:include href="javascript.section.xml" />
<xi:include href="lua.section.xml" />
<xi:include href="julia.section.xml" />
<xi:include href="maven.section.xml" />
<xi:include href="nim.section.xml" />
<xi:include href="ocaml.section.xml" />
Expand Down
72 changes: 72 additions & 0 deletions doc/languages-frameworks/julia.section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Julia {#sec-julia}

## Introduction {#ssec-julia-introduction}

The [Julia programming language](https://julialang.org/) was developed
with scientific applications in mind. It is distributed with a package
manager and the vast majority of packages are available through the
main package server
[pkg.julialang.org](https://pkg.julialang.org). While the native
package manager can be used to easily and declaratively install
packages, several ones come with binary blobs (called artifacts in
Julia parlance) that do not work in NixOS. To remedy this shortcoming,
Julia packages can be installed as described below. This way the
binary blobs are patched to work as expected and all software can be
declared in a single file.

### Installing Julia Packages {#sssec-installing-julia-packages}

Most Julia compilers available in Nixpkgs have a passthru attribute
called `withPackages`. This function permits creating Julia
environments with sets of packages ready for use. Compared with the
similarly named function available in other Nixpkgs language
frameworks, the one of Julia takes two arguments. The first one is a
function specifying a list of Julia packages available in Nixpkgs. The
second one is also a function, and it's used to specify a list of
extra package definitions not available in Nixpkgs. These definitions
can be generated with the help of `julia2nix`. As an example, to
install `julia-bin` with the Nixpkgs Julia package `Plots`, all its
dependencies and the upstream package `CSTParser` with its dependency
`Tokenize`, one can use the following command

```ShellSession
$ nix-shell -p 'julia-bin.withPackages
(p: with p; [ Plots ])
(p: with p; [
{ pname = "Tokenize";
version = "0.5.25";
src = fetchurl {
url = "https://pkg.julialang.org/package/0796e94c-ce3b-5d07-9a54-7f471281c624/90538bf898832b6ebd900fa40f223e695970e3a5";
name = "julia-bin-1.8.3-Tokenize-0.5.25.tar.gz";
sha256 = "00437718f09d81958e86c2131f3f8b63e0975494e505f6c7b62f872a5a102f51";
};
}

{ pname = "CSTParser";
version = "3.3.6";
src = fetchurl {
url = "https://pkg.julialang.org/package/00ebfdb7-1f24-5e51-bd34-a7502290713f/3ddd48d200eb8ddf9cb3e0189fc059fd49b97c1f";
name = "julia-bin-1.8.3-CSTParser-3.3.6.tar.gz";
sha256 = "647fc5588cb87362d216401a0a4124d41b80a85618a94098a737ad38ae5786c4";
};
requiredJuliaPackages = [ Tokenize ];
}
])
'
```

This is not very practical as a `nix-shell` command, but the same
expression can be used in a `shell.nix` file, or other places
expecting a Nix expression as described elsewhere in this
manual. After executing this command and starting Julia, the packages
can be directly imported with `using` or `import` without first having
to use `import Pkg; Pkg.add(...)`.

The list of Julia packages in Nixpkgs is accessible through the
compiler passthru attribute set `juliaPackages`, for example
`julia-bin.juliaPackages`.

Note that the above upstream package definitions are addressed with an
`url` specifying a unique package identifier (uuid) and the hash of
the package content. For this reason we don't lose reproducibility by
using upstream packages.
13 changes: 12 additions & 1 deletion nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,25 @@
In addition to numerous new and upgraded packages, this release
has the following highlights:
</para>
<itemizedlist spacing="compact">
<itemizedlist>
<listitem>
<para>
Cinnamon has been updated to 5.6, see
<link xlink:href="https://github.com/NixOS/nixpkgs/pull/201328#issue-1449910204">the
pull request</link> for what is changed.
</para>
</listitem>
<listitem>
<para>
It’s now possible to make
<link xlink:href="https://julialang.org">Julia</link>
environments with a set of packages using e.g.
<literal>julia-bin.withPackages</literal>. Definitions for
packages in the main Julia package repository not available in
NixOS can be generated with <literal>julia2nix</literal> and
used directly with <literal>withPackages</literal>.
</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="sec-release-23.05-new-services">
Expand Down
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2305.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ In addition to numerous new and upgraded packages, this release has the followin

- Cinnamon has been updated to 5.6, see [the pull request](https://github.com/NixOS/nixpkgs/pull/201328#issue-1449910204) for what is changed.

- It's now possible to make [Julia](https://julialang.org) environments with a set of packages using e.g. `julia-bin.withPackages`. Definitions for packages in the main Julia package repository not available in NixOS can be generated with `julia2nix` and used directly with `withPackages`.

## New Services {#sec-release-23.05-new-services}

<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
Expand Down
32 changes: 32 additions & 0 deletions pkgs/development/compilers/julia/build-env.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{ buildEnv
, makeWrapper
, extraPackages ? []
, julia
}:

buildEnv {
name = "julia-env";
paths = extraPackages;

nativeBuildInputs = [ makeWrapper ];

pathsToLink = [ "/share/julia" ];

# FONTCONFIG_FILE is needed to make Julia's fontconfig artifact
# find the system fonts.
#
# On Wayland, without setting GDK_BACKEND to X11, we get
# "Gdk-CRITICAL" messages. We may remove it in the future. The
# '*' means that if X11 is not available, it will try other
# backends.
postBuild = ''
makeWrapper ${julia}/bin/julia $out/bin/julia \
--set JULIA_LOAD_PATH "$JULIA_LOAD_PATH:$out/share/julia/upstream/packages:$out/share/julia/nixpkgs/packages" \
--set JULIA_DEPOT_PATH "$JULIA_DEPOT_PATH:$out/share/julia/upstream:$out/share/julia/nixpkgs" \
--set JULIA_PKG_OFFLINE true \
--set FONTCONFIG_FILE "/etc/fonts/fonts.conf" \
--set GDK_BACKEND "x11,*"
'';

passthru = { inherit julia; };
}
177 changes: 177 additions & 0 deletions pkgs/development/compilers/julia/build-julia-package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
{ lib
, stdenv
, config
, patchelf
, julia
, computeRequiredJuliaPackages
, computeJuliaDepotPath
, computeJuliaLoadPath
, computeJuliaArtifacts
, xvfb-run
}:

# Build an individual package
{ fullPkgName ? "${attrs.pname}-${attrs.version}"

, src

, dontPatch ? false
, patches ? []
, patchPhase ? ""

, enableParallelBuilding ? true
# Build-time dependencies for the package, which were compiled for the system compiling this.
, nativeBuildInputs ? []

# Build-time dependencies for the package, which may not have been compiled for the system compiling this.
, buildInputs ? []

# Propagate build dependencies so in case we have A -> B -> C,
# C can import package A propagated by B
# Run-time dependencies for the package.
, propagatedBuildInputs ? []

# Julia packages that are required at runtime for this one.
# These behave similarly to propagatedBuildInputs, where if
# package A is needed by B, and C needs B, then C also requires A.
# The main difference between these and propagatedBuildInputs is
# during the package's installation into Julia, where all
# requiredJuliaPackages are ALSO installed into Julia.
, requiredJuliaPackages ? []

, preBuild ? ""
, meta ? {}
, passthru ? {}

# Julia packages can distribute 'Artifacts'. These can be some data,
# binary, ... We Package 'Artifacts' as independent derivations. They
# differ from the rest of the packages in that they don't get compiled
# and the package providing it expect to find them in a specific location.
, isJuliaArtifact ? false

# Relative path from $out/share/julia to avoid name clashes between
# packages provided by nixpkgs and upstream ones added by the user.
, packageCollection ? "nixpkgs"
, ... } @ attrs:

let
requiredJuliaPackages' = computeRequiredJuliaPackages requiredJuliaPackages;
juliaDepotPath = computeJuliaDepotPath requiredJuliaPackages';
juliaLoadPath = computeJuliaLoadPath requiredJuliaPackages';
juliaArtifacts = computeJuliaArtifacts requiredJuliaPackages';


# Must use attrs.nativeBuildInputs before they are removed by the removeAttrs
# below, or everything fails.
nativeBuildInputs' = [ julia patchelf ] ++ nativeBuildInputs;

# This step is required because when
# a = { test = [ "a" "b" ]; }; b = { test = [ "c" "d" ]; };
# (a // b).test = [ "c" "d" ];
# This used to mean that if a package defined extra nativeBuildInputs, it
# would override the ones for building a Julia package (Julia itself)
# causing everything to fail.
attrs' = builtins.removeAttrs attrs [ "nativeBuildInputs" ];

in stdenv.mkDerivation ({
packageName = "${fullPkgName}";
# The name of the package ends up being
# "julia-version-package-version"
name = "${julia.pname}-${julia.version}-${fullPkgName}";

# This states that any package built with the function that this returns
# will be a julia package. This is used for ensuring other julia
# packages are installed into julia during the environment building phase.
isJuliaPackage = true;
# Julia Artifacts need to be treated specially.
isJuliaArtifact = isJuliaArtifact;

inherit packageCollection;

inherit src;

inherit dontPatch patches patchPhase;

enableParallelBuilding = enableParallelBuilding;

requiredJuliaPackages = requiredJuliaPackages';

nativeBuildInputs = nativeBuildInputs';

buildInputs = buildInputs ++ requiredJuliaPackages' ++ [ stdenv.cc.libc ];

propagatedBuildInputs = propagatedBuildInputs;

preUnpack = ''
mkdir ${attrs.pname}
cd ${attrs.pname}
'';

setSourceRoot = ''
sourceRoot=$PWD
'';

# Set NIX_JULIA_PRECMD to the currntly only known command needed so
# as to maximize the number of packages that compile without extra
# configuration (see https://github.com/JuliaGraphics/Gtk.jl/issues/346)
configurePhase = ''
runHook preConfigure
export NIX_JULIA_PRECMD=${xvfb-run}/bin/xvfb-run
runHook postConfigure
'';

# Julia installs compiled cache in the first path in DEPOT_PATH.
# Packages are pre-complied when imported.
buildPhase = if isJuliaArtifact then ''
runHook preBuild

mkdir -p $out/share/julia/${packageCollection}/artifacts/${attrs.juliaPath}
cp -r * $out/share/julia/${packageCollection}/artifacts/${attrs.juliaPath}

runHook postBuild
'' else ''
runHook preBuild

mkdir -p $out/share/julia/${packageCollection}/packages/${attrs.pname}
cp -r * $out/share/julia/${packageCollection}/packages/${attrs.pname}
# 'Pkg' searches in 'packageName/slug' which is stored in the attribute 'juliaPath'
ln -s $out/share/julia/${packageCollection}/packages/${attrs.pname} $out/share/julia/${packageCollection}/packages/${attrs.juliaPath}

export JULIA_LOAD_PATH=$out/share/julia/${packageCollection}/packages:${juliaLoadPath}:$JULIA_LOAD_PATH
export JULIA_DEPOT_PATH=$out/share/julia/${packageCollection}:${julia}/share/julia:${juliaDepotPath}

mkdir -p $out/share/julia/${packageCollection}/artifacts
for path in ${lib.concatMapStringsSep " " (p: p + "/share/julia/${packageCollection}/artifacts/*") juliaArtifacts}; do
ln -s $path $out/share/julia/${packageCollection}/artifacts/$(basename $path);
done

pushd $out/share/julia/${packageCollection}/packages/${attrs.pname}
if [[ -f ./deps/build.jl ]]; then
pushd deps
$NIX_JULIA_PRECMD julia -- build.jl
popd
fi
$NIX_JULIA_PRECMD julia -e 'import ${attrs.pname}'
popd

# these may cause collisions
rm -r $out/share/julia/${packageCollection}/logs || true
rm -r $out/share/julia/${packageCollection}/scratchspaces || true

runHook postBuild
'';

# Need to install before building
dontInstall = true;

# Patch interpreter of bundled binary files
postFixup = if isJuliaArtifact then ''
if [[ -d "$out"/share/julia/${packageCollection}/artifacts/${attrs.juliaPath}/bin ]]; then
for fn in $out/share/julia/${packageCollection}/artifacts/${attrs.juliaPath}/bin/*; do
isELF $fn && patchelf --set-interpreter ${stdenv.cc.bintools.dynamicLinker} $fn || true
done;
fi
'' else "";

inherit meta;
} // attrs')
Loading