mirror of
https://github.com/nix-community/dream2nix.git
synced 2024-12-23 14:31:55 +03:00
456 lines
15 KiB
Nix
456 lines
15 KiB
Nix
{
|
|
lib,
|
|
parseSpdxId,
|
|
sanitizePath,
|
|
sanitizeRelativePath,
|
|
simpleTranslate2,
|
|
...
|
|
}: let
|
|
l = lib // builtins;
|
|
|
|
translate = {
|
|
# project,
|
|
projectRelPath,
|
|
tree,
|
|
...
|
|
}: let
|
|
# get the root source and project source
|
|
rootTree = tree;
|
|
projectTree = rootTree.getNodeFromPath projectRelPath;
|
|
rootSource = rootTree.fullPath;
|
|
projectSource = sanitizePath "${rootSource}/${projectRelPath}";
|
|
# pull all crates from subsystemInfo, if not find all of them
|
|
# this is mainly helpful when `projects` is defined manually in which case
|
|
# crates won't be available, so we will reduce burden on the user here.
|
|
allCrates =
|
|
(import ./findAllCrates.nix {inherit lib;})
|
|
{tree = rootTree;};
|
|
|
|
# Get the root toml
|
|
rootToml = {
|
|
relPath = projectRelPath;
|
|
value = projectTree.files."Cargo.toml".tomlContent;
|
|
};
|
|
workspacePackage = rootToml.value.workspace.package or null;
|
|
getVersion = package:
|
|
if package.version.workspace or false
|
|
then workspacePackage.version or (l.warn "no workspace version found in root Cargo.toml for ${package.name}, defaulting to unknown" "unknown")
|
|
else package.version or (l.warn "no version found in Cargo.toml for ${package.name}, defaulting to unknown" "unknown");
|
|
|
|
# use workspace members from discover phase
|
|
# or discover them again ourselves
|
|
workspaceMembers =
|
|
l.flatten
|
|
(
|
|
l.map
|
|
(
|
|
memberName: let
|
|
components = l.splitString "/" memberName;
|
|
in
|
|
# Resolve globs if there are any
|
|
if l.last components == "*"
|
|
then let
|
|
parentDirRel = l.concatStringsSep "/" (l.init components);
|
|
dirs = (rootTree.getNodeFromPath parentDirRel).directories;
|
|
in
|
|
l.mapAttrsToList
|
|
(name: _: "${parentDirRel}/${name}")
|
|
dirs
|
|
else memberName
|
|
)
|
|
(rootToml.value.workspace.members or [])
|
|
);
|
|
# Get cargo packages (for workspace members)
|
|
workspaceCargoPackages =
|
|
l.map
|
|
(relPath: {
|
|
inherit relPath;
|
|
value = (projectTree.getNodeFromPath "${relPath}/Cargo.toml").tomlContent;
|
|
})
|
|
# Filter root referencing member, we already parsed this (rootToml)
|
|
(l.filter (relPath: relPath != ".") workspaceMembers);
|
|
|
|
# All cargo packages that we will output
|
|
cargoPackages =
|
|
if l.hasAttrByPath ["package" "name"] rootToml.value
|
|
# Note: the ordering is important here, since packageToml assumes
|
|
# the rootToml to be at 0 index (if it is a package)
|
|
then [rootToml] ++ workspaceCargoPackages
|
|
else workspaceCargoPackages;
|
|
|
|
# Get a "main" package toml
|
|
packageToml = l.elemAt cargoPackages 0;
|
|
|
|
# Parse Cargo.lock and extract dependencies
|
|
parsedLock = projectTree.files."Cargo.lock".tomlContent;
|
|
parsedDeps = parsedLock.package;
|
|
|
|
makeDepId = dep: "${dep.name} ${dep.version} (${dep.source or ""})";
|
|
|
|
# Gets a checksum from the [metadata] table of the lockfile
|
|
getChecksum = dep: parsedLock.metadata."checksum ${makeDepId dep}";
|
|
|
|
# map of dependency names to a list of the possible versions
|
|
depNamesToVersions =
|
|
l.foldl'
|
|
(
|
|
all: el:
|
|
if l.hasAttr el.name all
|
|
then all // {${el.name} = all.${el.name} ++ [el];}
|
|
else all // {${el.name} = [el];}
|
|
)
|
|
{}
|
|
parsedDeps;
|
|
# takes a parsed dependency entry and finds the original dependency from `Cargo.lock`
|
|
findOriginalDep = let
|
|
# checks dep against another dep to see if they are the same
|
|
# this is only for checking entries from a `dependencies` list
|
|
# against a dependency entry under `package` from Cargo.lock
|
|
isSameDependency = dep: againstDep:
|
|
l.foldl'
|
|
(previousResult: result: previousResult && result)
|
|
true
|
|
(
|
|
l.mapAttrsToList
|
|
(
|
|
name: value:
|
|
if l.hasAttr name againstDep
|
|
then
|
|
# if git source, we need to get rid of the revision part
|
|
if name == "source" && l.hasPrefix "git+" againstDep.source
|
|
then l.concatStringsSep "#" (l.init (l.splitString "#" againstDep.source)) == value
|
|
else againstDep.${name} == value
|
|
else false
|
|
)
|
|
dep
|
|
);
|
|
in
|
|
dep: let
|
|
notFoundError = "no dependency found with name ${dep.name} in Cargo.lock";
|
|
foundCount = l.length depNamesToVersions.${dep.name};
|
|
found =
|
|
# if found one version, then that's the dependency we are looking for
|
|
if foundCount == 1
|
|
then l.head depNamesToVersions.${dep.name}
|
|
# if found multiple, then we need to check which dependency we are looking for
|
|
else if foundCount > 1
|
|
then
|
|
l.findFirst
|
|
(otherDep: isSameDependency dep otherDep)
|
|
(throw notFoundError)
|
|
depNamesToVersions.${dep.name}
|
|
else throw notFoundError;
|
|
in
|
|
found;
|
|
# This parses a "package-name version (source)" entry in the "dependencies"
|
|
# field of a dependency in Cargo.lock
|
|
parseDepEntryImpl = entry: let
|
|
parsed = l.splitString " " entry;
|
|
# name is always at the beginning
|
|
name = l.head parsed;
|
|
# parse the version if it exists
|
|
maybeVersion =
|
|
if l.length parsed > 1
|
|
then l.elemAt parsed 1
|
|
else null;
|
|
# parse the source if it exists
|
|
source =
|
|
if l.length parsed > 2
|
|
then l.removePrefix "(" (l.removeSuffix ")" (l.elemAt parsed 2))
|
|
else null;
|
|
# find the original dependency from the information we have
|
|
foundDep = findOriginalDep (
|
|
{inherit name;}
|
|
// l.optionalAttrs (source != null) {inherit source;}
|
|
// l.optionalAttrs (maybeVersion != null) {version = maybeVersion;}
|
|
);
|
|
in
|
|
foundDep;
|
|
# dependency entries mapped to their original dependency
|
|
entryToDependencyAttrs = let
|
|
makePair = entry: l.nameValuePair entry (parseDepEntryImpl entry);
|
|
depEntries = l.flatten (l.map (dep: dep.dependencies or []) parsedDeps);
|
|
in
|
|
l.listToAttrs (l.map makePair (l.unique depEntries));
|
|
parseDepEntry = entry: entryToDependencyAttrs.${entry};
|
|
|
|
# Parses a git source, taken straight from nixpkgs.
|
|
parseSourceImpl = src: let
|
|
parts = l.match ''git\+([^?]+)(\?(rev|tag|branch)=(.*))?#(.*)'' src;
|
|
type = l.elemAt parts 2; # rev, tag or branch
|
|
value = l.elemAt parts 3;
|
|
checkType = type: l.hasPrefix "${type}+" src;
|
|
in
|
|
if checkType "registry"
|
|
then
|
|
if src == "registry+https://github.com/rust-lang/crates.io-index"
|
|
then {
|
|
type = "crates-io";
|
|
value = null;
|
|
}
|
|
else throw "registries other than crates.io are not supported yet"
|
|
else if parts != null
|
|
then {
|
|
type = "git";
|
|
value =
|
|
{
|
|
url = l.elemAt parts 0;
|
|
sha = l.elemAt parts 4;
|
|
}
|
|
// (lib.optionalAttrs (type != null) {inherit type value;});
|
|
}
|
|
else throw "unknown or unsupported source type: ${src}";
|
|
parsedSources = l.listToAttrs (
|
|
l.map
|
|
(dep: l.nameValuePair dep.source (parseSourceImpl dep.source))
|
|
(l.filter (dep: dep ? source) parsedDeps)
|
|
);
|
|
parseSource = dep:
|
|
if dep ? source
|
|
then parsedSources.${dep.source}
|
|
else {
|
|
type = "path";
|
|
value = null;
|
|
};
|
|
|
|
package = rec {
|
|
toml = packageToml.value;
|
|
name = toml.package.name;
|
|
version = getVersion toml.package;
|
|
};
|
|
|
|
extractVersionFromDep = rawObj: let
|
|
source = parseSource rawObj;
|
|
duplicateVersions =
|
|
l.filter
|
|
(dep: dep.version == rawObj.version)
|
|
depNamesToVersions.${rawObj.name};
|
|
in
|
|
if l.length duplicateVersions > 1 && source.type != "path"
|
|
then rawObj.version + "$" + source.type
|
|
else rawObj.version;
|
|
in
|
|
simpleTranslate2
|
|
({...}: {
|
|
translatorName = "cargo-lock";
|
|
# relative path of the project within the source tree.
|
|
location = projectRelPath;
|
|
|
|
# the name of the subsystem
|
|
subsystemName = "rust";
|
|
|
|
# Extract subsystem specific attributes.
|
|
# The structure of this should be defined in:
|
|
# ./src/specifications/{subsystem}
|
|
subsystemAttrs = {
|
|
relPathReplacements = let
|
|
# function to find path replacements for one package
|
|
findReplacements = package: let
|
|
# Extract dependencies from the Cargo.toml of the package
|
|
tomlDeps =
|
|
l.flatten
|
|
(
|
|
l.map
|
|
(
|
|
target:
|
|
(l.attrValues (target.dependencies or {}))
|
|
++ (l.attrValues (target.buildDependencies or {}))
|
|
)
|
|
([package.value] ++ (l.attrValues (package.value.target or {})))
|
|
);
|
|
# We only need to patch path dependencies
|
|
pathDeps = l.filter (dep: dep ? path) tomlDeps;
|
|
# filter out path dependencies whose path are same as in workspace members.
|
|
# this is because otherwise workspace.members paths will also get replaced in the build.
|
|
# and there is no reason to replace these anyways since they are in the source.
|
|
outsideDeps =
|
|
l.filter
|
|
(
|
|
dep:
|
|
!(l.any (memberPath: dep.path == memberPath) workspaceMembers)
|
|
)
|
|
pathDeps;
|
|
makeReplacement = dep: {
|
|
name = dep.path;
|
|
value = sanitizeRelativePath "${package.relPath}/${dep.path}";
|
|
};
|
|
replacements = l.listToAttrs (l.map makeReplacement outsideDeps);
|
|
# filter out replacements which won't replace anything
|
|
# this means that the path doesn't need to be replaced because it's
|
|
# already in the source that we are building
|
|
filtered =
|
|
l.filterAttrs
|
|
(
|
|
n: v: ! l.pathExists (sanitizePath "${projectSource}/${v}")
|
|
)
|
|
replacements;
|
|
in
|
|
filtered;
|
|
# find replacements for all packages we export
|
|
allPackageReplacements =
|
|
l.map
|
|
(
|
|
package: let
|
|
pkg = package.value.package;
|
|
replacements = findReplacements package;
|
|
in {${pkg.name}.${getVersion pkg} = replacements;}
|
|
)
|
|
cargoPackages;
|
|
in
|
|
l.foldl' l.recursiveUpdate {} allPackageReplacements;
|
|
gitSources = l.unique (
|
|
l.map (src: src.value) (
|
|
l.filter
|
|
(src: src.type == "git")
|
|
(l.map parseSource parsedDeps)
|
|
)
|
|
);
|
|
meta = l.foldl' l.recursiveUpdate {} (
|
|
l.map
|
|
(
|
|
package: let
|
|
pkg = package.value.package;
|
|
in {
|
|
${pkg.name}.${getVersion pkg} =
|
|
{license = parseSpdxId (pkg.license or "");}
|
|
// (
|
|
l.filterAttrs
|
|
(n: v: l.any (on: n == on) ["description" "homepage"])
|
|
pkg
|
|
);
|
|
}
|
|
)
|
|
cargoPackages
|
|
);
|
|
};
|
|
|
|
defaultPackage = package.name;
|
|
|
|
/*
|
|
List the package candidates which should be exposed to the user.
|
|
Only top-level packages should be listed here.
|
|
Users will not be interested in all individual dependencies.
|
|
*/
|
|
exportedPackages = let
|
|
makePair = p: let
|
|
pkg = p.value.package;
|
|
in
|
|
l.nameValuePair pkg.name (getVersion pkg);
|
|
in
|
|
l.listToAttrs (l.map makePair cargoPackages);
|
|
|
|
/*
|
|
a list of raw package objects
|
|
If the upstream format is a deep attrset, this list should contain
|
|
a flattened representation of all entries.
|
|
*/
|
|
serializedRawObjects = parsedDeps;
|
|
|
|
/*
|
|
Define extractor functions which each extract one property from
|
|
a given raw object.
|
|
(Each rawObj comes from serializedRawObjects).
|
|
|
|
Extractors can access the fields extracted by other extractors
|
|
by accessing finalObj.
|
|
*/
|
|
extractors = {
|
|
name = rawObj: finalObj: rawObj.name;
|
|
|
|
version = rawObj: finalObj: extractVersionFromDep rawObj;
|
|
|
|
dependencies = rawObj: finalObj:
|
|
l.map
|
|
(dep: {
|
|
name = dep.name;
|
|
version = extractVersionFromDep dep;
|
|
})
|
|
(l.map parseDepEntry (rawObj.dependencies or []));
|
|
|
|
sourceSpec = rawObj: finalObj: let
|
|
source = parseSource rawObj;
|
|
depNameVersion = {
|
|
pname = rawObj.name;
|
|
version = l.removeSuffix ("$" + source.type) rawObj.version;
|
|
};
|
|
sourceConstructors = {
|
|
path = dependencyObject: let
|
|
findCrate =
|
|
l.findFirst
|
|
(
|
|
crate:
|
|
(crate.name == dependencyObject.name)
|
|
&& (crate.version == dependencyObject.version)
|
|
)
|
|
null;
|
|
workspaceCrates =
|
|
l.map
|
|
(
|
|
pkg: {
|
|
inherit (pkg.value.package) name;
|
|
inherit (pkg) relPath;
|
|
|
|
version = getVersion pkg.value.package;
|
|
}
|
|
)
|
|
cargoPackages;
|
|
workspaceCrate = findCrate workspaceCrates;
|
|
nonWorkspaceCrate = findCrate allCrates;
|
|
final =
|
|
if
|
|
(package.name == dependencyObject.name)
|
|
&& (package.version == dependencyObject.version)
|
|
then {
|
|
type = "path";
|
|
path = projectRelPath;
|
|
rootName = null;
|
|
rootVersion = null;
|
|
}
|
|
else if workspaceCrate != null
|
|
then {
|
|
type = "path";
|
|
path = workspaceCrate.relPath;
|
|
rootName = package.name;
|
|
rootVersion = package.version;
|
|
}
|
|
else if nonWorkspaceCrate != null
|
|
then {
|
|
type = "path";
|
|
path = nonWorkspaceCrate.relPath;
|
|
rootName = null;
|
|
rootVersion = null;
|
|
}
|
|
else throw "could not find crate '${dependencyObject.name}-${dependencyObject.version}'";
|
|
in
|
|
final;
|
|
|
|
git = dependencyObject: let
|
|
parsed = source.value;
|
|
maybeRef =
|
|
if parsed.type or null == "branch"
|
|
then {ref = "refs/heads/${parsed.value}";}
|
|
else if parsed.type or null == "tag"
|
|
then {ref = "refs/tags/${parsed.value}";}
|
|
else {};
|
|
in
|
|
maybeRef
|
|
// {
|
|
type = "git";
|
|
url = parsed.url;
|
|
rev = parsed.sha;
|
|
};
|
|
|
|
crates-io = dependencyObject:
|
|
depNameVersion
|
|
// {
|
|
type = "crates-io";
|
|
hash = dependencyObject.checksum or (getChecksum dependencyObject);
|
|
};
|
|
};
|
|
in
|
|
sourceConstructors."${source.type}" rawObj;
|
|
};
|
|
});
|
|
in
|
|
translate
|