Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at devShellTools-shell 668 lines 21 kB view raw
1# Checks derivation meta and attrs for problems (like brokenness, 2# licenses, etc). 3 4{ 5 lib, 6 config, 7 hostPlatform, 8}: 9 10let 11 inherit (lib) 12 all 13 attrNames 14 concatMapStrings 15 concatMapStringsSep 16 concatStrings 17 findFirst 18 isDerivation 19 length 20 concatMap 21 mutuallyExclusive 22 optional 23 optionalAttrs 24 optionalString 25 optionals 26 isAttrs 27 isString 28 mapAttrs 29 filterAttrs 30 ; 31 32 inherit (lib.lists) 33 any 34 toList 35 isList 36 elem 37 ; 38 39 inherit (lib.meta) 40 availableOn 41 ; 42 43 inherit (lib.generators) 44 toPretty 45 ; 46 47 # If we're in hydra, we can dispense with the more verbose error 48 # messages and make problems easier to spot. 49 inHydra = config.inHydra or false; 50 # Allow the user to opt-into additional warnings, e.g. 51 # import <nixpkgs> { config = { showDerivationWarnings = [ "maintainerless" ]; }; } 52 showWarnings = config.showDerivationWarnings; 53 54 getNameWithVersion = 55 attrs: attrs.name or ("${attrs.pname or "«name-missing»"}-${attrs.version or "«version-missing»"}"); 56 57 allowUnfree = config.allowUnfree || builtins.getEnv "NIXPKGS_ALLOW_UNFREE" == "1"; 58 59 allowNonSource = 60 let 61 envVar = builtins.getEnv "NIXPKGS_ALLOW_NONSOURCE"; 62 in 63 if envVar != "" then envVar != "0" else config.allowNonSource or true; 64 65 allowlist = config.allowlistedLicenses or config.whitelistedLicenses or [ ]; 66 blocklist = config.blocklistedLicenses or config.blacklistedLicenses or [ ]; 67 68 areLicenseListsValid = 69 if mutuallyExclusive allowlist blocklist then 70 true 71 else 72 throw "allowlistedLicenses and blocklistedLicenses are not mutually exclusive."; 73 74 hasLicense = attrs: attrs ? meta.license; 75 76 hasListedLicense = 77 assert areLicenseListsValid; 78 list: attrs: 79 length list > 0 80 && hasLicense attrs 81 && ( 82 if isList attrs.meta.license then 83 any (l: elem l list) attrs.meta.license 84 else 85 elem attrs.meta.license list 86 ); 87 88 hasAllowlistedLicense = attrs: hasListedLicense allowlist attrs; 89 90 hasBlocklistedLicense = attrs: hasListedLicense blocklist attrs; 91 92 allowBroken = config.allowBroken || builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1"; 93 94 allowUnsupportedSystem = 95 config.allowUnsupportedSystem || builtins.getEnv "NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM" == "1"; 96 97 isUnfree = 98 licenses: 99 if isAttrs licenses then 100 !licenses.free or true 101 # TODO: Returning false in the case of a string is a bug that should be fixed. 102 # In a previous implementation of this function the function body 103 # was `licenses: lib.lists.any (l: !l.free or true) licenses;` 104 # which always evaluates to `!true` for strings. 105 else if isString licenses then 106 false 107 else 108 any (l: !l.free or true) licenses; 109 110 hasUnfreeLicense = attrs: hasLicense attrs && isUnfree attrs.meta.license; 111 112 hasNoMaintainers = 113 # To get usable output, we want to avoid flagging "internal" derivations. 114 # Because we do not have a way to reliably decide between internal or 115 # external derivation, some heuristics are required to decide. 116 # 117 # If `outputHash` is defined, the derivation is a FOD, such as the output of a fetcher. 118 # If `description` is not defined, the derivation is probably not a package. 119 # Simply checking whether `meta` is defined is insufficient, 120 # as some fetchers and trivial builders do define meta. 121 attrs: 122 (!attrs ? outputHash) 123 && (attrs ? meta.description) 124 && (attrs.meta.maintainers or [ ] == [ ]) 125 && (attrs.meta.teams or [ ] == [ ]); 126 127 isMarkedBroken = attrs: attrs.meta.broken or false; 128 129 hasUnsupportedPlatform = pkg: !(availableOn hostPlatform pkg); 130 131 isMarkedInsecure = attrs: (attrs.meta.knownVulnerabilities or [ ]) != [ ]; 132 133 # Allow granular checks to allow only some unfree packages 134 # Example: 135 # {pkgs, ...}: 136 # { 137 # allowUnfree = false; 138 # allowUnfreePredicate = (x: pkgs.lib.hasPrefix "vscode" x.name); 139 # } 140 allowUnfreePredicate = config.allowUnfreePredicate or (x: false); 141 142 # Check whether unfree packages are allowed and if not, whether the 143 # package has an unfree license and is not explicitly allowed by the 144 # `allowUnfreePredicate` function. 145 hasDeniedUnfreeLicense = 146 attrs: hasUnfreeLicense attrs && !allowUnfree && !allowUnfreePredicate attrs; 147 148 allowInsecureDefaultPredicate = 149 x: builtins.elem (getNameWithVersion x) (config.permittedInsecurePackages or [ ]); 150 allowInsecurePredicate = x: (config.allowInsecurePredicate or allowInsecureDefaultPredicate) x; 151 152 hasAllowedInsecure = 153 attrs: 154 !(isMarkedInsecure attrs) 155 || allowInsecurePredicate attrs 156 || builtins.getEnv "NIXPKGS_ALLOW_INSECURE" == "1"; 157 158 isNonSource = sourceTypes: any (t: !t.isSource) sourceTypes; 159 160 hasNonSourceProvenance = 161 attrs: (attrs ? meta.sourceProvenance) && isNonSource attrs.meta.sourceProvenance; 162 163 # Allow granular checks to allow only some non-source-built packages 164 # Example: 165 # { pkgs, ... }: 166 # { 167 # allowNonSource = false; 168 # allowNonSourcePredicate = with pkgs.lib.lists; pkg: !(any (p: !p.isSource && p != lib.sourceTypes.binaryFirmware) pkg.meta.sourceProvenance); 169 # } 170 allowNonSourcePredicate = config.allowNonSourcePredicate or (x: false); 171 172 # Check whether non-source packages are allowed and if not, whether the 173 # package has non-source provenance and is not explicitly allowed by the 174 # `allowNonSourcePredicate` function. 175 hasDeniedNonSourceProvenance = 176 attrs: hasNonSourceProvenance attrs && !allowNonSource && !allowNonSourcePredicate attrs; 177 178 showLicenseOrSourceType = value: toString (map (v: v.shortName or "unknown") (toList value)); 179 showLicense = showLicenseOrSourceType; 180 showSourceType = showLicenseOrSourceType; 181 182 pos_str = meta: meta.position or "«unknown-file»"; 183 184 remediation = { 185 unfree = remediate_allowlist "Unfree" (remediate_predicate "allowUnfreePredicate"); 186 non-source = remediate_allowlist "NonSource" (remediate_predicate "allowNonSourcePredicate"); 187 broken = remediate_allowlist "Broken" (x: ""); 188 unsupported = remediate_allowlist "UnsupportedSystem" (x: ""); 189 blocklisted = x: ""; 190 insecure = remediate_insecure; 191 broken-outputs = remediateOutputsToInstall; 192 unknown-meta = x: ""; 193 maintainerless = x: ""; 194 }; 195 remediation_env_var = 196 allow_attr: 197 { 198 Unfree = "NIXPKGS_ALLOW_UNFREE"; 199 Broken = "NIXPKGS_ALLOW_BROKEN"; 200 UnsupportedSystem = "NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM"; 201 NonSource = "NIXPKGS_ALLOW_NONSOURCE"; 202 } 203 .${allow_attr}; 204 remediation_phrase = 205 allow_attr: 206 { 207 Unfree = "unfree packages"; 208 Broken = "broken packages"; 209 UnsupportedSystem = "packages that are unsupported for this system"; 210 NonSource = "packages not built from source"; 211 } 212 .${allow_attr}; 213 remediate_predicate = predicateConfigAttr: attrs: '' 214 215 Alternatively you can configure a predicate to allow specific packages: 216 { nixpkgs.config.${predicateConfigAttr} = pkg: builtins.elem (lib.getName pkg) [ 217 "${lib.getName attrs}" 218 ]; 219 } 220 ''; 221 222 # flakeNote will be printed in the remediation messages below. 223 flakeNote = " 224 Note: When using `nix shell`, `nix build`, `nix develop`, etc with a flake, 225 then pass `--impure` in order to allow use of environment variables. 226 "; 227 228 remediate_allowlist = allow_attr: rebuild_amendment: attrs: '' 229 a) To temporarily allow ${remediation_phrase allow_attr}, you can use an environment variable 230 for a single invocation of the nix tools. 231 232 $ export ${remediation_env_var allow_attr}=1 233 ${flakeNote} 234 b) For `nixos-rebuild` you can set 235 { nixpkgs.config.allow${allow_attr} = true; } 236 in configuration.nix to override this. 237 ${rebuild_amendment attrs} 238 c) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add 239 { allow${allow_attr} = true; } 240 to ~/.config/nixpkgs/config.nix. 241 ''; 242 243 remediate_insecure = 244 attrs: 245 '' 246 247 Known issues: 248 '' 249 + (concatStrings (map (issue: " - ${issue}\n") attrs.meta.knownVulnerabilities)) 250 + '' 251 252 You can install it anyway by allowing this package, using the 253 following methods: 254 255 a) To temporarily allow all insecure packages, you can use an environment 256 variable for a single invocation of the nix tools: 257 258 $ export NIXPKGS_ALLOW_INSECURE=1 259 ${flakeNote} 260 b) for `nixos-rebuild` you can add ${getNameWithVersion attrs} to 261 `nixpkgs.config.permittedInsecurePackages` in the configuration.nix, 262 like so: 263 264 { 265 nixpkgs.config.permittedInsecurePackages = [ 266 "${getNameWithVersion attrs}" 267 ]; 268 } 269 270 c) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add 271 ${getNameWithVersion attrs} to `permittedInsecurePackages` in 272 ~/.config/nixpkgs/config.nix, like so: 273 274 { 275 permittedInsecurePackages = [ 276 "${getNameWithVersion attrs}" 277 ]; 278 } 279 280 ''; 281 282 remediateOutputsToInstall = 283 attrs: 284 let 285 expectedOutputs = attrs.meta.outputsToInstall or [ ]; 286 actualOutputs = attrs.outputs or [ "out" ]; 287 missingOutputs = builtins.filter (output: !builtins.elem output actualOutputs) expectedOutputs; 288 in 289 '' 290 The package ${getNameWithVersion attrs} has set meta.outputsToInstall to: ${builtins.concatStringsSep ", " expectedOutputs} 291 292 however ${getNameWithVersion attrs} only has the outputs: ${builtins.concatStringsSep ", " actualOutputs} 293 294 and is missing the following ouputs: 295 296 ${concatStrings (builtins.map (output: " - ${output}\n") missingOutputs)} 297 ''; 298 299 handleEvalIssue = 300 { meta, attrs }: 301 { 302 reason, 303 errormsg ? "", 304 }: 305 let 306 msg = 307 if inHydra then 308 "Failed to evaluate ${getNameWithVersion attrs}: «${reason}»: ${errormsg}" 309 else 310 '' 311 Package ${getNameWithVersion attrs} in ${pos_str meta} ${errormsg}, refusing to evaluate. 312 313 '' 314 + (builtins.getAttr reason remediation) attrs; 315 316 handler = if config ? handleEvalIssue then config.handleEvalIssue reason else throw; 317 in 318 handler msg; 319 320 handleEvalWarning = 321 { meta, attrs }: 322 { 323 reason, 324 errormsg ? "", 325 }: 326 let 327 remediationMsg = (builtins.getAttr reason remediation) attrs; 328 msg = 329 if inHydra then 330 "Warning while evaluating ${getNameWithVersion attrs}: «${reason}»: ${errormsg}" 331 else 332 "Package ${getNameWithVersion attrs} in ${pos_str meta} ${errormsg}, continuing anyway." 333 + (optionalString (remediationMsg != "") "\n${remediationMsg}"); 334 isEnabled = findFirst (x: x == reason) null showWarnings; 335 in 336 if isEnabled != null then builtins.trace msg true else true; 337 338 metaTypes = 339 let 340 types = import ./meta-types.nix { inherit lib; }; 341 inherit (types) 342 str 343 union 344 int 345 attrs 346 attrsOf 347 any 348 listOf 349 bool 350 ; 351 platforms = listOf (union [ 352 str 353 (attrsOf any) 354 ]); # see lib.meta.platformMatch 355 in 356 { 357 # These keys are documented 358 description = str; 359 mainProgram = str; 360 longDescription = str; 361 branch = str; 362 homepage = union [ 363 (listOf str) 364 str 365 ]; 366 downloadPage = str; 367 changelog = union [ 368 (listOf str) 369 str 370 ]; 371 license = 372 let 373 # TODO disallow `str` licenses, use a module 374 licenseType = union [ 375 (attrsOf any) 376 str 377 ]; 378 in 379 union [ 380 (listOf licenseType) 381 licenseType 382 ]; 383 sourceProvenance = listOf attrs; 384 maintainers = listOf (attrsOf any); # TODO use the maintainer type from lib/tests/maintainer-module.nix 385 teams = listOf (attrsOf any); # TODO similar to maintainers, use a teams type 386 priority = int; 387 pkgConfigModules = listOf str; 388 inherit platforms; 389 hydraPlatforms = listOf str; 390 broken = bool; 391 unfree = bool; 392 unsupported = bool; 393 insecure = bool; 394 tests = { 395 name = "test"; 396 verify = 397 x: 398 x == { } 399 || 400 # Accept {} for tests that are unsupported 401 (isDerivation x && x ? meta.timeout); 402 }; 403 timeout = int; 404 knownVulnerabilities = listOf str; 405 badPlatforms = platforms; 406 407 # Needed for Hydra to expose channel tarballs: 408 # https://github.com/NixOS/hydra/blob/53335323ae79ca1a42643f58e520b376898ce641/doc/manual/src/jobs.md#meta-fields 409 isHydraChannel = bool; 410 411 # Weirder stuff that doesn't appear in the documentation? 412 maxSilent = int; 413 name = str; 414 version = str; 415 tag = str; 416 executables = listOf str; 417 outputsToInstall = listOf str; 418 position = str; 419 available = any; 420 isBuildPythonPackage = platforms; 421 schedulingPriority = int; 422 isFcitxEngine = bool; 423 isIbusEngine = bool; 424 isGutenprint = bool; 425 426 # Used for the original location of the maintainer and team attributes to assist with pings. 427 maintainersPosition = any; 428 teamsPosition = any; 429 }; 430 431 checkMetaAttr = 432 let 433 # Map attrs directly to the verify function for performance 434 metaTypes' = mapAttrs (_: t: t.verify) metaTypes; 435 in 436 k: v: 437 if metaTypes ? ${k} then 438 if metaTypes'.${k} v then 439 [ ] 440 else 441 [ 442 "key 'meta.${k}' has invalid value; expected ${metaTypes.${k}.name}, got\n ${ 443 toPretty { indent = " "; } v 444 }" 445 ] 446 else 447 [ 448 "key 'meta.${k}' is unrecognized; expected one of: \n [${ 449 concatMapStringsSep ", " (x: "'${x}'") (attrNames metaTypes) 450 }]" 451 ]; 452 checkMeta = 453 meta: 454 optionals config.checkMeta (concatMap (attr: checkMetaAttr attr meta.${attr}) (attrNames meta)); 455 456 checkOutputsToInstall = 457 attrs: 458 let 459 expectedOutputs = attrs.meta.outputsToInstall or [ ]; 460 actualOutputs = attrs.outputs or [ "out" ]; 461 missingOutputs = builtins.filter (output: !builtins.elem output actualOutputs) expectedOutputs; 462 in 463 if config.checkMeta then builtins.length missingOutputs > 0 else false; 464 465 # Check if a derivation is valid, that is whether it passes checks for 466 # e.g brokenness or license. 467 # 468 # Return { valid: "yes", "warn" or "no" } and additionally 469 # { reason: String; errormsg: String } if it is not valid, where 470 # reason is one of "unfree", "blocklisted", "broken", "insecure", ... 471 # !!! reason strings are hardcoded into OfBorg, make sure to keep them in sync 472 # Along with a boolean flag for each reason 473 checkValidity = 474 let 475 validYes = { 476 valid = "yes"; 477 handled = true; 478 }; 479 in 480 attrs: 481 # Check meta attribute types first, to make sure it is always called even when there are other issues 482 # Note that this is not a full type check and functions below still need to by careful about their inputs! 483 let 484 res = checkMeta (attrs.meta or { }); 485 in 486 if res != [ ] then 487 { 488 valid = "no"; 489 reason = "unknown-meta"; 490 errormsg = "has an invalid meta attrset:${concatMapStrings (x: "\n - " + x) res}\n"; 491 } 492 493 # --- Put checks that cannot be ignored here --- 494 else if checkOutputsToInstall attrs then 495 { 496 valid = "no"; 497 reason = "broken-outputs"; 498 errormsg = "has invalid meta.outputsToInstall"; 499 } 500 501 # --- Put checks that can be ignored here --- 502 else if hasDeniedUnfreeLicense attrs && !(hasAllowlistedLicense attrs) then 503 { 504 valid = "no"; 505 reason = "unfree"; 506 errormsg = "has an unfree license (${showLicense attrs.meta.license})"; 507 } 508 else if hasBlocklistedLicense attrs then 509 { 510 valid = "no"; 511 reason = "blocklisted"; 512 errormsg = "has a blocklisted license (${showLicense attrs.meta.license})"; 513 } 514 else if hasDeniedNonSourceProvenance attrs then 515 { 516 valid = "no"; 517 reason = "non-source"; 518 errormsg = "contains elements not built from source (${showSourceType attrs.meta.sourceProvenance})"; 519 } 520 else if !allowBroken && attrs.meta.broken or false then 521 { 522 valid = "no"; 523 reason = "broken"; 524 errormsg = "is marked as broken"; 525 } 526 else if !allowUnsupportedSystem && hasUnsupportedPlatform attrs then 527 let 528 toPretty' = toPretty { 529 allowPrettyValues = true; 530 indent = " "; 531 }; 532 in 533 { 534 valid = "no"; 535 reason = "unsupported"; 536 errormsg = '' 537 is not available on the requested hostPlatform: 538 hostPlatform.config = "${hostPlatform.config}" 539 package.meta.platforms = ${toPretty' (attrs.meta.platforms or [ ])} 540 package.meta.badPlatforms = ${toPretty' (attrs.meta.badPlatforms or [ ])} 541 ''; 542 } 543 else if !(hasAllowedInsecure attrs) then 544 { 545 valid = "no"; 546 reason = "insecure"; 547 errormsg = "is marked as insecure"; 548 } 549 550 # --- warnings --- 551 # Please also update the type in /pkgs/top-level/config.nix alongside this. 552 else if hasNoMaintainers attrs then 553 { 554 valid = "warn"; 555 reason = "maintainerless"; 556 errormsg = "has no maintainers or teams"; 557 } 558 # ----- 559 else 560 validYes; 561 562 # The meta attribute is passed in the resulting attribute set, 563 # but it's not part of the actual derivation, i.e., it's not 564 # passed to the builder and is not a dependency. But since we 565 # include it in the result, it *is* available to nix-env for queries. 566 # Example: 567 # meta = checkMeta.commonMeta { inherit validity attrs pos references; }; 568 # validity = checkMeta.assertValidity { inherit meta attrs; }; 569 commonMeta = 570 { 571 validity, 572 attrs, 573 pos ? null, 574 references ? [ ], 575 }: 576 let 577 outputs = attrs.outputs or [ "out" ]; 578 hasOutput = out: builtins.elem out outputs; 579 in 580 { 581 # `name` derivation attribute includes cross-compilation cruft, 582 # is under assert, and is sanitized. 583 # Let's have a clean always accessible version here. 584 name = attrs.name or "${attrs.pname}-${attrs.version}"; 585 586 # If the packager hasn't specified `outputsToInstall`, choose a default, 587 # which is the name of `p.bin or p.out or p` along with `p.man` when 588 # present. 589 # 590 # If the packager has specified it, it will be overridden below in 591 # `// meta`. 592 # 593 # Note: This default probably shouldn't be globally configurable. 594 # Services and users should specify outputs explicitly, 595 # unless they are comfortable with this default. 596 outputsToInstall = [ 597 ( 598 if hasOutput "bin" then 599 "bin" 600 else if hasOutput "out" then 601 "out" 602 else 603 findFirst hasOutput null outputs 604 ) 605 ] 606 ++ optional (hasOutput "man") "man"; 607 } 608 // (filterAttrs (_: v: v != null) { 609 # CI scripts look at these to determine pings. Note that we should filter nulls out of this, 610 # or nix-env complains: https://github.com/NixOS/nix/blob/2.18.8/src/nix-env/nix-env.cc#L963 611 maintainersPosition = builtins.unsafeGetAttrPos "maintainers" (attrs.meta or { }); 612 teamsPosition = builtins.unsafeGetAttrPos "teams" (attrs.meta or { }); 613 }) 614 // attrs.meta or { } 615 # Fill `meta.position` to identify the source location of the package. 616 // optionalAttrs (pos != null) { 617 position = pos.file + ":" + toString pos.line; 618 } 619 // { 620 # Maintainers should be inclusive of teams. 621 # Note that there may be external consumers of this API (repology, for instance) - 622 # if you add a new maintainer or team attribute please ensure that this expectation is still met. 623 maintainers = 624 attrs.meta.maintainers or [ ] ++ concatMap (team: team.members or [ ]) attrs.meta.teams or [ ]; 625 } 626 // { 627 # Expose the result of the checks for everyone to see. 628 unfree = hasUnfreeLicense attrs; 629 broken = isMarkedBroken attrs; 630 unsupported = hasUnsupportedPlatform attrs; 631 insecure = isMarkedInsecure attrs; 632 633 available = 634 validity.valid != "no" 635 && ( 636 if config.checkMetaRecursively or false then all (d: d.meta.available or true) references else true 637 ); 638 }; 639 640 assertValidity = 641 { meta, attrs }: 642 let 643 validity = checkValidity attrs; 644 inherit (validity) valid; 645 in 646 if validity ? handled then 647 validity 648 else 649 validity 650 // { 651 # Throw an error if trying to evaluate a non-valid derivation 652 # or, alternatively, just output a warning message. 653 handled = ( 654 if valid == "yes" then 655 true 656 else if valid == "no" then 657 (handleEvalIssue { inherit meta attrs; } { inherit (validity) reason errormsg; }) 658 else if valid == "warn" then 659 (handleEvalWarning { inherit meta attrs; } { inherit (validity) reason errormsg; }) 660 else 661 throw "Unknown validitiy: '${valid}'" 662 ); 663 }; 664 665in 666{ 667 inherit assertValidity commonMeta; 668}