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

check-by-name: Refactor to prepare for enforcing pkgs/by-name, make --base required #278805

Merged
merged 8 commits into from
Jan 9, 2024
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