diff options
Diffstat (limited to 'nix')
24 files changed, 610 insertions, 371 deletions
diff --git a/nix/bufCheck/default.nix b/nix/bufCheck/default.nix index 25a8865d8d..ec98cfc376 100644 --- a/nix/bufCheck/default.nix +++ b/nix/bufCheck/default.nix @@ -1,9 +1,26 @@ -# Check protobuf syntax and breaking. +# Check protobuf breaking. Lints already happen in individual targets. # -{ depot, pkgs, ... }: +{ depot, pkgs, lib, ... }: -pkgs.writeShellScriptBin "ci-buf-check" '' - ${depot.third_party.nixpkgs.buf}/bin/buf lint . - # Report-only - ${depot.third_party.nixpkgs.buf}/bin/buf breaking . --against "./.git#ref=HEAD~1" || true -'' +let + inherit (depot.nix) bufCheck;# self reference + + script = pkgs.writeShellScriptBin "ci-buf-check" '' + export PATH="$PATH:${pkgs.lib.makeBinPath [ pkgs.buf ]}" + # Report-only + (cd $(git rev-parse --show-toplevel) && (buf breaking . --against "./.git#ref=HEAD~1" || true)) + ''; +in + +script.overrideAttrs (old: { + meta = lib.recursiveUpdate old.meta { + # Protobuf check step executed in the buildkite pipeline which + # validates that changes to .proto files between revisions + # don't cause backwards-incompatible or otherwise flawed changes. + ci.extraSteps.protoCheck = { + alwaysRun = true; + label = ":water_buffalo: protoCheck"; + command = pkgs.writeShellScript "ci-buf-check-step" "exec ${depot.nix.bufCheck}/bin/ci-buf-check"; + }; + }; +}) diff --git a/nix/buildGo/README.md b/nix/buildGo/README.md index 37e0c06933..e9667c039a 100644 --- a/nix/buildGo/README.md +++ b/nix/buildGo/README.md @@ -2,8 +2,7 @@ buildGo.nix =========== This is an alternative [Nix][] build system for [Go][]. It supports building Go -libraries and programs, and even automatically generating Protobuf & gRPC -libraries. +libraries and programs. *Note:* This will probably end up being folded into [Nixery][]. @@ -33,7 +32,6 @@ Given a program layout like this: ├── lib <-- some library component │ ├── bar.go │ └── foo.go -├── api.proto <-- gRPC API definition ├── main.go <-- program implementation └── default.nix <-- build instructions ``` @@ -44,11 +42,6 @@ The contents of `default.nix` could look like this: { buildGo }: let - api = buildGo.grpc { - name = "someapi"; - proto = ./api.proto; - }; - lib = buildGo.package { name = "somelib"; srcs = [ @@ -58,7 +51,7 @@ let }; in buildGo.program { name = "my-program"; - deps = [ api lib ]; + deps = [ lib ]; srcs = [ ./main.go @@ -105,22 +98,6 @@ in buildGo.program { | `src` | `path` | Path to the source **directory** | yes | | `deps` | `list<drv>` | List of dependencies (i.e. other Go packages) | no | - For some examples of how `buildGo.external` is used, check out - [`proto.nix`](./proto.nix). - -* `buildGo.proto`: Build a Go library out of the specified Protobuf definition. - - | parameter | type | use | required? | - |-------------|-------------|--------------------------------------------------|-----------| - | `name` | `string` | Name for the resulting library | yes | - | `proto` | `path` | Path to the Protobuf definition file | yes | - | `path` | `string` | Import path for the resulting Go library | no | - | `extraDeps` | `list<drv>` | Additional Go dependencies to add to the library | no | - -* `buildGo.grpc`: Build a Go library out of the specified gRPC definition. - - The parameters are identical to `buildGo.proto`. - ## Current status This project is work-in-progress. Crucially it is lacking the following features: diff --git a/nix/buildGo/default.nix b/nix/buildGo/default.nix index 97b8bd2264..c93642a127 100644 --- a/nix/buildGo/default.nix +++ b/nix/buildGo/default.nix @@ -22,10 +22,8 @@ let replaceStrings toString; - inherit (pkgs) lib runCommand fetchFromGitHub protobuf symlinkJoin; - - # TODO: Adapt to Go 1.19 changes - go = pkgs.go_1_18; + inherit (pkgs) lib runCommand fetchFromGitHub protobuf symlinkJoin go; + goStdlib = buildStdlib go; # Helpers for low-level Go compiler invocations spaceOut = lib.concatStringsSep " "; @@ -44,8 +42,6 @@ let xFlags = x_defs: spaceOut (map (k: "-X ${k}=${x_defs."${k}"}") (attrNames x_defs)); - pathToName = p: replaceStrings [ "/" ] [ "_" ] (toString p); - # Add an `overrideGo` attribute to a function result that works # similar to `overrideAttrs`, but is used specifically for the # arguments passed to Go builders. @@ -53,16 +49,52 @@ let overrideGo = new: makeOverridable f (orig // (new orig)); }; + buildStdlib = go: runCommand "go-stdlib-${go.version}" + { + nativeBuildInputs = [ go ]; + } '' + HOME=$NIX_BUILD_TOP/home + mkdir $HOME + + goroot="$(go env GOROOT)" + cp -R "$goroot/src" "$goroot/pkg" . + + chmod -R +w . + GODEBUG=installgoroot=all GOROOT=$NIX_BUILD_TOP go install -v --trimpath std + + mkdir $out + cp -r pkg/*_*/* $out + + find $out -name '*.a' | while read -r ARCHIVE_FULL; do + ARCHIVE="''${ARCHIVE_FULL#"$out/"}" + PACKAGE="''${ARCHIVE%.a}" + echo "packagefile $PACKAGE=$ARCHIVE_FULL" + done > $out/importcfg + ''; + + importcfgCmd = { name, deps, out ? "importcfg" }: '' + echo "# nix buildGo ${name}" > "${out}" + cat "${goStdlib}/importcfg" >> "${out}" + ${lib.concatStringsSep "\n" (map (dep: '' + find "${dep}" -name '*.a' | while read -r pkgp; do + relpath="''${pkgp#"${dep}/"}" + pkgname="''${relpath%.a}" + echo "packagefile $pkgname=$pkgp" + done >> "${out}" + '') deps)} + ''; + # High-level build functions # Build a Go program out of the specified files and dependencies. program = { name, srcs, deps ? [ ], x_defs ? { } }: let uniqueDeps = allDeps (map (d: d.gopkg) deps); in runCommand name { } '' - ${go}/bin/go tool compile -o ${name}.a -trimpath=$PWD -trimpath=${go} ${includeSources uniqueDeps} ${spaceOut srcs} + ${importcfgCmd { inherit name; deps = uniqueDeps; }} + ${go}/bin/go tool compile -o ${name}.a -importcfg=importcfg -trimpath=$PWD -trimpath=${go} -p main ${includeSources uniqueDeps} ${spaceOut srcs} mkdir -p $out/bin export GOROOT_FINAL=go - ${go}/bin/go tool link -o $out/bin/${name} -buildid nix ${xFlags x_defs} ${includeLibs uniqueDeps} ${name}.a + ${go}/bin/go tool link -o $out/bin/${name} -importcfg=importcfg -buildid nix ${xFlags x_defs} ${includeLibs uniqueDeps} ${name}.a ''; # Build a Go library assembled out of the specified files. @@ -79,8 +111,8 @@ let # This is required for several popular packages (e.g. x/sys). ifAsm = do: lib.optionalString (sfiles != [ ]) do; asmBuild = ifAsm '' - ${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o ./symabis ${spaceOut sfiles} - ${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o ./asm.o ${spaceOut sfiles} + ${go}/bin/go tool asm -p ${path} -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o ./symabis ${spaceOut sfiles} + ${go}/bin/go tool asm -p ${path} -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o ./asm.o ${spaceOut sfiles} ''; asmLink = ifAsm "-symabis ./symabis -asmhdr $out/go_asm.h"; asmPack = ifAsm '' @@ -91,7 +123,8 @@ let mkdir -p $out/${path} ${srcList path (map (s: "${s}") srcs)} ${asmBuild} - ${go}/bin/go tool compile -pack ${asmLink} -o $out/${path}.a -trimpath=$PWD -trimpath=${go} -p ${path} ${includeSources uniqueDeps} ${spaceOut srcs} + ${importcfgCmd { inherit name; deps = uniqueDeps; }} + ${go}/bin/go tool compile -pack ${asmLink} -o $out/${path}.a -importcfg=importcfg -trimpath=$PWD -trimpath=${go} -p ${path} ${includeSources uniqueDeps} ${spaceOut srcs} ${asmPack} '').overrideAttrs (_: { passthru = { @@ -111,33 +144,14 @@ let # named "gopkg", and an attribute named "gobin" for binaries. external = import ./external { inherit pkgs program package; }; - # Import support libraries needed for protobuf & gRPC support - protoLibs = import ./proto.nix { - inherit external; - }; - - # Build a Go library out of the specified protobuf definition. - proto = { name, proto, path ? name, goPackage ? name, extraDeps ? [ ] }: (makeOverridable package) { - inherit name path; - deps = [ protoLibs.goProto.proto.gopkg ] ++ extraDeps; - srcs = lib.singleton (runCommand "goproto-${name}.pb.go" { } '' - cp ${proto} ${baseNameOf proto} - ${protobuf}/bin/protoc --plugin=${protoLibs.goProto.protoc-gen-go.gopkg}/bin/protoc-gen-go \ - --go_out=plugins=grpc,import_path=${baseNameOf path}:. ${baseNameOf proto} - mv ./${goPackage}/*.pb.go $out - ''); - }; - - # Build a Go library out of the specified gRPC definition. - grpc = args: proto (args // { extraDeps = [ protoLibs.goGrpc.gopkg ]; }); - in { # Only the high-level builder functions are exposed, but made # overrideable. program = makeOverridable program; package = makeOverridable package; - proto = makeOverridable proto; - grpc = makeOverridable grpc; external = makeOverridable external; + + # re-expose the Go version used + inherit go; } diff --git a/nix/buildGo/example/default.nix b/nix/buildGo/example/default.nix index 08da075e18..6756bf39e2 100644 --- a/nix/buildGo/example/default.nix +++ b/nix/buildGo/example/default.nix @@ -19,13 +19,6 @@ let ]; }; - # Example use of buildGo.proto, which generates a Go library from a - # Protobuf definition file. - exampleProto = buildGo.proto { - name = "exampleproto"; - proto = ./thing.proto; - }; - # Example use of buildGo.program, which builds an executable using # the specified name and dependencies (which in turn must have been # created via buildGo.package etc.) @@ -39,7 +32,6 @@ buildGo.program { deps = [ examplePackage - exampleProto ]; x_defs = { diff --git a/nix/buildGo/example/thing.proto b/nix/buildGo/example/thing.proto deleted file mode 100644 index 0f6d6575e0..0000000000 --- a/nix/buildGo/example/thing.proto +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 Google LLC. -// SPDX-License-Identifier: Apache-2.0 - -syntax = "proto3"; -package example; - -message Thing { - string id = 1; - string kind_of_thing = 2; -} diff --git a/nix/buildGo/external/main.go b/nix/buildGo/external/main.go index a77c43b371..4402a8eb86 100644 --- a/nix/buildGo/external/main.go +++ b/nix/buildGo/external/main.go @@ -10,7 +10,6 @@ import ( "flag" "fmt" "go/build" - "io/ioutil" "log" "os" "path" @@ -74,8 +73,8 @@ func findGoDirs(at string) ([]string, error) { } goDirs := []string{} - for k, _ := range dirSet { - goDirs = append(goDirs, k) + for goDir := range dirSet { + goDirs = append(goDirs, goDir) } return goDirs, nil @@ -148,7 +147,7 @@ func analysePackage(root, source, importpath string, stdlib map[string]bool) (pk } func loadStdlibPkgs(from string) (pkgs map[string]bool, err error) { - f, err := ioutil.ReadFile(from) + f, err := os.ReadFile(from) if err != nil { return } diff --git a/nix/buildGo/proto.nix b/nix/buildGo/proto.nix deleted file mode 100644 index 6c37f758ce..0000000000 --- a/nix/buildGo/proto.nix +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2019 Google LLC. -# SPDX-License-Identifier: Apache-2.0 -# -# This file provides derivations for the dependencies of a gRPC -# service in Go. - -{ external }: - -let - inherit (builtins) fetchGit map; -in -rec { - goProto = external { - path = "github.com/golang/protobuf"; - src = fetchGit { - url = "https://github.com/golang/protobuf"; - rev = "ed6926b37a637426117ccab59282c3839528a700"; - }; - }; - - xnet = external { - path = "golang.org/x/net"; - - src = fetchGit { - url = "https://go.googlesource.com/net"; - rev = "ffdde105785063a81acd95bdf89ea53f6e0aac2d"; - }; - - deps = [ - xtext.secure.bidirule - xtext.unicode.bidi - xtext.unicode.norm - ]; - }; - - xsys = external { - path = "golang.org/x/sys"; - src = fetchGit { - url = "https://go.googlesource.com/sys"; - rev = "bd437916bb0eb726b873ee8e9b2dcf212d32e2fd"; - }; - }; - - xtext = external { - path = "golang.org/x/text"; - src = fetchGit { - url = "https://go.googlesource.com/text"; - rev = "cbf43d21aaebfdfeb81d91a5f444d13a3046e686"; - }; - }; - - genproto = external { - path = "google.golang.org/genproto"; - src = fetchGit { - url = "https://github.com/google/go-genproto"; - # necessary because https://github.com/NixOS/nix/issues/1923 - ref = "main"; - rev = "83cc0476cb11ea0da33dacd4c6354ab192de6fe6"; - }; - - deps = with goProto; [ - proto - ptypes.any - ]; - }; - - goGrpc = external { - path = "google.golang.org/grpc"; - deps = ([ - xnet.trace - xnet.http2 - xsys.unix - xnet.http2.hpack - genproto.googleapis.rpc.status - ] ++ (with goProto; [ - proto - ptypes - ptypes.duration - ptypes.timestamp - ])); - - src = fetchGit { - url = "https://github.com/grpc/grpc-go"; - rev = "d8e3da36ac481ef00e510ca119f6b68177713689"; - }; - }; -} diff --git a/nix/buildLisp/default.nix b/nix/buildLisp/default.nix index eb85f0ff51..0d68a2818b 100644 --- a/nix/buildLisp/default.nix +++ b/nix/buildLisp/default.nix @@ -513,7 +513,6 @@ let # See https://ccl.clozure.com/docs/ccl.html#building-definitions faslExt = - /**/ if targetPlatform.isPower && targetPlatform.is32bit then "pfsl" else if targetPlatform.isPower && targetPlatform.is64bit then "p64fsl" else if targetPlatform.isx86_64 && targetPlatform.isLinux then "lx64fsl" diff --git a/nix/buildkite/default.nix b/nix/buildkite/default.nix index b04dc55a0b..9abba9408a 100644 --- a/nix/buildkite/default.nix +++ b/nix/buildkite/default.nix @@ -27,64 +27,112 @@ let inherit (pkgs) lib runCommand writeText; inherit (depot.nix.readTree) mkLabel; + + inherit (depot.nix) dependency-analyzer; in rec { - # Creates a Nix expression that yields the target at the specified - # location in the repository. - # - # This makes a distinction between normal targets (which physically - # exist in the repository) and subtargets (which are "virtual" - # targets exposed by a physical one) to make it clear in the build - # output which is which. - mkBuildExpr = target: + # Create a unique key for the buildkite pipeline based on the given derivation + # or drvPath. A consequence of using such keys is that every derivation may + # only be exposed as a single, unique step in the pipeline. + keyForDrv = drvOrPath: + let + drvPath = + if lib.isDerivation drvOrPath then drvOrPath.drvPath + else if lib.isString drvOrPath then drvOrPath + else builtins.throw "keyForDrv: expected string or derivation"; + + # Only use the drv hash to prevent escaping problems. Buildkite also has a + # limit of 100 characters on keys. + in + "drv-" + (builtins.substring 0 32 + (builtins.baseNameOf (unsafeDiscardStringContext drvPath)) + ); + + # Given an arbitrary attribute path generate a Nix expression which obtains + # this from the root of depot (assumed to be ./.). Attributes may be any + # Nix strings suitable as attribute names, not just Nix literal-safe strings. + mkBuildExpr = attrPath: let descend = expr: attr: "builtins.getAttr \"${attr}\" (${expr})"; - targetExpr = foldl' descend "import ./. {}" target.__readTree; - subtargetExpr = descend targetExpr target.__subtarget; in - if target ? __subtarget then subtargetExpr else targetExpr; + foldl' descend "import ./. {}" attrPath; # Determine whether to skip a target if it has not diverged from the # HEAD branch. - shouldSkip = parentTargetMap: label: drvPath: + shouldSkip = { parentTargetMap ? { }, label, drvPath }: if (hasAttr label parentTargetMap) && parentTargetMap."${label}".drvPath == drvPath then "Target has not changed." else false; - # Create build command for a derivation target. - mkBuildCommand = target: drvPath: concatStringsSep " " [ + # Create build command for an attribute path pointing to a derivation. + mkBuildCommand = { attrPath, drvPath, outLink ? "result" }: concatStringsSep " " [ + # If the nix build fails, the Nix command's exit status should be used. + "set -o pipefail;" + # First try to realise the drvPath of the target so we don't evaluate twice. # Nix has no concept of depending on a derivation file without depending on # at least one of its `outPath`s, so we need to discard the string context # if we don't want to build everything during pipeline construction. - "(nix-store --realise '${drvPath}' --add-root result --indirect && readlink result)" + # + # To make this more uniform with how nix-build(1) works, we call realpath(1) + # on nix-store(1)'s output since it has the habit of printing the path of the + # out link, not the store path. + "(nix-store --realise '${drvPath}' --add-root '${outLink}' --indirect | xargs -r realpath)" # Since we don't gcroot the derivation files, they may be deleted by the # garbage collector. In that case we can reevaluate and build the attribute # using nix-build. - "|| (test ! -f '${drvPath}' && nix-build -E '${mkBuildExpr target}' --show-trace)" + "|| (test ! -f '${drvPath}' && nix-build -E '${mkBuildExpr attrPath}' --show-trace --out-link '${outLink}')" ]; + # Attribute path of a target relative to the depot root. Needs to take into + # account whether the target is a physical target (which corresponds to a path + # in the filesystem) or the subtarget of a physical target. + targetAttrPath = target: + target.__readTree + ++ lib.optionals (target ? __subtarget) [ target.__subtarget ]; + + # Given a derivation (identified by drvPath) that is part of the list of + # targets passed to mkPipeline, determine all derivations that it depends on + # and are also part of the pipeline. Finally, return the keys of the steps + # that build them. This is used to populate `depends_on` in `mkStep`. + # + # See //nix/dependency-analyzer for documentation on the structure of `targetDepMap`. + getTargetPipelineDeps = targetDepMap: drvPath: + # Sanity check: We should only call this function on targets explicitly + # passed to mkPipeline. Thus it should have been passed as a “known” drv to + # dependency-analyzer. + assert targetDepMap.${drvPath}.known; + builtins.map keyForDrv targetDepMap.${drvPath}.knownDeps; + # Create a pipeline step from a single target. - mkStep = headBranch: parentTargetMap: target: cancelOnBuildFailing: + mkStep = { headBranch, parentTargetMap, targetDepMap, target, cancelOnBuildFailing }: let label = mkLabel target; drvPath = unsafeDiscardStringContext target.drvPath; - shouldSkip' = shouldSkip parentTargetMap; in { label = ":nix: " + label; - key = hashString "sha1" label; - skip = shouldSkip' label drvPath; - command = mkBuildCommand target drvPath; + key = keyForDrv target; + skip = shouldSkip { inherit label drvPath parentTargetMap; }; + command = mkBuildCommand { + attrPath = targetAttrPath target; + inherit drvPath; + }; env.READTREE_TARGET = label; cancel_on_build_failing = cancelOnBuildFailing; # Add a dependency on the initial static pipeline step which # always runs. This allows build steps uploaded in batches to # start running before all batches have been uploaded. - depends_on = ":init:"; - }; + depends_on = [ ":init:" ] + ++ getTargetPipelineDeps targetDepMap drvPath + ++ lib.optionals (target ? meta.ci.buildkiteExtraDeps) target.meta.ci.buildkiteExtraDeps; + } // lib.optionalAttrs (target ? meta.timeout) { + timeout_in_minutes = target.meta.timeout / 60; + # Additional arguments to set on the step. + # Keep in mind these *overwrite* existing step args, not extend. Use with caution. + } // lib.optionalAttrs (target ? meta.ci.buildkiteExtraStepArgs) target.meta.ci.buildkiteExtraStepArgs; # Helper function to inelegantly divide a list into chunks of at # most n elements. @@ -182,18 +230,24 @@ rec { # logic/optimisation depends on knowing whether is executing. buildEnabled = elem "build" enabledPhases; + # Dependency relations between the `drvTargets`. See also //nix/dependency-analyzer. + targetDepMap = dependency-analyzer (dependency-analyzer.drvsToPaths drvTargets); + # Convert a target into all of its steps, separated by build # phase (as phases end up in different chunks). targetToSteps = target: let - step = mkStep headBranch parentTargetMap target cancelOnBuildFailing; + mkStepArgs = { + inherit headBranch parentTargetMap targetDepMap target cancelOnBuildFailing; + }; + step = mkStep mkStepArgs; # Same step, but with an override function applied. This is # used in mkExtraStep if the extra step needs to modify the # parent derivation somehow. # # Note that this will never affect the label. - overridable = f: mkStep headBranch parentTargetMap (f target) cancelOnBuildFailing; + overridable = f: mkStep (mkStepArgs // { target = (f target); }); # Split extra steps by phase. splitExtraSteps = lib.groupBy ({ phase, ... }: phase) @@ -202,7 +256,7 @@ rec { extraSteps = mapAttrs (_: steps: - map (mkExtraStep buildEnabled) steps) + map (mkExtraStep (targetAttrPath target) buildEnabled) steps) splitExtraSteps; in if !buildEnabled then extraSteps @@ -252,9 +306,7 @@ rec { # Include the attrPath in the output to reconstruct the drv # without parsing the human-readable label. - attrPath = target.__readTree ++ lib.optionals (target ? __subtarget) [ - target.__subtarget - ]; + attrPath = targetAttrPath target; }; }) drvTargets))); @@ -278,10 +330,6 @@ rec { # confirmation. These steps always run after the main build is # done and have no influence on CI status. # - # postBuild (optional): If set to true, this step will run after - # all primary build steps (that is, after status has been reported - # back to CI). - # # needsOutput (optional): If set to true, the parent derivation # will be built in the working directory before running the # command. Output will be available as 'result'. @@ -332,12 +380,7 @@ rec { , alwaysRun ? false , prompt ? false , softFail ? false - - # TODO(tazjin): Default to 'build' after 2022-10-01. - , phase ? if (isNull postBuild || !postBuild) then "build" else "release" - - # TODO(tazjin): Turn into hard-failure after 2022-10-01. - , postBuild ? null + , phase ? "build" , skip ? false , agents ? null }: @@ -368,32 +411,12 @@ rec { skip agents; - # //nix/buildkite is growing a new feature for adding different - # "build phases" which supersedes the previous `postBuild` - # boolean API. - # - # To help users transition, emit warnings if the old API is used. - phase = lib.warnIfNot (isNull postBuild) '' - In step '${label}' (from ${parentLabel}): - - Please note: The CI system is introducing support for running - steps in different build phases. - - The currently supported phases are 'build' (all Nix targets, - extra steps such as tests that feed into the build results, - etc.) and 'release' (steps that run after builds and tests - have already completed). - - This replaces the previous boolean `postBuild` API in extra - step definitions. Please remove the `postBuild` parameter from - this step and instead set `phase = ${phase};`. - '' - validPhase; + phase = validPhase; prompt = lib.throwIf (prompt != false && phase == "build") '' In step '${label}' (from ${parentLabel}): - The 'prompt' feature can only be used by steps in the "release" + The 'prompt' feature can not be used by steps in the "build" phase, because CI builds should not be gated on manual human approvals. '' @@ -402,9 +425,13 @@ rec { # Create the Buildkite configuration for an extra step, optionally # wrapping it in a gate group. - mkExtraStep = buildEnabled: cfg: + mkExtraStep = parentAttrPath: buildEnabled: cfg: let + # ATTN: needs to match an entry in .gitignore so that the tree won't get dirty + commandScriptLink = "nix-buildkite-extra-step-command-script"; + step = { + key = "extra-step-" + hashString "sha1" "${cfg.label}-${cfg.parentLabel}"; label = ":gear: ${cfg.label} (from ${cfg.parentLabel})"; skip = let @@ -429,8 +456,22 @@ rec { "echo '~~~ Preparing build output of ${cfg.parentLabel}'" } ${lib.optionalString cfg.needsOutput cfg.parent.command} - echo '+++ Running extra step command' - exec ${cfg.command} + echo '--- Building extra step script' + command_script="$(${ + # Using command substitution in this way assumes the script drv only has one output + assert builtins.length cfg.command.outputs == 1; + mkBuildCommand { + # script is exposed at <parent>.meta.ci.extraSteps.<key>.command + attrPath = + parentAttrPath + ++ [ "meta" "ci" "extraSteps" cfg.key "command" ]; + drvPath = unsafeDiscardStringContext cfg.command.drvPath; + # make sure it doesn't conflict with result (from needsOutput) + outLink = commandScriptLink; + } + })" + echo '+++ Running extra step script' + exec "$command_script" ''; soft_fail = cfg.softFail; diff --git a/nix/buildkite/fetch-parent-targets.sh b/nix/buildkite/fetch-parent-targets.sh index 8afac1e5ec..08c2d1f3ab 100755 --- a/nix/buildkite/fetch-parent-targets.sh +++ b/nix/buildkite/fetch-parent-targets.sh @@ -2,43 +2,54 @@ set -ueo pipefail # Each Buildkite build stores the derivation target map as a pipeline -# artifact. This script determines the most appropriate commit (the -# fork point of the current chain from HEAD) and fetches the artifact. +# artifact. To reduce the amount of work done by CI, each CI build is +# diffed against the latest such derivation map found for the +# repository. # -# New builds can be based on HEAD before the pipeline for the last -# commit has finished, in which case it is possible that the fork -# point has no derivation map. To account for this, up to 3 commits -# prior to HEAD are also queried to find a map. +# Note that this does not take into account when the currently +# processing CL was forked off from the canonical branch, meaning that +# things like nixpkgs updates in between will cause mass rebuilds in +# any case. # # If no map is found, the failure mode is not critical: We simply # build all targets. +readonly REPO_ROOT=$(git rev-parse --show-toplevel) + : ${DRVMAP_PATH:=pipeline/drvmap.json} : ${BUILDKITE_TOKEN_PATH:=~/buildkite-token} -git fetch -v origin "${BUILDKITE_PIPELINE_DEFAULT_BRANCH}" - -FIRST=$(git merge-base FETCH_HEAD "${BUILDKITE_COMMIT}") -SECOND=$(git rev-parse "$FIRST~1") -THIRD=$(git rev-parse "$FIRST~2") - -function most_relevant_builds { +# Runs a fairly complex Buildkite GraphQL query that attempts to fetch all +# pipeline-gen steps from the default branch, as long as one appears within the +# last 50 builds or so. The query restricts build states to running or passed +# builds, which means that it *should* be unlikely that nothing is found. +# +# There is no way to filter this more loosely (e.g. by saying "any recent build +# matching these conditions"). +# +# The returned data structure is complex, and disassembled by a JQ script that +# first filters out all builds with no matching jobs (e.g. builds that are still +# in progress), and then filters those down to builds with artifacts, and then +# to drvmap artifacts specifically. +# +# If a recent drvmap was found, this returns its download URL. Otherwise, it +# returns the string "null". +function latest_drvmap_url { set -u curl 'https://graphql.buildkite.com/v1' \ --silent \ -H "Authorization: Bearer $(cat ${BUILDKITE_TOKEN_PATH})" \ - -d "{\"query\": \"query { pipeline(slug: \\\"$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_PIPELINE_SLUG\\\") { builds(commit: [\\\"$FIRST\\\",\\\"$SECOND\\\",\\\"$THIRD\\\"]) { edges { node { uuid }}}}}\"}" | \ - jq -r '.data.pipeline.builds.edges[] | .node.uuid' + -H "Content-Type: application/json" \ + -d "{\"query\": \"{ pipeline(slug: \\\"$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_PIPELINE_SLUG\\\") { builds(first: 50, branch: [\\\"%default\\\"], state: [RUNNING, PASSED]) { edges { node { jobs(passed: true, first: 1, type: [COMMAND], step: {key: [\\\"pipeline-gen\\\"]}) { edges { node { ... on JobTypeCommand { url artifacts { edges { node { downloadURL path }}}}}}}}}}}}\"}" | tee out.json | \ + jq -r '[.data.pipeline.builds.edges[] | select((.node.jobs.edges | length) > 0) | .node.jobs.edges[] | .node.artifacts[][] | select(.node.path == "pipeline/drvmap.json")][0].node.downloadURL' } -mkdir -p tmp -for build in $(most_relevant_builds); do - echo "Checking artifacts for build $build" - buildkite-agent artifact download --build "${build}" "${DRVMAP_PATH}" 'tmp/' || true +readonly DOWNLOAD_URL=$(latest_drvmap_url) - if [[ -f "tmp/${DRVMAP_PATH}" ]]; then - echo "Fetched target map from build ${build}" - mv "tmp/${DRVMAP_PATH}" tmp/parent-target-map.json - break - fi -done +if [[ ${DOWNLOAD_URL} != "null" ]]; then + mkdir -p tmp + curl -o tmp/parent-target-map.json ${DOWNLOAD_URL} && echo "downloaded parent derivation map" \ + || echo "failed to download derivation map!" +else + echo "no derivation map found!" +fi diff --git a/nix/dependency-analyzer/default.nix b/nix/dependency-analyzer/default.nix index 54ff72912e..2ec8d7b5b9 100644 --- a/nix/dependency-analyzer/default.nix +++ b/nix/dependency-analyzer/default.nix @@ -16,30 +16,35 @@ let # # TODO(sterni): clean this up and expose it directDrvDeps = - if lib.versionAtLeast builtins.nixVersion "2.6" - then - # Since https://github.com/NixOS/nix/pull/1643, Nix apparently »preserves - # string context« through a readFile invocation. This has the side effect - # that it becomes possible to query the actual references a store path has. - # Not a 100% sure this is intended, but _very_ convenient for us here. - drvPath: - # if the passed path is not a derivation we can't necessarily get its - # dependencies, since it may not be representable as a Nix string due to - # NUL bytes, e.g. compressed patch files imported into the Nix store. - if builtins.match "^.+\\.drv$" drvPath == null - then [ ] - else builtins.attrNames (builtins.getContext (builtins.readFile drvPath)) - else - # For Nix < 2.6 we have to rely on HACK, namely grepping for quoted store - # path references in the file. In the future this should be replaced by - # a proper derivation parser. - drvPath: builtins.concatLists ( - builtins.filter builtins.isList ( - builtins.split - "\"(${lib.escapeRegex builtins.storeDir}/[[:alnum:]+._?=-]+.drv)\"" - (builtins.readFile drvPath) - ) - ); + let + getDeps = + if lib.versionAtLeast builtins.nixVersion "2.6" + then + # Since https://github.com/NixOS/nix/pull/1643, Nix apparently »preserves + # string context« through a readFile invocation. This has the side effect + # that it becomes possible to query the actual references a store path has. + # Not a 100% sure this is intended, but _very_ convenient for us here. + drvPath: + builtins.attrNames (builtins.getContext (builtins.readFile drvPath)) + else + # For Nix < 2.6 we have to rely on HACK, namely grepping for quoted + # store path references in the file. In the future this should be + # replaced by a proper derivation parser. + drvPath: builtins.concatLists ( + builtins.filter builtins.isList ( + builtins.split + "\"(${lib.escapeRegex builtins.storeDir}/[[:alnum:]+._?=-]+.drv)\"" + (builtins.readFile drvPath) + ) + ); + in + drvPath: + # if the passed path is not a derivation we can't necessarily get its + # dependencies, since it may not be representable as a Nix string due to + # NUL bytes, e.g. compressed patch files imported into the Nix store. + if builtins.match "^.+\\.drv$" drvPath == null + then [ ] + else getDeps drvPath; # Maps a list of derivation to the list of corresponding `drvPath`s. # diff --git a/nix/lazy-deps/default.nix b/nix/lazy-deps/default.nix index 3cce48d8a5..fbdb30b38e 100644 --- a/nix/lazy-deps/default.nix +++ b/nix/lazy-deps/default.nix @@ -9,11 +9,11 @@ # evaluation, and expects both `git` and `nix-build` to exist in the # user's $PATH. If required, this can be done in the shell # configuration invoking this function. -{ pkgs, ... }: +{ pkgs, lib, ... }: let inherit (builtins) attrNames attrValues mapAttrs; - inherit (pkgs.lib) concatStringsSep; + inherit (lib) fix concatStringsSep; # Create the case statement for a command invocations, optionally # overriding the `TARGET_TOOL` variable. @@ -28,20 +28,21 @@ let invocations = tools: concatStringsSep "\n" (attrValues (mapAttrs invoke tools)); in +fix (self: # Attribute set of tools that should be lazily-added to the $PATH. - - # The name of each attribute is used as the command name (on $PATH). - # It must contain the keys 'attr' (containing the Nix attribute path - # to the tool's derivation from the top-level), and may optionally - # contain the key 'cmd' to override the name of the binary inside the - # derivation. +# +# The name of each attribute is used as the command name (on $PATH). +# It must contain the keys 'attr' (containing the Nix attribute path +# to the tool's derivation from the top-level), and may optionally +# contain the key 'cmd' to override the name of the binary inside the +# derivation. tools: -pkgs.writeTextFile { - name = "lazy-dispatch"; - executable = true; - destination = "/bin/__dispatch"; +pkgs.runCommandNoCC "lazy-dispatch" +{ + passthru.overrideDeps = newTools: self (tools // newTools); + passthru.tools = tools; text = '' #!${pkgs.runtimeShell} @@ -68,8 +69,23 @@ pkgs.writeTextFile { exec "''${TARGET_TOOL}" "''${@}" ''; - checkPhase = '' - ${pkgs.stdenv.shellDryRun} "$target" - ${concatStringsSep "\n" (map link (attrNames tools))} - ''; + # Access this to get a compatible nix-shell + passthru.devShell = pkgs.mkShellNoCC { + name = "${self.name}-shell"; + packages = [ self ]; + }; } + '' + # Write the dispatch code + target=$out/bin/__dispatch + mkdir -p "$(dirname "$target")" + echo "$text" > $target + chmod +x $target + + # Add symlinks from all the tools to the dispatch + ${concatStringsSep "\n" (map link (attrNames tools))} + + # Check that it's working-ish + ${pkgs.stdenv.shellDryRun} $target + '' +) diff --git a/nix/nix-1p/README.md b/nix/nix-1p/README.md index e7cf1e2d90..309eddb51e 100644 --- a/nix/nix-1p/README.md +++ b/nix/nix-1p/README.md @@ -1,3 +1,8 @@ +> [!TIP] +> Are you interested in hacking on Nix projects for a week, together +> with other Nix users? Do you have time at the end of August? Great, +> come join us at [Volga Sprint](https://volgasprint.org/)! + Nix - A One Pager ================= @@ -9,6 +14,9 @@ Unless otherwise specified, the word "Nix" refers only to the language below. Please file an issue if something in here confuses you or you think something important is missing. +If you have Nix installed, you can try the examples below by running `nix repl` +and entering code snippets there. + <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> **Table of Contents** @@ -16,6 +24,7 @@ important is missing. - [Language constructs](#language-constructs) - [Primitives / literals](#primitives--literals) - [Operators](#operators) + - [`//` (merge) operator](#-merge-operator) - [Variable bindings](#variable-bindings) - [Functions](#functions) - [Multiple arguments (currying)](#multiple-arguments-currying) @@ -46,8 +55,7 @@ Nix is: any dependency between operations is established by depending on *data* from previous operations. - Everything in Nix is an expression, meaning that every directive returns - some kind of data. + Any valid piece of Nix code is an *expression* that returns a value. Evaluating a Nix expression *yields a single data structure*, it does not execute a sequence of operations. @@ -110,23 +118,52 @@ rec { a = 15; b = a * 2; } Nix has several operators, most of which are unsurprising: -| Syntax | Description | -|----------------------|-----------------------------------------------------------------------------| -| `+`, `-`, `*`, `/` | Numerical operations | -| `+` | String concatenation | -| `++` | List concatenation | -| `==` | Equality | -| `>`, `>=`, `<`, `<=` | Ordering comparators | -| `&&` | Logical `AND` | -| <code>||</code> | Logical `OR` | -| `e1 -> e2` | Logical implication (i.e. <code>!e1 || e2</code>) | -| `!` | Boolean negation | -| `set.attr` | Access attribute `attr` in attribute set `set` | -| `set ? attribute` | Test whether attribute set contains an attribute | -| `left // right` | Merge `left` & `right` attribute sets, with the right set taking precedence | - -Make sure to understand the `//`-operator, as it is used quite a lot and is -probably the least familiar one. +| Syntax | Description | +|---------------------------|-----------------------------------------------------------------------------| +| `+`, `-`, `*`, `/` | Numerical operations | +| `+` | String concatenation | +| `++` | List concatenation | +| `==` | Equality | +| `>`, `>=`, `<`, `<=` | Ordering comparators | +| `&&` | Logical `AND` | +| <code>||</code> | Logical `OR` | +| `e1 -> e2` | Logical implication (i.e. <code>!e1 || e2</code>) | +| `!` | Boolean negation | +| `set.attr` | Access attribute `attr` in attribute set `set` | +| `set ? attribute` | Test whether attribute set contains an attribute | +| `left // right` | Merge `left` & `right` attribute sets, with the right set taking precedence | + + +### `//` (merge) operator + +The `//`-operator is used pervasively in Nix code. You should familiarise +yourself with it, as it is likely also the least familiar one. + +It merges the left and right attribute sets given to it: + +```nix +{ a = 1; } // { b = 2; } + +# yields { a = 1; b = 2; } +``` + +Values from the right side take precedence: + +```nix +{ a = "left"; } // { a = "right"; } + +# yields { a = "right"; } +``` + +The merge operator does *not* recursively merge attribute sets; + +```nix +{ a = { b = 1; }; } // { a = { c = 2; }; } + +# yields { a = { c = 2; }; } +``` + +Helper functions for recursive merging exist in the [`lib` library](#pkgslib). ## Variable bindings @@ -323,10 +360,18 @@ let attrs = { a = 15; b = 2; }; in with attrs; a + b # 'a' and 'b' become variables in the scope following 'with' ``` +The scope of a `with`-"block" is the expression immediately following the +semicolon, i.e.: + +```nix +let attrs = { /* some attributes */ }; +in with attrs; (/* this is the scope of the `with` */) +``` + ## `import` / `NIX_PATH` / `<entry>` -Nix files can import each other by using the `import` keyword and a literal -path: +Nix files can import each other by using the builtin `import` function and a +literal path: ```nix # assuming there is a file lib.nix with some useful functions @@ -334,6 +379,8 @@ let myLib = import ./lib.nix; in myLib.usefulFunction 42 ``` +The `import` function will read and evaluate the file, and return its Nix value. + Nix files often begin with a function header to pass parameters into the rest of the file, so you will often see imports of the form `import ./some-file { ... }`. @@ -587,15 +634,15 @@ details (note: the convention has moved away from using `self` in favor of updated to reflect this). [currying]: https://en.wikipedia.org/wiki/Currying -[builtins]: https://nixos.org/nix/manual/#ssec-builtins +[builtins]: https://nixos.org/manual/nix/stable/language/builtins [nixpkgs]: https://github.com/NixOS/nixpkgs [lib-src]: https://github.com/NixOS/nixpkgs/tree/master/lib [nixdoc]: https://github.com/tazjin/nixdoc -[lib-manual]: https://nixos.org/nixpkgs/manual/#sec-functions-library -[channels]: https://nixos.org/nix/manual/#sec-channels -[trivial builders]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/trivial-builders.nix -[smkd]: https://nixos.org/nixpkgs/manual/#chap-stdenv -[drv-manual]: https://nixos.org/nix/manual/#ssec-derivation +[lib-manual]: https://nixos.org/manual/nixpkgs/stable/#sec-functions-library +[channels]: https://nixos.org/manual/nix/stable/command-ref/files/channels +[trivial builders]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/trivial-builders/default.nix +[smkd]: https://nixos.org/manual/nixpkgs/stable/#chap-stdenv +[drv-manual]: https://nixos.org/manual/nix/stable/language/derivations [fp]: https://github.com/NixOS/nixpkgs/blob/master/lib/fixed-points.nix -[on overrides]: https://nixos.org/nixpkgs/manual/#sec-overrides -[on overlays]: https://nixos.org/nixpkgs/manual/#chap-overlays +[on overrides]: https://nixos.org/manual/nixpkgs/stable/#chap-overrides +[on overlays]: https://nixos.org/manual/nixpkgs/stable/#chap-overlays diff --git a/nix/readTree/README.md b/nix/readTree/README.md index f8bbe2255e..5d430d1cfc 100644 --- a/nix/readTree/README.md +++ b/nix/readTree/README.md @@ -52,14 +52,17 @@ true;` attribute merged into it. `readTree` will follow any subdirectories of a tree and import all Nix files, with some exceptions: +* If a folder contains a `default.nix` file, no *sibling* Nix files will be + imported - however children are traversed as normal. +* If a folder contains a `default.nix` it is loaded and, if it + evaluates to a set, *merged* with the children. If it evaluates to + anything other than a set, else the children are *not traversed*. +* A folder can opt out from readTree completely by containing a + `.skip-tree` file. The content of the file is not read. These + folders will be missing completely from the readTree structure. * A folder can declare that its children are off-limit by containing a `.skip-subtree` file. Since the content of the file is not checked, it can be useful to leave a note for a human in the file. -* If a folder contains a `default.nix` file, no *sibling* Nix files will be - imported - however children are traversed as normal. -* If a folder contains a `default.nix` it is loaded and, if it evaluates to a - set, *merged* with the children. If it evaluates to anything else the children - are *not traversed*. * The `default.nix` of the top-level folder on which readTree is called is **not** read to avoid infinite recursion (as, presumably, this file is where readTree itself is called). diff --git a/nix/readTree/default.nix b/nix/readTree/default.nix index e243e85517..4a745ce33c 100644 --- a/nix/readTree/default.nix +++ b/nix/readTree/default.nix @@ -2,7 +2,7 @@ # Copyright (c) 2020-2021 The TVL Authors # SPDX-License-Identifier: MIT # -# Provides a function to automatically read a a filesystem structure +# Provides a function to automatically read a filesystem structure # into a Nix attribute set. # # Called with an attribute set taking the following arguments: @@ -41,7 +41,8 @@ let readDirVisible = path: let children = readDir path; - isVisible = f: f == ".skip-subtree" || (substring 0 1 f) != "."; + # skip hidden files, except for those that contain special instructions to readTree + isVisible = f: f == ".skip-subtree" || f == ".skip-tree" || (substring 0 1 f) != "."; names = filter isVisible (attrNames children); in listToAttrs (map @@ -86,16 +87,39 @@ let pathType = builtins.typeOf importedFile; in if pathType != "lambda" - then builtins.throw "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }" + then throw "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }" else importedFile (filter parts (argsWithPath args parts)); nixFileName = file: let res = match "(.*)\\.nix" file; in if res == null then null else head res; - readTree = { args, initPath, rootDir, parts, argsFilter, scopedArgs }: + # Internal implementation of readTree, which handles things like the + # skipping of trees and subtrees. + # + # This method returns an attribute sets with either of two shapes: + # + # { ok = ...; } # a tree was read successfully + # { skip = true; } # a tree was skipped + # + # The higher-level `readTree` method assembles the final attribute + # set out of these results at the top-level, and the internal + # `children` implementation unwraps and processes nested trees. + readTreeImpl = { args, initPath, rootDir, parts, argsFilter, scopedArgs }: let dir = readDirVisible initPath; + + # Determine whether any part of this tree should be skipped. + # + # Adding a `.skip-subtree` file will still allow the import of + # the current node's "default.nix" file, but stop recursion + # there. + # + # Adding a `.skip-tree` file will completely ignore the folder + # in which this file is located. + skipTree = hasAttr ".skip-tree" dir; + skipSubtree = skipTree || hasAttr ".skip-subtree" dir; + joinChild = c: initPath + ("/" + c); self = @@ -103,19 +127,17 @@ let then { __readTree = [ ]; } else importFile args scopedArgs initPath parts argsFilter; - # Import subdirectories of the current one, unless the special - # `.skip-subtree` file exists which makes readTree ignore the - # children. + # Import subdirectories of the current one, unless any skip + # instructions exist. # # This file can optionally contain information on why the tree # should be ignored, but its content is not inspected by # readTree filterDir = f: dir."${f}" == "directory"; - children = if hasAttr ".skip-subtree" dir then [ ] else - map + filteredChildren = map (c: { name = c; - value = readTree { + value = readTreeImpl { inherit argsFilter scopedArgs; args = args; initPath = (joinChild c); @@ -125,9 +147,15 @@ let }) (filter filterDir (attrNames dir)); + # Remove skipped children from the final set, and unwrap the + # result set. + children = + if skipSubtree then [ ] + else map ({ name, value }: { inherit name; value = value.ok; }) (filter (child: child.value ? ok) filteredChildren); + # Import Nix files nixFiles = - if hasAttr ".skip-subtree" dir then [ ] + if skipSubtree then [ ] else filter (f: f != null) (map nixFileName (attrNames dir)); nixChildren = map (c: @@ -154,9 +182,23 @@ let ); in - if isAttrs nodeValue - then merge nodeValue (allChildren // (marker parts allChildren)) - else nodeValue; + if skipTree + then { skip = true; } + else { + ok = + if isAttrs nodeValue + then merge nodeValue (allChildren // (marker parts allChildren)) + else nodeValue; + }; + + # Top-level implementation of readTree itself. + readTree = args: + let + tree = readTreeImpl args; + in + if tree ? skip + then throw "Top-level folder has a .skip-tree marker and could not be read by readTree!" + else tree.ok; # Helper function to fetch subtargets from a target. This is a # temporary helper to warn on the use of the `meta.targets` diff --git a/nix/readTree/tests/default.nix b/nix/readTree/tests/default.nix index fcca141714..6f9eb02eff 100644 --- a/nix/readTree/tests/default.nix +++ b/nix/readTree/tests/default.nix @@ -41,6 +41,16 @@ let }; traversal-logic = it "corresponds to the traversal logic in the README" [ + (assertEq "skip-tree/a is read" + tree-tl.skip-tree.a + "a is read normally") + (assertEq "skip-tree does not contain b" + (builtins.attrNames tree-tl.skip-tree) + [ "__readTree" "__readTreeChildren" "a" ]) + (assertEq "skip-tree children list does not contain b" + tree-tl.skip-tree.__readTreeChildren + [ "a" ]) + (assertEq "skip subtree default.nix is read" tree-tl.skip-subtree.but "the default.nix is still read") diff --git a/nix/readTree/tests/test-tree-traversal/skip-tree/a/default.nix b/nix/readTree/tests/test-tree-traversal/skip-tree/a/default.nix new file mode 100644 index 0000000000..186488be3c --- /dev/null +++ b/nix/readTree/tests/test-tree-traversal/skip-tree/a/default.nix @@ -0,0 +1 @@ +_: "a is read normally" diff --git a/nix/readTree/tests/test-tree-traversal/skip-tree/b/.skip-tree b/nix/readTree/tests/test-tree-traversal/skip-tree/b/.skip-tree new file mode 100644 index 0000000000..34936b45d1 --- /dev/null +++ b/nix/readTree/tests/test-tree-traversal/skip-tree/b/.skip-tree @@ -0,0 +1 @@ +b subfolder should be skipped completely diff --git a/nix/readTree/tests/test-tree-traversal/skip-tree/b/default.nix b/nix/readTree/tests/test-tree-traversal/skip-tree/b/default.nix new file mode 100644 index 0000000000..7903f8e95a --- /dev/null +++ b/nix/readTree/tests/test-tree-traversal/skip-tree/b/default.nix @@ -0,0 +1 @@ +throw "b is skipped completely" diff --git a/nix/renderMarkdown/default.nix b/nix/renderMarkdown/default.nix index c5e830c834..8759ada0fe 100644 --- a/nix/renderMarkdown/default.nix +++ b/nix/renderMarkdown/default.nix @@ -3,6 +3,19 @@ with depot.nix.yants; -defun [ path drv ] (file: pkgs.runCommand "${file}.rendered.html" { } '' - cat ${file} | ${depot.tools.cheddar}/bin/cheddar --about-filter ${file} > $out -'') +let + args = struct "args" { + path = path; + tagfilter = option bool; + }; +in +defun [ (either path args) drv ] + (arg: pkgs.runCommand "${arg.path or arg}.rendered.html" { } + ( + let + tagfilter = if (arg.tagfilter or true) then "" else "--no-tagfilter"; + in + '' + cat ${arg.path or arg} | ${depot.tools.cheddar}/bin/cheddar --about-filter ${tagfilter} ${arg.path or arg} > $out + '' + )) diff --git a/nix/sparseTree/default.nix b/nix/sparseTree/default.nix index bf56a5348c..35fa459e1c 100644 --- a/nix/sparseTree/default.nix +++ b/nix/sparseTree/default.nix @@ -2,22 +2,33 @@ # and directories if they are listed in a supplied list: # # # A very minimal depot -# sparseTree ./depot [ -# ./default.nix -# ./depot/nix/readTree/default.nix -# ./third_party/nixpkgs -# ./third_party/overlays -# ] +# sparseTree { +# root = ./depot; +# paths = [ +# ./default.nix +# ./depot/nix/readTree/default.nix +# ./third_party/nixpkgs +# ./third_party/overlays +# ]; +# } { pkgs, lib, ... }: -# root path to use as a reference point -root: -# list of paths below `root` that should be -# included in the resulting directory -# -# If path, need to refer to the actual file / directory to be included. -# If a string, it is treated as a string relative to the root. -paths: +{ + # root path to use as a reference point + root +, # list of paths below `root` that should be + # included in the resulting directory + # + # If path, need to refer to the actual file / directory to be included. + # If a string, it is treated as a string relative to the root. + paths +, # (optional) name to use for the derivation + # + # This should always be set when using roots that do not have + # controlled names, such as when passing the top-level of a git + # repository (e.g. `depot.path.origSrc`). + name ? builtins.baseNameOf root +}: let rootLength = builtins.stringLength (toString root); @@ -45,7 +56,6 @@ let let withLeading = p: if builtins.substring 0 1 p == "/" then p else "/" + p; fullPath = - /**/ if builtins.isPath path then path else if builtins.isString path then (root + withLeading path) else builtins.throw "Unsupported path type ${builtins.typeOf path}"; @@ -64,7 +74,7 @@ in # TODO(sterni): teach readTree to also read symlinked directories, # so we ln -sT instead of cp -aT. -pkgs.runCommand "sparse-${builtins.baseNameOf root}" { } ( +pkgs.runCommand "sparse-${name}" { } ( lib.concatMapStrings ({ src, dst }: '' mkdir -p "$(dirname "$out${dst}")" diff --git a/nix/writeTree/OWNERS b/nix/writeTree/OWNERS new file mode 100644 index 0000000000..b381c4e660 --- /dev/null +++ b/nix/writeTree/OWNERS @@ -0,0 +1 @@ +aspen diff --git a/nix/writeTree/default.nix b/nix/writeTree/default.nix new file mode 100644 index 0000000000..0c7c2a130f --- /dev/null +++ b/nix/writeTree/default.nix @@ -0,0 +1,43 @@ +{ depot, lib, pkgs, ... }: +let + inherit (lib) fix pipe mapAttrsToList isAttrs concatLines isString isDerivation isPath; + + # TODO(sterni): move to //nix/utils with clearer naming and alternative similar to lib.types.path + isPathLike = value: + isPath value + || isDerivation value + || (isString value && builtins.hasContext value); + + esc = s: lib.escapeShellArg /* ensure paths import into store */ "${s}"; + + writeTreeAtPath = path: tree: + '' + mkdir -p "$out/"${esc path} + '' + + pipe tree [ + (mapAttrsToList (k: v: + if isPathLike v then + "cp -R --reflink=auto ${v} \"$out/\"${esc path}/${esc k}" + else if lib.isAttrs v then + writeTreeAtPath (path + "/" + k) v + else + throw "invalid type (expected path, derivation, string with context, or attrs)")) + concatLines + ]; + + /* Create a directory tree specified by a Nix attribute set structure. + + Each value in `tree` should either be a file, a directory, or another tree + attribute set. Those paths will be written to a directory tree + corresponding to the structure of the attribute set. + + Type: string -> attrSet -> derivation + */ + writeTree = name: tree: + pkgs.runCommandLocal name { } (writeTreeAtPath "" tree); +in + +# __functor trick so readTree can add the tests attribute +{ + __functor = _: writeTree; +} diff --git a/nix/writeTree/tests/default.nix b/nix/writeTree/tests/default.nix new file mode 100644 index 0000000000..c5858ee96e --- /dev/null +++ b/nix/writeTree/tests/default.nix @@ -0,0 +1,93 @@ +{ depot, pkgs, lib, ... }: + +let + inherit (pkgs) runCommand writeText writeTextFile; + inherit (depot.nix) writeTree; + + checkTree = name: tree: expected: + runCommand "writeTree-test-${name}" + { + nativeBuildInputs = [ pkgs.buildPackages.lr ]; + passAsFile = [ "expected" ]; + inherit expected; + } '' + actualPath="$NIX_BUILD_TOP/actual" + cd ${lib.escapeShellArg (writeTree name tree)} + lr . > "$actualPath" + diff -u "$expectedPath" "$actualPath" | tee "$out" + ''; +in + +depot.nix.readTree.drvTargets { + empty = checkTree "empty" { } + '' + . + ''; + + simple-paths = checkTree "simple" + { + writeTree = { + meta = { + "owners.txt" = ../OWNERS; + }; + "code.nix" = ../default.nix; + all-tests = ./.; + nested.dirs.eval-time = builtins.toFile "owothia" '' + hold me owo + ''; + }; + } + '' + . + ./writeTree + ./writeTree/all-tests + ./writeTree/all-tests/default.nix + ./writeTree/code.nix + ./writeTree/meta + ./writeTree/meta/owners.txt + ./writeTree/nested + ./writeTree/nested/dirs + ./writeTree/nested/dirs/eval-time + ''; + + empty-dirs = checkTree "empty-dirs" + { + this.dir.is.empty = { }; + so.is.this.one = { }; + } + '' + . + ./so + ./so/is + ./so/is/this + ./so/is/this/one + ./this + ./this/dir + ./this/dir/is + ./this/dir/is/empty + ''; + + drvs = checkTree "drvs" + { + file-drv = writeText "road.txt" '' + Any road followed precisely to its end leads precisely nowhere. + ''; + dir-drv = writeTextFile { + name = "dir-of-text"; + destination = "/text/in/more/dirs.txt"; + text = '' + Climb the mountain just a little bit to test that it’s a mountain. + From the top of the mountain, you cannot see the mountain. + ''; + }; + } + '' + . + ./dir-drv + ./dir-drv/text + ./dir-drv/text/in + ./dir-drv/text/in/more + ./dir-drv/text/in/more/dirs.txt + ./file-drv + ''; +} |