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
 | |
|     ;
 | |
| }
 |