This allows pinning the name of the sparse tree derivation, which stops the continous rebuilding of tvix-store-proto dependents. I've opted to let the function take an attribute set instead and refactored the call sites appropriately. Change-Id: I3e57785094b1adbfffa24caf9f1c3384844fa200 Reviewed-on: https://cl.tvl.fyi/c/depot/+/8965 Reviewed-by: grfn <grfn@gws.fyi> Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
		
			
				
	
	
		
			272 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ depot, lib, pkgs, ... }:
 | 
						|
 | 
						|
let
 | 
						|
 | 
						|
  inherit (depot.nix)
 | 
						|
    runExecline
 | 
						|
    getBins
 | 
						|
    utils
 | 
						|
    sparseTree
 | 
						|
    nint
 | 
						|
    ;
 | 
						|
 | 
						|
  minimalDepot = sparseTree {
 | 
						|
    root = depot.path.origSrc;
 | 
						|
    name = "minimal-depot";
 | 
						|
 | 
						|
    paths = [
 | 
						|
      # general depot things
 | 
						|
      "default.nix"
 | 
						|
      "nix/readTree"
 | 
						|
      # nixpkgs for lib and packages
 | 
						|
      "third_party/nixpkgs"
 | 
						|
      "third_party/overlays"
 | 
						|
      # bubblegum and its dependencies
 | 
						|
      "web/bubblegum"
 | 
						|
      "nix/runExecline"
 | 
						|
      "nix/utils"
 | 
						|
      "nix/sparseTree"
 | 
						|
      # tvix docs for svg demo
 | 
						|
      "tvix/docs"
 | 
						|
      # for blog.nix
 | 
						|
      "users/sterni/nix"
 | 
						|
    ];
 | 
						|
  };
 | 
						|
 | 
						|
  statusCodes = {
 | 
						|
    # 1xx
 | 
						|
    "Continue" = 100;
 | 
						|
    "Switching Protocols" = 101;
 | 
						|
    "Processing" = 102;
 | 
						|
    "Early Hints" = 103;
 | 
						|
    # 2xx
 | 
						|
    "OK" = 200;
 | 
						|
    "Created" = 201;
 | 
						|
    "Accepted" = 202;
 | 
						|
    "Non-Authoritative Information" = 203;
 | 
						|
    "No Content" = 204;
 | 
						|
    "Reset Content" = 205;
 | 
						|
    "Partial Content" = 206;
 | 
						|
    "Multi Status" = 207;
 | 
						|
    "Already Reported" = 208;
 | 
						|
    "IM Used" = 226;
 | 
						|
    # 3xx
 | 
						|
    "Multiple Choices" = 300;
 | 
						|
    "Moved Permanently" = 301;
 | 
						|
    "Found" = 302;
 | 
						|
    "See Other" = 303;
 | 
						|
    "Not Modified" = 304;
 | 
						|
    "Use Proxy" = 305;
 | 
						|
    "Switch Proxy" = 306;
 | 
						|
    "Temporary Redirect" = 307;
 | 
						|
    "Permanent Redirect" = 308;
 | 
						|
    # 4xx
 | 
						|
    "Bad Request" = 400;
 | 
						|
    "Unauthorized" = 401;
 | 
						|
    "Payment Required" = 402;
 | 
						|
    "Forbidden" = 403;
 | 
						|
    "Not Found" = 404;
 | 
						|
    "Method Not Allowed" = 405;
 | 
						|
    "Not Acceptable" = 406;
 | 
						|
    "Proxy Authentication Required" = 407;
 | 
						|
    "Request Timeout" = 408;
 | 
						|
    "Conflict" = 409;
 | 
						|
    "Gone" = 410;
 | 
						|
    "Length Required" = 411;
 | 
						|
    "Precondition Failed" = 412;
 | 
						|
    "Payload Too Large" = 413;
 | 
						|
    "URI Too Long" = 414;
 | 
						|
    "Unsupported Media Type" = 415;
 | 
						|
    "Range Not Satisfiable" = 416;
 | 
						|
    "Expectation Failed" = 417;
 | 
						|
    "I'm a teapot" = 418;
 | 
						|
    "Misdirected Request" = 421;
 | 
						|
    "Unprocessable Entity" = 422;
 | 
						|
    "Locked" = 423;
 | 
						|
    "Failed Dependency" = 424;
 | 
						|
    "Too Early" = 425;
 | 
						|
    "Upgrade Required" = 426;
 | 
						|
    "Precondition Required" = 428;
 | 
						|
    "Too Many Requests" = 429;
 | 
						|
    "Request Header Fields Too Large" = 431;
 | 
						|
    "Unavailable For Legal Reasons" = 451;
 | 
						|
    # 5xx
 | 
						|
    "Internal Server Error" = 500;
 | 
						|
    "Not Implemented" = 501;
 | 
						|
    "Bad Gateway" = 502;
 | 
						|
    "Service Unavailable" = 503;
 | 
						|
    "Gateway Timeout" = 504;
 | 
						|
    "HTTP Version Not Supported" = 505;
 | 
						|
    "Variant Also Negotiates" = 506;
 | 
						|
    "Insufficient Storage" = 507;
 | 
						|
    "Loop Detected" = 508;
 | 
						|
    "Not Extended" = 510;
 | 
						|
    "Network Authentication Required" = 511;
 | 
						|
  };
 | 
						|
 | 
						|
  /* Generate a CGI response. Takes three arguments:
 | 
						|
 | 
						|
     1. Status of the response as a string which is
 | 
						|
        the descriptive name in the protocol, e. g.
 | 
						|
        `"OK"`, `"Not Found"` etc.
 | 
						|
     2. Attribute set describing extra headers to
 | 
						|
        send, keys and values should both be strings.
 | 
						|
     3. Response content as a string.
 | 
						|
 | 
						|
     See the [README](./README.md) for an example.
 | 
						|
 | 
						|
    Type: either int string -> attrs string -> string -> string
 | 
						|
  */
 | 
						|
  respond =
 | 
						|
    # response status as an integer (status code) or its
 | 
						|
    # textual representation in the HTTP protocol.
 | 
						|
    # See `statusCodes` for a list of valid options.
 | 
						|
    statusArg:
 | 
						|
    # headers as an attribute set of strings
 | 
						|
    headers:
 | 
						|
    # response body as a string
 | 
						|
    bodyArg:
 | 
						|
    let
 | 
						|
      status =
 | 
						|
        if builtins.isInt statusArg
 | 
						|
        then {
 | 
						|
          code = statusArg;
 | 
						|
          line = lib.findFirst
 | 
						|
            (line: statusCodes."${line}" == statusArg)
 | 
						|
            null
 | 
						|
            (builtins.attrNames statusCodes);
 | 
						|
        } else if builtins.isString statusArg then {
 | 
						|
          code = statusCodes."${statusArg}" or null;
 | 
						|
          line = statusArg;
 | 
						|
        } else {
 | 
						|
          code = null;
 | 
						|
          line = null;
 | 
						|
        };
 | 
						|
      renderedHeaders = lib.concatStrings
 | 
						|
        (lib.mapAttrsToList (n: v: "${n}: ${toString v}\r\n") headers);
 | 
						|
      internalError = msg: respond 500
 | 
						|
        {
 | 
						|
          Content-type = "text/plain";
 | 
						|
        } "bubblegum error: ${msg}";
 | 
						|
      body = builtins.tryEval bodyArg;
 | 
						|
    in
 | 
						|
    if status.code == null || status.line == null
 | 
						|
    then internalError "Invalid status ${lib.generators.toPretty {} statusArg}."
 | 
						|
    else if !body.success
 | 
						|
    then internalError "Unknown evaluation error in user code"
 | 
						|
    else
 | 
						|
      lib.concatStrings [
 | 
						|
        "Status: ${toString status.code} ${status.line}\r\n"
 | 
						|
        renderedHeaders
 | 
						|
        "\r\n"
 | 
						|
        body.value
 | 
						|
      ];
 | 
						|
 | 
						|
  /* Returns the value of the `SCRIPT_NAME` environment
 | 
						|
     variable used by CGI.
 | 
						|
  */
 | 
						|
  scriptName = builtins.getEnv "SCRIPT_NAME";
 | 
						|
 | 
						|
  /* Returns the value of the `PATH_INFO` environment
 | 
						|
     variable used by CGI. All cases that could be
 | 
						|
     considered as the CGI script's root (i. e.
 | 
						|
     `PATH_INFO` is empty or `/`) is mapped to `"/"`
 | 
						|
     for convenience.
 | 
						|
  */
 | 
						|
  pathInfo =
 | 
						|
    let
 | 
						|
      p = builtins.getEnv "PATH_INFO";
 | 
						|
    in
 | 
						|
    if builtins.stringLength p == 0
 | 
						|
    then "/"
 | 
						|
    else p;
 | 
						|
 | 
						|
  /* Helper function which converts a path from the
 | 
						|
     root of the CGI script (i. e. something which
 | 
						|
     could be the content of `PATH_INFO`) to an
 | 
						|
     absolute path from the web root by also
 | 
						|
     utilizing `scriptName`.
 | 
						|
 | 
						|
     Type: string -> string
 | 
						|
  */
 | 
						|
  absolutePath = path:
 | 
						|
    if builtins.substring 0 1 path == "/"
 | 
						|
    then "${scriptName}${path}"
 | 
						|
    else "${scriptName}/${path}";
 | 
						|
 | 
						|
  bins = getBins pkgs.coreutils [ "env" "tee" "cat" "printf" "chmod" ]
 | 
						|
    // getBins nint [ "nint" ];
 | 
						|
 | 
						|
  /* Type: args -> either path derivation string -> derivation
 | 
						|
  */
 | 
						|
  writeCGI =
 | 
						|
    {
 | 
						|
      # if given sets the `PATH` to search for `nix-instantiate`
 | 
						|
      # Useful when using for example thttpd which unsets `PATH`
 | 
						|
      # in the CGI environment.
 | 
						|
      binPath ? ""
 | 
						|
      # name of the resulting derivation. Defaults to `baseNameOf`
 | 
						|
      # the input path or name of the input derivation.
 | 
						|
      # Must be given if the input is a string.
 | 
						|
    , name ? null
 | 
						|
    , ...
 | 
						|
    }@args:
 | 
						|
    input:
 | 
						|
    let
 | 
						|
      drvName =
 | 
						|
        if builtins.isString input || args ? name
 | 
						|
        then args.name
 | 
						|
        else utils.storePathName input;
 | 
						|
      script =
 | 
						|
        if builtins.isPath input || lib.isDerivation input
 | 
						|
        then input
 | 
						|
        else if builtins.isString input
 | 
						|
        then pkgs.writeText "${drvName}-source" input
 | 
						|
        else builtins.throw "Unsupported input: ${lib.generators.toPretty {} input}";
 | 
						|
      shebang = lib.concatStringsSep " " ([
 | 
						|
        "#!${bins.env}"
 | 
						|
        # use the slightly cursed /usr/bin/env -S which allows us
 | 
						|
        # to pass any number of arguments to our interpreter
 | 
						|
        # instead of maximum one using plain shebang which considers
 | 
						|
        # everything after the first space as the second argument.
 | 
						|
        "-S"
 | 
						|
      ] ++ lib.optionals (builtins.stringLength binPath > 0) [
 | 
						|
        "PATH=${binPath}"
 | 
						|
      ] ++ [
 | 
						|
        "${bins.nint}"
 | 
						|
        # always pass depot so scripts can use this library
 | 
						|
        "--arg depot '(import ${minimalDepot} {})'"
 | 
						|
      ]);
 | 
						|
    in
 | 
						|
    runExecline.local drvName { } [
 | 
						|
      "importas"
 | 
						|
      "out"
 | 
						|
      "out"
 | 
						|
      "pipeline"
 | 
						|
      [
 | 
						|
        "foreground"
 | 
						|
        [
 | 
						|
          "if"
 | 
						|
          [ bins.printf "%s\n" shebang ]
 | 
						|
        ]
 | 
						|
        "if"
 | 
						|
        [ bins.cat script ]
 | 
						|
      ]
 | 
						|
      "if"
 | 
						|
      [ bins.tee "$out" ]
 | 
						|
      "if"
 | 
						|
      [ bins.chmod "+x" "$out" ]
 | 
						|
      "exit"
 | 
						|
      "0"
 | 
						|
    ];
 | 
						|
 | 
						|
in
 | 
						|
{
 | 
						|
  inherit
 | 
						|
    respond
 | 
						|
    pathInfo
 | 
						|
    scriptName
 | 
						|
    absolutePath
 | 
						|
    writeCGI
 | 
						|
    ;
 | 
						|
}
 |