crane/lib/vendorCargoRegistries.nix
Ivan Petkov 960d350770
vendorCargoDeps: manually splice packages to avoid cross build issues (#507)
* Source vendoring is passed as a standalone attribute to
  `mkCargoDerivation`, meaning it does not automagically get spliced
  with the correct local/cross system inputs (i.e. what would happen if
  it was a `depsBuildBuild` entry)
* To fix this we need to make sure that `vendorCargoDeps` and all of its
  transitive dependencies always use `runCommand` (and friends) from
  their `pkgsBuildBuild` equivalent. This should always be safe to do
  (even for cross-builds) since this amounts to building up a bunch of
  sources which will be read by the build system
* Unfortunately I had to manually specify `pkgsBuildBuild.whatever` in
  multiple places as I could not get things to work by messing with the
  `callPackage` definition. Perhaps we should be using
  `makeScopeWithSplicing'` instead of `makeScope` when constructing the
  library, but I couldn't get it working (and I couldn't find any decent
  docs on how to use it online) so this will make do for the time being.
2024-01-28 19:08:44 +00:00

126 lines
3.6 KiB
Nix

{ downloadCargoPackage
, lib
, pkgsBuildBuild
}:
let
inherit (pkgsBuildBuild)
runCommandLocal;
inherit (builtins)
attrNames
concatStringsSep
filter
hasAttr
hashString
head
length
mapAttrs
placeholder
readFile;
inherit (lib)
concatMapStrings
concatStrings
escapeShellArg
flatten
groupBy
hasPrefix
mapAttrs'
mapAttrsToList
nameValuePair;
inherit (lib.lists) unique;
hash = hashString "sha256";
hasRegistryProtocolPrefix = s: hasPrefix "registry+" s || hasPrefix "sparse+" s;
in
{ cargoConfigs ? [ ]
, lockPackages
, ...
}@args:
let
# Local crates will show up in the lock file with no checksum/source,
# so should filter them out without trying to download them
lockedPackagesFromRegistry = filter
(p: p ? checksum && hasRegistryProtocolPrefix (p.source or ""))
lockPackages;
lockedRegistryGroups = groupBy (p: p.source) lockedPackagesFromRegistry;
vendorSingleRegistry = packages: runCommandLocal "vendor-registry" { } ''
mkdir -p $out
${concatMapStrings (p: ''
ln -s ${escapeShellArg (downloadCargoPackage p)} $out/${escapeShellArg "${p.name}-${p.version}"}
'') packages}
'';
parsedCargoConfigTomls = map (p: builtins.fromTOML (readFile p)) cargoConfigs;
allCargoRegistries = flatten (map (c: c.registries or [ ]) parsedCargoConfigTomls);
allCargoRegistryPairs = flatten (map (mapAttrsToList (name: value: { inherit name value; })) allCargoRegistries);
allCargoRegistryPairsWithIndex = filter (r: r ? value.index) allCargoRegistryPairs;
configuredRegistries = mapAttrs (_: map (r: r.value.index)) (groupBy (x: x.name) allCargoRegistryPairsWithIndex);
# Append the default crates.io registry, but allow it to be overridden
registries = {
"crates-io" = [ "https://github.com/rust-lang/crates.io-index" ];
} // (
if args ? registries
then mapAttrs (_: val: [ val ]) args.registries
else configuredRegistries
);
sources = mapAttrs'
(url: packages: nameValuePair (hash url) (vendorSingleRegistry packages))
lockedRegistryGroups;
configLocalSources = concatMapStrings
(hashedUrl: ''
[source.nix-sources-${hashedUrl}]
directory = "${placeholder "out"}/${hashedUrl}"
'')
(attrNames sources);
# e.g. hasSparse x if either has sparse+x, or x starts with sparse+ and has x.
hasRegistryWithProtocol = (lrg: protocol: u:
(hasAttr "${protocol}+${u}" lrg) || ((lib.hasPrefix protocol u) && (hasAttr u lrg))
);
hasSparseRegistry = hasRegistryWithProtocol lockedRegistryGroups "sparse";
hasLegacyRegistry = hasRegistryWithProtocol lockedRegistryGroups "registry";
hasRegistry = (u: (hasSparseRegistry u) || (hasLegacyRegistry u));
configReplaceRegistries = mapAttrsToList
(name: urls:
let
actuallyConfigured = unique (filter hasRegistry urls);
numConfigured = length actuallyConfigured;
in
if numConfigured == 0 then ""
else if numConfigured > 1 then
throw ''
there are multiple distinct registries configured with the same name.
please ensure that each unique registry name is used with exactly one registry url.
${name} is used with:
${concatStringsSep "\n" urls}
''
else
let
url = head actuallyConfigured;
prefixedUrl = if hasRegistryProtocolPrefix url then url else "registry+${url}";
hashed = hash prefixedUrl;
in
''
[source.${name}]
registry = "${url}"
replace-with = "nix-sources-${hashed}"
''
)
registries;
in
{
inherit sources;
config = configLocalSources + (concatStrings configReplaceRegistries);
}