Skip to content

Commit

Permalink
Merge pull request #278805 from tweag/by-name-enforce-preparation
Browse files Browse the repository at this point in the history
check-by-name: Refactor to prepare for enforcing `pkgs/by-name`, make `--base` required
  • Loading branch information
infinisil authored Jan 9, 2024
2 parents 0ff29c7 + a1db0cd commit da3e72b
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 215 deletions.
2 changes: 2 additions & 0 deletions pkgs/test/nixpkgs-check-by-name/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ These checks are performed by this tool:
- Each package directory must not refer to files outside itself using symlinks or Nix path expressions.

### Nix evaluation checks

Evaluate Nixpkgs with `system` set to `x86_64-linux` and check that:
- For each package directory, the `pkgs.${name}` attribute must be defined as `callPackage pkgs/by-name/${shard}/${name}/package.nix args` for some `args`.
- For each package directory, `pkgs.lib.isDerivation pkgs.${name}` must be `true`.

Expand Down
123 changes: 67 additions & 56 deletions pkgs/test/nixpkgs-check-by-name/src/eval.nix
Original file line number Diff line number Diff line change
@@ -1,82 +1,93 @@
# Takes a path to nixpkgs and a path to the json-encoded list of attributes to check.
# Returns an attribute set containing information on each requested attribute.
# If the attribute is missing from Nixpkgs it's also missing from the result.
#
# The returned information is an attribute set with:
# - call_package_path: The <path> from `<attr> = callPackage <path> { ... }`,
# or null if it's not defined as with callPackage, or if the <path> is not a path
# - is_derivation: The result of `lib.isDerivation <attr>`
# Returns an value containing information on each requested attribute,
# which is decoded on the Rust side.
# See ./eval.rs for the meaning of the returned values
{
attrsPath,
nixpkgsPath,
}:
let
attrs = builtins.fromJSON (builtins.readFile attrsPath);

# This overlay mocks callPackage to persist the path of the first argument
callPackageOverlay = self: super: {
nixpkgsPathLength = builtins.stringLength (toString nixpkgsPath) + 1;
removeNixpkgsPrefix = builtins.substring nixpkgsPathLength (-1);

# We need access to the `callPackage` arguments of each attribute.
# The only way to do so is to override `callPackage` with our own version that adds this information to the result,
# and then try to access this information.
overlay = final: prev: {

# Information for attributes defined using `callPackage`
callPackage = fn: args:
let
result = super.callPackage fn args;
variantInfo._attributeVariant = {
# These names are used by the deserializer on the Rust side
CallPackage.path =
addVariantInfo (prev.callPackage fn args) {
Manual = {
path =
if builtins.isPath fn then
toString fn
removeNixpkgsPrefix (toString fn)
else
null;
CallPackage.empty_arg =
empty_arg =
args == { };
};
in
if builtins.isAttrs result then
# If this was the last overlay to be applied, we could just only return the `_callPackagePath`,
# but that's not the case because stdenv has another overlays on top of user-provided ones.
# So to not break the stdenv build we need to return the mostly proper result here
result // variantInfo
else
# It's very rare that callPackage doesn't return an attribute set, but it can occur.
variantInfo;
};

# Information for attributes that are auto-called from pkgs/by-name.
# This internal attribute is only used by pkgs/by-name
_internalCallByNamePackageFile = file:
let
result = super._internalCallByNamePackageFile file;
variantInfo._attributeVariant = {
# This name is used by the deserializer on the Rust side
AutoCalled = null;
};
in
if builtins.isAttrs result then
# If this was the last overlay to be applied, we could just only return the `_callPackagePath`,
# but that's not the case because stdenv has another overlays on top of user-provided ones.
# So to not break the stdenv build we need to return the mostly proper result here
result // variantInfo
else
# It's very rare that callPackage doesn't return an attribute set, but it can occur.
variantInfo;
addVariantInfo (prev._internalCallByNamePackageFile file) {
Auto = null;
};

};

# We can't just replace attribute values with their info in the overlay,
# because attributes can depend on other attributes, so this would break evaluation.
addVariantInfo = value: variant:
if builtins.isAttrs value then
value // {
_callPackageVariant = variant;
}
else
# It's very rare that callPackage doesn't return an attribute set, but it can occur.
# In such a case we can't really return anything sensible that would include the info,
# so just don't return the info and let the consumer handle it.
value;

pkgs = import nixpkgsPath {
# Don't let the users home directory influence this result
config = { };
overlays = [ callPackageOverlay ];
overlays = [ overlay ];
# We check evaluation and callPackage only for x86_64-linux.
# Not ideal, but hard to fix
system = "x86_64-linux";
};

attrInfo = attr:
let
value = pkgs.${attr};
in
{
# These names are used by the deserializer on the Rust side
variant = value._attributeVariant or { Other = null; };
is_derivation = pkgs.lib.isDerivation value;
};
attrInfo = name: value:
if ! builtins.isAttrs value then
{
NonAttributeSet = null;
}
else if ! value ? _callPackageVariant then
{
NonCallPackage = null;
}
else
{
CallPackage = {
call_package_variant = value._callPackageVariant;
is_derivation = pkgs.lib.isDerivation value;
};
};

attrInfos = builtins.listToAttrs (map (name: {
inherit name;
value = attrInfo name;
}) attrs);
attrInfos = map (name: [
name
(
if ! pkgs ? ${name} then
{ Missing = null; }
else
{ Existing = attrInfo name pkgs.${name}; }
)
]) attrs;

in
# Filter out attributes not in Nixpkgs
builtins.intersectAttrs pkgs attrInfos
attrInfos
Loading

0 comments on commit da3e72b

Please sign in to comment.