Profpatsch/nix/buildGo/external/default.nix
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# Copyright 2019 Google LLC.
# SPDX-License-Identifier: Apache-2.0
{ pkgs, program, package }:

let
  inherit (builtins)
    elemAt
    foldl'
    fromJSON
    head
    length
    listToAttrs
    readFile
    replaceStrings
    tail
    unsafeDiscardStringContext
    throw;

  inherit (pkgs) lib runCommand go jq ripgrep;

  pathToName = p: replaceStrings [ "/" ] [ "_" ] (toString p);

  # Collect all non-vendored dependencies from the Go standard library
  # into a file that can be used to filter them out when processing
  # dependencies.
  stdlibPackages = runCommand "stdlib-pkgs.json" { } ''
    export HOME=$PWD
    export GOPATH=/dev/null
    ${go}/bin/go list std | \
      ${ripgrep}/bin/rg -v 'vendor' | \
      ${jq}/bin/jq -R '.' | \
      ${jq}/bin/jq -c -s 'map({key: ., value: true}) | from_entries' \
      > $out
  '';

  analyser = program {
    name = "analyser";

    srcs = [
      ./main.go
    ];

    x_defs = {
      "main.stdlibList" = "${stdlibPackages}";
    };
  };

  mkset = path: value:
    if path == [ ] then { gopkg = value; }
    else { "${head path}" = mkset (tail path) value; };

  last = l: elemAt l ((length l) - 1);

  toPackage = self: src: path: depMap: entry:
    let
      localDeps = map
        (d: lib.attrByPath (d ++ [ "gopkg" ])
          (
            let
              localPath = lib.concatStringsSep "/" d;
              fullPath = if localPath == "" then path else "${path}/${localPath}";
            in
            throw "missing local dependency '${fullPath}' in package '${entry.name}' (${path})\n\nThis indicates a package within the same repository is missing from the build.\nCheck that all Go packages in the source are being analyzed correctly."
          )
          self)
        entry.localDeps;

      # Check which foreign dependencies are missing
      missingForeign = builtins.filter
        (d: !(depMap ? ${d.path}))
        entry.foreignDeps;

      # If any are missing, report them all at once
      foreignDeps =
        if missingForeign != [ ]
        then
          let
            depList = lib.concatMapStringsSep "\n"
              (d: "  - ${d.path} (imported at ${d.position})")
              missingForeign;
          in
          throw ''
            Missing ${toString (builtins.length missingForeign)} foreign ${if builtins.length missingForeign == 1 then "dependency" else "dependencies"} in '${path}':

            ${depList}

            Hint: Add these packages to deps in go-deps.nix
                  For subpackage imports (e.g., foo/bar/baz), you may need to add the parent package.
          ''
        else map (d: depMap.${d.path}) entry.foreignDeps;

      args = {
        srcs = map (f: src + ("/" + f)) entry.files;
        deps = localDeps ++ foreignDeps;
      };

      libArgs = args // {
        name = pathToName entry.name;
        path = lib.concatStringsSep "/" ([ path ] ++ entry.locator);
        sfiles = map (f: src + ("/" + f)) entry.sfiles;
      };

      binArgs = args // {
        name = (last ((lib.splitString "/" path) ++ entry.locator));
      };
    in
    if entry.isCommand then (program binArgs) else (package libArgs);

in
{ src, path, deps ? [ ] }:
let
  # Build a map of dependencies (from their import paths to their
  # derivation) so that they can be conditionally imported only in
  # sub-packages that require them.
  depMap = listToAttrs (map
    (d: {
      name = d.goImportPath;
      value = d;
    })
    (map (d: d.gopkg) deps));

  name = pathToName path;
  analysisOutput = runCommand "${name}-structure.json" { } ''
    ${analyser}/bin/analyser -path ${path} -source ${src} > $out
  '';
  # readFile adds the references of the read in file to the string context for
  # Nix >= 2.6 which would break the attribute set construction in fromJSON
  analysis = fromJSON (unsafeDiscardStringContext (readFile analysisOutput));
in
lib.fix (self: foldl' lib.recursiveUpdate { } (
  map (entry: mkset entry.locator (toPackage self src path depMap entry)) analysis
))