Files
rust-overlay/lib/rust-bin.nix

553 lines
18 KiB
Nix

# Component resolution, aggregation and other utility functions.
# Provide the content of `rust-bin`.
{
lib,
pkgs,
manifests,
nightly,
}:
let
inherit (builtins)
compareVersions
fromTOML
match
readFile
tryEval
;
inherit (lib)
any
attrNames
attrValues
concatStringsSep
elem
elemAt
filter
flatten
foldl'
hasPrefix
head
isString
length
listToAttrs
makeOverridable
mapAttrs
mapAttrsToList
optional
optionalAttrs
replaceStrings
substring
trace
unique
;
inherit (pkgs) stdenv callPackage fetchurl;
# Remove keys from attrsets whose value is null.
removeNulls = set: removeAttrs set (filter (name: set.${name} == null) (attrNames set));
toRustTarget = platform: platform.rust.rustcTarget;
# The platform where `rustc` is running.
rustHostPlatform = toRustTarget stdenv.hostPlatform;
# The platform of binary which `rustc` produces.
rustTargetPlatform = toRustTarget stdenv.targetPlatform;
mkComponentSet = callPackage ./mk-component-set.nix {
inherit removeNulls toRustTarget;
};
mkAggregated = callPackage ./mk-aggregated.nix { };
# Manifest selector.
selectManifest =
{
channel,
date ? null,
}:
let
assertWith =
cond: msg: body:
if cond then body else throw msg;
# https://rust-lang.github.io/rustup/concepts/toolchains.html#toolchain-specification
# <channel> = stable|beta|nightly|<major.minor>|<major.minor.patch>
asVersion = match "[0-9]+\\.[0-9]+(\\.[0-9]+)?" channel;
asNightlyDate =
let
m = match "nightly-([0-9]+-[0-9]+-[0-9]+)" channel;
in
if m == null then null else elemAt m 0;
asBetaDate =
let
m = match "beta-([0-9]+-[0-9]+-[0-9]+)" channel;
in
if m == null then null else elemAt m 0;
maxWith = zero: f: foldl' (lhs: rhs: if lhs == zero || f lhs rhs < 0 then rhs else lhs) zero;
latestStableWithMajorMinor = maxWith "" compareVersions (
filter (hasPrefix (channel + ".")) (attrNames manifests.stable)
);
in
# "stable"
if channel == "stable" then
assertWith (
date == null
) "Stable version with specific date is not supported" manifests.stable.latest
# "nightly"
else if channel == "nightly" then
manifests.nightly.${if date != null then date else "latest"}
or (throw "Nightly ${date} is not available")
# "beta"
else if channel == "beta" then
manifests.beta.${if date != null then date else "latest"} or (throw "Beta ${date} is not available")
# "1.49.0" or "1.49"
else if asVersion != null then
assertWith (date == null) "Stable version with specific date is not supported" (
# "1.49"
if asVersion == [ null ] then
manifests.stable.${latestStableWithMajorMinor} or (throw "No stable ${channel}.* is available")
# "1.49.0"
else
manifests.stable.${channel} or (throw "Stable ${channel} is not available")
)
# "beta-2021-01-01"
else if asBetaDate != null then
assertWith (date == null) "Cannot specify date in both `channel` and `date`"
manifests.beta.${asBetaDate} or (throw "Beta ${asBetaDate} is not available")
# "nightly-2021-01-01"
else if asNightlyDate != null then
assertWith (date == null) "Cannot specify date in both `channel` and `date`"
manifests.nightly.${asNightlyDate} or (throw "Nightly ${asNightlyDate} is not available")
# Otherwise
else
throw "Unknown channel: ${channel}";
# Select a toolchain and aggregate components by rustup's `rust-toolchain` file format.
# See: https://rust-lang.github.io/rustup/concepts/profiles.html
# Or see source: https://github.com/rust-lang/rustup/blob/84974df1387812269c7b29fa5f3bb1c6480a6500/doc/src/overrides.md#the-toolchain-file
fromRustupToolchain =
{
path ? null,
channel ? null,
profile ? null,
components ? [ ],
targets ? [ ],
}:
if path != null then
throw "`path` is not supported, please directly add it to your PATH instead"
else if channel == null then
throw "`channel` is required"
else
let
toolchain = toolchainFromManifest (selectManifest {
inherit channel;
});
profile' = if profile == null then "default" else profile;
pkg =
if toolchain._profiles != { } then
toolchain._profiles.${profile'} or (throw ''
Rust ${toolchain._version} doesn't have profile `${profile'}`.
Available profiles are: ${concatStringsSep ", " (attrNames toolchain._profiles)}
'')
# Fallback to package `rust` when profiles are not supported and not specified.
else if profile == null then
toolchain.rust
else
throw "Cannot select profile `${profile'}` since rust ${toolchain._version} is too early to support profiles";
in
pkg.override {
extensions = components;
inherit targets;
};
# Same as `fromRustupToolchain` but read from a `rust-toolchain` file (legacy one-line string or in TOML).
fromRustupToolchainFile =
path:
let
content = readFile path;
legacy = match "([^\r\n]+)\r?\n?" content;
in
if legacy != null then
fromRustupToolchain { channel = head legacy; }
else
fromRustupToolchain (fromTOML content).toolchain;
mkComponentSrc =
{ url, sha256 }:
let
url' = replaceStrings [ " " ] [ "%20" ] url; # This is required or download will fail.
# Filter names like `llvm-tools-1.34.2 (6c2484dc3 2019-05-13)-aarch64-unknown-linux-gnu.tar.xz`
matchParenPart = match ".*/([^ /]*) [(][^)]*[)](.*)" url;
name = if matchParenPart == null then "" else (elemAt matchParenPart 0) + (elemAt matchParenPart 1);
in
fetchurl {
inherit name sha256;
url = url';
};
# Resolve final components to install from mozilla-overlay style `extensions`, `targets` and `targetExtensions`.
#
# `componentSet` has a layout of `componentSet.<name>.<rust-target> : Derivation`.
# `targetComponentsList` is a list of all component names for target platforms.
# `name` is only used for error message.
#
# Returns a list of component derivations, or throw if failed.
resolveComponents =
{
name,
componentSet,
allComponentSet,
allPlatformSet,
targetComponentsList,
profileComponents,
extensions,
targets,
targetExtensions,
}:
let
# Components for target platform like `rust-std`.
collectTargetComponents =
allowMissing: name:
let
targetSelected = flatten (map (tgt: componentSet.${tgt}.${name} or [ ]) targets);
in
if !allowMissing -> targetSelected != [ ] then
targetSelected
else
"Component `${name}` doesn't support any of targets: ${concatStringsSep ", " targets}";
collectComponents =
allowMissing: name:
if elem name targetComponentsList then
collectTargetComponents allowMissing name
else
# Components for host platform like `rustc`.
componentSet.${rustHostPlatform}.${name}
or (if allowMissing then [ ] else "Host component `${name}` doesn't support `${rustHostPlatform}`");
# Profile components can be skipped silently when missing.
# Eg. `rust-mingw` on non-Windows platforms, or `rust-docs` on non-tier1 platforms.
result =
flatten (map (collectComponents true) profileComponents)
++ flatten (map (collectComponents false) extensions)
++ flatten (map (collectTargetComponents false) targetExtensions);
isTargetUnused =
target:
!any (name: componentSet ? ${target}.${name})
# FIXME: Get rid of the legacy component `rust`.
(
filter (name: name == "rust" || elem name targetComponentsList) (profileComponents ++ extensions)
++ targetExtensions
);
# Fail-fast for typo in `targets`, `extensions`, `targetExtensions`.
fastErrors = flatten (
map (
tgt:
optional (
!(allPlatformSet ? ${tgt})
) "Unknown target `${tgt}`, typo or not supported by this version?"
) targets
++ map (
name:
optional (
!(allComponentSet ? ${name})
) "Unknown component `${name}`, typo or not support by this version?"
) (profileComponents ++ extensions ++ targetExtensions)
);
errors =
if fastErrors != [ ] then
fastErrors
else
filter isString result
++ map (tgt: "Target `${tgt}` is not supported by any components or extensions") (
filter isTargetUnused targets
);
notes = [
"note: profile components: ${toString profileComponents}"
]
++ optional (targets != [ ]) "note: selected targets: ${toString targets}"
++ optional (extensions != [ ]) "note: selected extensions: ${toString extensions}"
++ optional (
targetExtensions != [ ]
) "note: selected targetExtensions: ${toString targetExtensions}"
++ flatten (
map (
platform:
optional (componentSet ? ${platform})
"note: components available for ${platform}: ${toString (attrNames componentSet.${platform})}"
) (unique ([ rustHostPlatform ] ++ targets))
)
++ [
''
note: check here to see all targets and which components are available on each targets:
https://rust-lang.github.io/rustup-components-history
''
];
in
if errors == [ ] then
result
else
throw ''
Component resolution failed for ${name}
${concatStringsSep "\n" (errors ++ notes)}
'';
# Generate the toolchain set from a parsed manifest.
#
# Manifest files are organized as follow:
# { date = "2017-03-03";
# pkg.cargo.version= "0.18.0-nightly (5db6d64 2017-03-03)";
# pkg.cargo.target.x86_64-unknown-linux-gnu = {
# available = true;
# hash = "abce..."; # sha256
# url = "https://static.rust-lang.org/dist/....tar.gz";
# xz_hash = "abce..."; # sha256
# xz_url = "https://static.rust-lang.org/dist/....tar.xz";
# };
# }
#
# The packages available usually are:
# cargo, rust-analysis, rust-docs, rust-src, rust-std, rustc, and
# rust, which aggregates them in one package.
#
# For each package the following options are available:
# extensions - The extensions that should be installed for the package.
# For example, install the package rust and add the extension rust-src.
# targets - The package will always be installed for the host system, but with this option
# extra targets can be specified, e.g. "mips-unknown-linux-musl". The target
# will only apply to components of the package that support being installed for
# a different architecture. For example, the rust package will install rust-std
# for the host system and the targets.
# targetExtensions - If you want to force extensions to be installed for the given targets, this is your option.
# All extensions in this list will be installed for the target architectures.
# *Attention* If you want to install an extension like rust-src, that has no fixed architecture (arch *),
# you will need to specify this extension in the extensions options or it will not be installed!
toolchainFromManifest =
manifest:
let
# platform -> true
# For fail-fast test.
allPlatformSet = listToAttrs (
flatten (
mapAttrsToList (
compName:
{ target, ... }:
map (platform: {
name = platform;
value = true;
}) (attrNames target)
) manifest.pkg
)
);
# componentName -> true
# May also contains unavailable components. Just for fail-fast test.
allComponentSet = mapAttrs (compName: _: true) (manifest.pkg // manifest.renames);
# componentSet.x86_64-unknown-linux-gnu.cargo = <derivation>;
componentSet = mapAttrs (
platform: _:
mkComponentSet {
inherit (manifest) version renames;
inherit platform;
srcs = removeNulls (
mapAttrs (
compName:
{ target, ... }:
let
content = target.${platform} or target."*" or null;
in
if content == null then
null
else
mkComponentSrc {
url = content.xz_url;
sha256 = content.xz_hash;
}
) manifest.pkg
);
}
) allPlatformSet;
mkProfile =
name: profileComponents:
makeOverridable
(
{
extensions,
targets,
targetExtensions,
}:
mkAggregated {
pname = "rust-${name}";
inherit (manifest) version date;
availableComponents = componentSet.${rustHostPlatform};
selectedComponents = resolveComponents {
name = "rust-${name}-${manifest.version}";
inherit
allPlatformSet
allComponentSet
componentSet
profileComponents
targetExtensions
;
inherit (manifest) targetComponentsList;
extensions = extensions;
targets = unique (
[
rustHostPlatform # Build script requires host std.
rustTargetPlatform
]
++ targets
);
};
}
)
{
extensions = [ ];
targets = [ ];
targetExtensions = [ ];
};
profiles = mapAttrs mkProfile manifest.profiles;
result =
# Individual components.
componentSet.${rustHostPlatform}
//
# Profiles.
profiles
// {
# Legacy support for special pre-aggregated package.
# It has more components than `default` profile but less than `complete` profile.
rust =
let
pkg = mkProfile "legacy" [ "rust" ];
in
if profiles != { } then
trace ''
Rust ${manifest.version}:
Pre-aggregated package `rust` is not encouraged for stable channel since it contains almost all and uncertain components.
Consider use `default` profile like `rust-bin.stable.latest.default` and override it with extensions you need.
See README for more information.
'' pkg
else
pkg;
};
in
# If the platform is not supported for the current version, return nothing here,
# so others can easily check it by `toolchain ? default`.
optionalAttrs (componentSet ? ${rustHostPlatform}) result
// {
# Internal use.
_components = componentSet;
_profiles = profiles;
_version = manifest.version;
_manifest = manifest;
};
# From a git revision of rustc.
# This does the same thing as crate `rustup-toolchain-install-master`.
# But you need to manually provide component hashes.
fromRustcRev =
{
# Package name of the derivation.
pname ? "rust-custom",
# Git revision of rustc.
rev,
# Version of the built package.
version ? substring 0 7 rev,
# Attrset with component name as key and its SRI hash as value.
components,
# Rust target to download.
target ? rustTargetPlatform,
}:
let
hashToSrc =
compName: hash:
fetchurl {
url =
if compName == "rust-src" then
"https://ci-artifacts.rust-lang.org/rustc-builds/${rev}/${compName}-nightly.tar.xz"
else
"https://ci-artifacts.rust-lang.org/rustc-builds/${rev}/${compName}-nightly-${target}.tar.xz";
inherit hash;
};
components' = mkComponentSet {
inherit version;
platform = target;
srcs = mapAttrs hashToSrc components;
# We cannot know aliases in this case.
renames = { };
};
in
mkAggregated {
inherit pname version;
date = null;
selectedComponents = attrValues components';
};
# Select latest nightly toolchain which makes selected profile builds.
# Some components are missing in some nightly releases.
# Usage:
# `selectLatestNightlyWith (toolchain: toolchain.default.override { extensions = ["llvm-tools-preview"]; })`
selectLatestNightlyWith =
selector:
let
nightlyDates = attrNames (removeAttrs nightly [ "latest" ]);
dateLength = length nightlyDates;
go =
idx:
let
ret = selector (nightly.${elemAt nightlyDates idx});
in
if idx == 0 then
ret
else if dateLength - idx >= 256 then
trace "Failed to select nightly version after 100 tries" ret
else if ret != null && (tryEval ret.drvPath).success then
ret
else
go (idx - 1);
in
go (length nightlyDates - 1);
in
# For each channel:
# rust-bin.stable.latest.{minimal,default,complete} # Profiles.
# rust-bin.stable.latest.rust # Pre-aggregate from upstream.
# rust-bin.stable.latest.cargo # Components...
# rust-bin.stable.latest.rustc
# rust-bin.stable.latest.rust-docs
# ...
#
# For a specific version of stable:
# rust-bin.stable."1.47.0".default
#
# For a specific date of beta:
# rust-bin.beta."2021-01-01".default
#
# For a specific date of nightly:
# rust-bin.nightly."2020-01-01".default
mapAttrs (channel: mapAttrs (version: toolchainFromManifest)) manifests
// {
inherit fromRustupToolchain fromRustupToolchainFile;
inherit selectLatestNightlyWith;
inherit fromRustcRev;
_internal = {
inherit toolchainFromManifest;
inherit selectManifest;
};
}