Nix packaging #15

Open
quentin wants to merge 6 commits from nix into main
7 changed files with 849 additions and 0 deletions
Showing only changes of commit c395880165 - Show all commits

30
default.nix Normal file
View file

@ -0,0 +1,30 @@
let
pkgsSrc = fetchTarball {
# As of 2022-07-19
url = "https://github.com/NixOS/nixpkgs/archive/d2db10786f27619d5519b12b03fb10dc8ca95e59.tar.gz";
sha256 = "0s9gigs3ylnq5b94rfcmxvrmmr3kzhs497gksajf638d5bv7zcl5";
};
pkgs = import pkgsSrc {
overlays = [
(self: super: {
gomod = super.callPackage ./nix/builder { };
})
];
};
in
pkgs.gomod.buildGoApplication {
pname = "bottin";
version = "0.1.0";
src = ./.;
modules = ./gomod2nix.toml;
CGO_ENABLED=0;
meta = with pkgs.lib; {
description = "Bottin is a cloud-native LDAP server backed by a Consul datastore";
homepage = "https://git.deuxfleurs.fr/Deuxfleurs/bottin";
license = licenses.gpl3Plus;
platforms = platforms.linux;
};
}

111
gomod2nix.toml Normal file
View file

@ -0,0 +1,111 @@
schema = 3
[mod]
[mod."github.com/davecgh/go-spew"]
version = "v1.1.1"
hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI="
[mod."github.com/dustin/go-humanize"]
version = "v1.0.0"
hash = "sha256-gy4G1PnHD9iw2MitHX6y1y93qr3C9IncmXL7ttUMDs8="
[mod."github.com/emersion/go-sasl"]
version = "v0.0.0-20191210011802-430746ea8b9b"
hash = "sha256-bADpAn0ZhlTTsEB3MsG8J31cQjTtHTzohX/wkL1aMIc="
[mod."github.com/emersion/go-smtp"]
version = "v0.12.1"
hash = "sha256-fiss5y7chfHv80vIQ9Xwx3J+3qLMA63EOP4OG3DxAtI="
[mod."github.com/go-asn1-ber/asn1-ber"]
version = "v1.3.1"
hash = "sha256-Alh6bUq9HoBDhY+n6W7xNBto/dUMxPGvucA6guarrjc="
[mod."github.com/go-ldap/ldap/v3"]
version = "v3.1.6"
hash = "sha256-UPUdYKOoCQWgl2Onbq1Oql7XU4TeYQA/+j4atwhdKbE="
[mod."github.com/google/gofuzz"]
version = "v1.0.0"
hash = "sha256-ZvgcSQt4kMwS6nvPp3jrlCHSH3bky1oBD6kytnEa5GM="
[mod."github.com/google/uuid"]
version = "v1.1.1"
hash = "sha256-66PXC/RCPUyhS9PhkIPQFR3tbM2zZYDNPGXN7JJj3UE="
[mod."github.com/gopherjs/gopherjs"]
version = "v0.0.0-20181017120253-0766667cb4d1"
hash = "sha256-AuXnjjoLbFZ85Oi8sldH117MBh+yCUB9HU5Y5syJ7Lg="
[mod."github.com/gorilla/mux"]
version = "v1.7.3"
hash = "sha256-YZSIN7Ua+hPqSIrT+tiRz3aFqJ1EWHvwee+PptpHI28="
[mod."github.com/gorilla/securecookie"]
version = "v1.1.1"
hash = "sha256-IBBYWfdOuXvQsb01DaA8tBizCfAE1J2KLXIn3W+NeJk="
[mod."github.com/gorilla/sessions"]
version = "v1.2.0"
hash = "sha256-4V7yd/vf03CEsb3pz5dbLWwv7t9QgKkEhVXtc1/z5s8="
[mod."github.com/jsimonetti/pwscheme"]
version = "v0.0.0-20220125093853-4d9895f5db73"
hash = "sha256-YF3RKU/4CWvLPgGzUd7Hk/2+41OUFuRWZgzQuqcsKg0="
[mod."github.com/json-iterator/go"]
version = "v1.1.10"
hash = "sha256-jdS2C0WsgsWREBSj+YUzSqdZofMfUMecaOQ/lB9Mu6k="
[mod."github.com/jtolds/gls"]
version = "v4.20.0+incompatible"
hash = "sha256-Zu5naRjnwOYBzRv0CYhIZTizA0AajzOg7mJrL7Bo/cw="
[mod."github.com/klauspost/cpuid"]
version = "v1.2.3"
hash = "sha256-1IBlONMxKVgudV/mzNrFZB60z8w4xFjVbEU2DoIAoeg="
[mod."github.com/konsorten/go-windows-terminal-sequences"]
version = "v1.0.3"
hash = "sha256-9HojTXKv7b3HiEhYaKXDxraEfvU5vrg47FbCjTRpOvs="
[mod."github.com/minio/md5-simd"]
version = "v1.1.0"
hash = "sha256-jJbDwg7KlLB991wj1U6y+kJKOUxKVGQrDbM3nY+6qxE="
[mod."github.com/minio/minio-go/v7"]
version = "v7.0.0"
hash = "sha256-xWAELgH6mWVGKFEe2gbzvigJDNk+ELmegJe09KvUqvY="
[mod."github.com/minio/sha256-simd"]
version = "v0.1.1"
hash = "sha256-HpcuLTnpcyKe0ua2MN/ysK5cXdrwquDjrx4Y2dG6W2s="
[mod."github.com/mitchellh/go-homedir"]
version = "v1.1.0"
hash = "sha256-oduBKXHAQG8X6aqLEpqZHs5DOKe84u6WkBwi4W6cv3k="
[mod."github.com/modern-go/concurrent"]
version = "v0.0.0-20180228061459-e0a39a4cb421"
hash = "sha256-+bdeHUArnpkk4eMQIwXm9K249NkpwAjoTrXrGn/4VUE="
[mod."github.com/modern-go/reflect2"]
version = "v0.0.0-20180701023420-4b7aa43c6742"
hash = "sha256-RyIwgrPwtd4lNjLGkBVxRvu5IdXLDqf5F69QWLm0zLw="
[mod."github.com/nfnt/resize"]
version = "v0.0.0-20180221191011-83c6a9932646"
hash = "sha256-yvPV+HlDOyJsiwAcVHQkmtw8DHSXyw+cXHkigXm8rAA="
[mod."github.com/pmezard/go-difflib"]
version = "v1.0.0"
hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA="
[mod."github.com/sirupsen/logrus"]
version = "v1.6.0"
hash = "sha256-4v27X4yyl52BtZcZEnDe0tfvOaEq+TCcp7R8HBzreDM="
[mod."github.com/smartystreets/assertions"]
version = "v0.0.0-20180927180507-b2de0cb4f26d"
hash = "sha256-PoE+VQEnzJogI/mDBJ6dTCCR217nFjHfYWXQt9Vr9MQ="
[mod."github.com/smartystreets/goconvey"]
version = "v0.0.0-20190330032615-68dc04aab96a"
hash = "sha256-HD+AZE1agl1pVbQTFUKLKtjg3XdVSVLwRSu4u+UVV2M="
[mod."github.com/stretchr/objx"]
version = "v0.1.0"
hash = "sha256-az0Vd4MG3JXfaYbj0Q6AOmNkrXgmXDeQm8+BBiDXmdA="
[mod."github.com/stretchr/testify"]
version = "v1.3.0"
hash = "sha256-+mSebBNccNcxbY462iKTNTWmd5ZuUkUqFebccn3EtIA="
[mod."golang.org/x/crypto"]
version = "v0.0.0-20200214034016-1d94cc7ab1c6"
hash = "sha256-fWTzdDxt/1E8Jx7b6tmYEVqqJs5FoVVya9aEK9gDbdY="
[mod."golang.org/x/net"]
version = "v0.0.0-20190522155817-f3200d17e092"
hash = "sha256-KkNNFr+wx/pf7lSLN2ygwkQ9oCZQuef+hCtEjEX+gJE="
[mod."golang.org/x/sys"]
version = "v0.0.0-20200223170610-d5e6a3e2c0ae"
hash = "sha256-IvG2XSER2dyrVfhYieEpHcp28LOz4FrjQqN0SCeFOek="
[mod."golang.org/x/text"]
version = "v0.3.0"
hash = "sha256-0FFbaxF1ZuAQF3sCcA85e8MO6prFeHint36inija4NY="
[mod."golang.org/x/tools"]
version = "v0.0.0-20190328211700-ab21143f2384"
hash = "sha256-OcjaTxx6C/cbnUZLN2ArTrOBlBCijWJVUPaMgK67MkY="
[mod."gopkg.in/ini.v1"]
version = "v1.57.0"
hash = "sha256-WSjX+qHJ1Rf4FRMTs7udQwEBkIo+z8+EK3uB5CebrZ4="

387
nix/builder/default.nix Normal file
View file

@ -0,0 +1,387 @@
{ stdenv
, stdenvNoCC
, runCommand
, buildEnv
, lib
, fetchgit
, removeReferencesTo
, jq
, cacert
, pkgs
, pkgsBuildBuild
}:
let
inherit (builtins) substring toJSON hasAttr trace split readFile elemAt;
inherit (lib)
concatStringsSep replaceStrings removePrefix optionalString pathExists
optional concatMapStrings fetchers filterAttrs mapAttrs mapAttrsToList
warnIf optionalAttrs platforms
;
parseGoMod = import ./parser.nix;
removeExpr = refs: ''remove-references-to ${concatMapStrings (ref: " -t ${ref}") refs}'';
# Internal only build-time attributes
internal =
let
mkInternalPkg = name: src: pkgsBuildBuild.runCommand "gomod2nix-${name}"
{
inherit (pkgsBuildBuild.go) GOOS GOARCH;
nativeBuildInputs = [ pkgsBuildBuild.go ];
} ''
export HOME=$(mktemp -d)
cp ${src} src.go
go build -o $out src.go
'';
in
{
# Create a symlink tree of vendored sources
symlink = mkInternalPkg "symlink" ./symlink/symlink.go;
# Install development dependencies from tools.go
install = mkInternalPkg "symlink" ./install/install.go;
};
fetchGoModule =
{ hash
, goPackagePath
, version
, go ? pkgs.go
}:
stdenvNoCC.mkDerivation {
name = "${baseNameOf goPackagePath}_${version}";
builder = ./fetch.sh;
inherit goPackagePath version;
nativeBuildInputs = [ go jq ];
outputHashMode = "recursive";
outputHashAlgo = null;
outputHash = hash;
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
impureEnvVars = fetchers.proxyImpureEnvVars ++ [ "GOPROXY" ];
};
mkVendorEnv =
{ go
, modulesStruct
, localReplaceCommands ? [ ]
, defaultPackage ? ""
, goMod
, pwd
}:
let
localReplaceCommands =
let
localReplaceAttrs = filterAttrs (n: v: hasAttr "path" v) goMod.replace;
commands = (
mapAttrsToList
(name: value: (
''
mkdir -p $(dirname vendor/${name})
ln -s ${pwd + "/${value.path}"} vendor/${name}
''
))
localReplaceAttrs);
in
if goMod != null then commands else [ ];
sources = mapAttrs
(goPackagePath: meta: fetchGoModule {
goPackagePath = meta.replaced or goPackagePath;
inherit (meta) version hash;
inherit go;
})
modulesStruct.mod;
in
runCommand "vendor-env"
{
nativeBuildInputs = [ go ];
json = toJSON (filterAttrs (n: _: n != defaultPackage) modulesStruct.mod);
sources = toJSON (filterAttrs (n: _: n != defaultPackage) sources);
passthru = {
inherit sources;
};
passAsFile = [ "json" "sources" ];
}
(
''
mkdir vendor
export GOCACHE=$TMPDIR/go-cache
export GOPATH="$TMPDIR/go"
${internal.symlink}
${concatStringsSep "\n" localReplaceCommands}
mv vendor $out
''
);
# Select Go attribute based on version specified in go.mod
selectGo = attrs: goMod: attrs.go or (if goMod == null then pkgs.go else
(
let
goVersion = goMod.go;
goAttr = "go_" + (replaceStrings [ "." ] [ "_" ] goVersion);
in
(
if hasAttr goAttr pkgs then pkgs.${goAttr}
else trace "go.mod specified Go version ${goVersion} but doesn't exist. Falling back to ${pkgs.go.version}." pkgs.go
)
));
# Strip the rubbish that Go adds to versions, and fall back to a version based on the date if it's a placeholder value
stripVersion = version:
let
parts = elemAt (split "(\\+|-)" (removePrefix "v" version));
v = parts 0;
d = parts 2;
in
if v != "0.0.0" then v else "unstable-" + (concatStringsSep "-" [
(substring 0 4 d)
(substring 4 2 d)
(substring 6 2 d)
]);
mkGoEnv =
{ pwd
}@attrs:
let
goMod = parseGoMod (readFile "${toString pwd}/go.mod");
modulesStruct = fromTOML (readFile "${toString pwd}/gomod2nix.toml");
go = selectGo attrs goMod;
vendorEnv = mkVendorEnv {
inherit go modulesStruct pwd goMod;
};
in
stdenv.mkDerivation (removeAttrs attrs [ "pwd" ] // {
name = "${baseNameOf goMod.module}-env";
dontUnpack = true;
dontConfigure = true;
dontInstall = true;
propagatedNativeBuildInputs = [ go ];
GO_NO_VENDOR_CHECKS = "1";
GO111MODULE = "on";
GOFLAGS = "-mod=vendor";
preferLocalBuild = true;
buildPhase = ''
mkdir $out
export GOCACHE=$TMPDIR/go-cache
export GOPATH="$out"
export GOSUMDB=off
export GOPROXY=off
'' + optionalString (pathExists (pwd + "/tools.go")) ''
mkdir source
cp ${pwd + "/go.mod"} source/go.mod
cp ${pwd + "/go.sum"} source/go.sum
cp ${pwd + "/tools.go"} source/tools.go
cd source
ln -s ${vendorEnv} vendor
${internal.install}
'';
});
buildGoApplication =
{ modules ? pwd + "/gomod2nix.toml"
, src ? pwd
, pwd ? null
, nativeBuildInputs ? [ ]
, allowGoReference ? false
, meta ? { }
, passthru ? { }
, tags ? [ ]
# needed for buildFlags{,Array} warning
, buildFlags ? ""
, buildFlagsArray ? ""
, ...
}@attrs:
let
modulesStruct = fromTOML (readFile modules);
goModPath = "${toString pwd}/go.mod";
goMod =
if pwd != null && pathExists goModPath
then parseGoMod (readFile goModPath)
else null;
go = selectGo attrs goMod;
removeReferences = [ ] ++ optional (!allowGoReference) go;
defaultPackage = modulesStruct.goPackagePath or "";
vendorEnv = mkVendorEnv {
inherit go modulesStruct defaultPackage goMod pwd;
};
in
warnIf (buildFlags != "" || buildFlagsArray != "")
"Use the `ldflags` and/or `tags` attributes instead of `buildFlags`/`buildFlagsArray`"
stdenv.mkDerivation
(optionalAttrs (defaultPackage != "")
{
pname = attrs.pname or baseNameOf defaultPackage;
version = stripVersion (modulesStruct.mod.${defaultPackage}).version;
src = vendorEnv.passthru.sources.${defaultPackage};
} // optionalAttrs (hasAttr "subPackages" modulesStruct) {
subPackages = modulesStruct.subPackages;
} // attrs // {
nativeBuildInputs = [ removeReferencesTo go ] ++ nativeBuildInputs;
inherit (go) GOOS GOARCH;
GO_NO_VENDOR_CHECKS = "1";
GO111MODULE = "on";
GOFLAGS = "-mod=vendor";
configurePhase = attrs.configurePhase or ''
runHook preConfigure
export GOCACHE=$TMPDIR/go-cache
export GOPATH="$TMPDIR/go"
export GOSUMDB=off
export GOPROXY=off
cd "$modRoot"
if [ -n "${vendorEnv}" ]; then
rm -rf vendor
ln -s ${vendorEnv} vendor
fi
runHook postConfigure
'';
buildPhase = attrs.buildPhase or ''
runHook preBuild
exclude='\(/_\|examples\|Godeps\|testdata'
if [[ -n "$excludedPackages" ]]; then
IFS=' ' read -r -a excludedArr <<<$excludedPackages
printf -v excludedAlternates '%s\\|' "''${excludedArr[@]}"
excludedAlternates=''${excludedAlternates%\\|} # drop final \| added by printf
exclude+='\|'"$excludedAlternates"
fi
exclude+='\)'
buildGoDir() {
local d; local cmd;
cmd="$1"
d="$2"
. $TMPDIR/buildFlagsArray
local OUT
if ! OUT="$(go $cmd $buildFlags "''${buildFlagsArray[@]}" ''${tags:+-tags=${concatStringsSep "," tags}} ''${ldflags:+-ldflags="$ldflags"} -v -p $NIX_BUILD_CORES $d 2>&1)"; then
if echo "$OUT" | grep -qE 'imports .*?: no Go files in'; then
echo "$OUT" >&2
return 1
fi
if ! echo "$OUT" | grep -qE '(no( buildable| non-test)?|build constraints exclude all) Go (source )?files'; then
echo "$OUT" >&2
return 1
fi
fi
if [ -n "$OUT" ]; then
echo "$OUT" >&2
fi
return 0
}
getGoDirs() {
local type;
type="$1"
if [ -n "$subPackages" ]; then
echo "$subPackages" | sed "s,\(^\| \),\1./,g"
else
find . -type f -name \*$type.go -exec dirname {} \; | grep -v "/vendor/" | sort --unique | grep -v "$exclude"
fi
}
if (( "''${NIX_DEBUG:-0}" >= 1 )); then
buildFlagsArray+=(-x)
fi
if [ ''${#buildFlagsArray[@]} -ne 0 ]; then
declare -p buildFlagsArray > $TMPDIR/buildFlagsArray
else
touch $TMPDIR/buildFlagsArray
fi
if [ -z "$enableParallelBuilding" ]; then
export NIX_BUILD_CORES=1
fi
for pkg in $(getGoDirs ""); do
echo "Building subPackage $pkg"
buildGoDir install "$pkg"
done
'' + optionalString (stdenv.hostPlatform != stdenv.buildPlatform) ''
# normalize cross-compiled builds w.r.t. native builds
(
dir=$GOPATH/bin/${go.GOOS}_${go.GOARCH}
if [[ -n "$(shopt -s nullglob; echo $dir/*)" ]]; then
mv $dir/* $dir/..
fi
if [[ -d $dir ]]; then
rmdir $dir
fi
)
'' + ''
runHook postBuild
'';
doCheck = attrs.doCheck or true;
checkPhase = attrs.checkPhase or ''
runHook preCheck
for pkg in $(getGoDirs test); do
buildGoDir test $checkFlags "$pkg"
done
runHook postCheck
'';
installPhase = attrs.installPhase or ''
runHook preInstall
mkdir -p $out
dir="$GOPATH/bin"
[ -e "$dir" ] && cp -r $dir $out
runHook postInstall
'';
preFixup = (attrs.preFixup or "") + ''
find $out/{bin,libexec,lib} -type f 2>/dev/null | xargs -r ${removeExpr removeReferences} || true
'';
strictDeps = true;
disallowedReferences = optional (!allowGoReference) go;
passthru = { inherit go vendorEnv; } // passthru;
meta = { platforms = go.meta.platforms or platforms.all; } // meta;
});
in
{
inherit buildGoApplication mkGoEnv;
}

13
nix/builder/fetch.sh Normal file
View file

@ -0,0 +1,13 @@
source $stdenv/setup
export HOME=$(mktemp -d)
# Call once first outside of subshell for better error reporting
go mod download "$goPackagePath@$version"
dir=$(go mod download --json "$goPackagePath@$version" | jq -r .Dir)
chmod -R +w $dir
find $dir -iname ".ds_store" | xargs -r rm -rf
cp -r $dir $out

View file

@ -0,0 +1,57 @@
package main
import (
"fmt"
"go/parser"
"go/token"
"io"
"os"
"os/exec"
"strconv"
)
const filename = "tools.go"
func main() {
fset := token.NewFileSet()
var src []byte
{
f, err := os.Open(filename)
if err != nil {
panic(err)
}
src, err = io.ReadAll(f)
if err != nil {
panic(err)
}
}
f, err := parser.ParseFile(fset, filename, src, parser.ImportsOnly)
if err != nil {
fmt.Println(err)
return
}
for _, s := range f.Imports {
path, err := strconv.Unquote(s.Path.Value)
if err != nil {
panic(err)
}
cmd := exec.Command("go", "install", path)
fmt.Printf("Executing '%s'\n", cmd)
err = cmd.Start()
if err != nil {
panic(err)
}
err = cmd.Wait()
if err != nil {
panic(err)
}
}
}

141
nix/builder/parser.nix Normal file
View file

@ -0,0 +1,141 @@
# Parse go.mod in Nix
# Returns a Nix structure with the contents of the go.mod passed in
# in normalised form.
let
inherit (builtins) elemAt mapAttrs split foldl' match filter typeOf hasAttr length;
# Strip lines with comments & other junk
stripStr = s: elemAt (split "^ *" (elemAt (split " *$" s) 0)) 2;
stripLines = initialLines: foldl' (acc: f: f acc) initialLines [
# Strip comments
(lines: map
(l: stripStr (elemAt (splitString "//" l) 0))
lines)
# Strip leading tabs characters
(lines: map (l: elemAt (match "(\t)?(.*)" l) 1) lines)
# Filter empty lines
(filter (l: l != ""))
];
# Parse lines into a structure
parseLines = lines: (foldl'
(acc: l:
let
m = match "([^ )]*) *(.*)" l;
directive = elemAt m 0;
rest = elemAt m 1;
# Maintain parser state (inside parens or not)
inDirective =
if rest == "(" then directive
else if rest == ")" then null
else acc.inDirective
;
in
{
data = (acc.data // (
if directive == "" && rest == ")" then { }
else if inDirective != null && rest == "(" && ! hasAttr inDirective acc.data then {
${inDirective} = { };
}
else if rest == "(" || rest == ")" then { }
else if inDirective != null then {
${inDirective} = acc.data.${inDirective} // { ${directive} = rest; };
} else if directive == "replace" then
(
let
segments = split " => " rest;
getSegment = elemAt segments;
in
assert length segments == 3; {
replace = acc.data.replace // {
${getSegment 0} = "=> ${getSegment 2}";
};
}
)
else {
${directive} = rest;
}
)
);
inherit inDirective;
})
{
inDirective = null;
data = {
require = { };
replace = { };
exclude = { };
};
}
lines
).data;
normaliseDirectives = data: (
let
normaliseString = s:
let
m = builtins.match "([^ ]+) (.+)" s;
in
{
${elemAt m 0} = elemAt m 1;
};
require = data.require or { };
replace = data.replace or { };
exclude = data.exclude or { };
in
data // {
require =
if typeOf require == "string" then normaliseString require
else require;
replace =
if typeOf replace == "string" then normaliseString replace
else replace;
}
);
parseVersion = ver:
let
m = elemAt (match "([^-]+)-?([^-]*)-?([^-]*)" ver);
v = elemAt (match "([^+]+)\\+?(.*)" (m 0));
in
{
version = v 0;
versionSuffix = v 1;
date = m 1;
rev = m 2;
};
parseReplace = data: (
data // {
replace =
mapAttrs
(_: v:
let
m = match "=> ([^ ]+) (.+)" v;
m2 = match "=> (.*+)" v;
in
if m != null then {
goPackagePath = elemAt m 0;
version = elemAt m 1;
} else {
path = elemAt m2 0;
})
data.replace;
}
);
splitString = sep: s: filter (t: t != [ ]) (split sep s);
in
contents:
foldl' (acc: f: f acc) (splitString "\n" contents) [
stripLines
parseLines
normaliseDirectives
parseReplace
]

View file

@ -0,0 +1,110 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
)
type Package struct {
GoPackagePath string `json:"-"`
Version string `json:"version"`
Hash string `json:"hash"`
ReplacedPath string `json:"replaced,omitempty"`
}
// type Output struct {
// SchemaVersion int `json:"schema"`
// Mod map[string]*Package `json:"mod"`
// }
func main() {
// var output Output
sources := make(map[string]string)
pkgs := make(map[string]*Package)
{
b, err := ioutil.ReadFile(os.Getenv("sourcesPath"))
if err != nil {
panic(err)
}
err = json.Unmarshal(b, &sources)
if err != nil {
panic(err)
}
}
{
b, err := ioutil.ReadFile(os.Getenv("jsonPath"))
if err != nil {
panic(err)
}
err = json.Unmarshal(b, &pkgs)
if err != nil {
panic(err)
}
}
keys := make([]string, 0, len(pkgs))
for key := range pkgs {
keys = append(keys, key)
}
sort.Strings(keys)
// Iterate, in reverse order
for i := len(keys) - 1; i >= 0; i-- {
key := keys[i]
src := sources[key]
paths := []string{key}
for _, path := range paths {
vendorDir := filepath.Join("vendor", filepath.Dir(path))
if err := os.MkdirAll(vendorDir, 0755); err != nil {
panic(err)
}
if _, err := os.Stat(filepath.Join("vendor", path)); err == nil {
files, err := ioutil.ReadDir(src)
if err != nil {
panic(err)
}
for _, f := range files {
innerSrc := filepath.Join(src, f.Name())
dst := filepath.Join("vendor", path, f.Name())
if err := os.Symlink(innerSrc, dst); err != nil {
// assume it's an existing directory, try to link the directory content instead.
// TODO should we do this recursively
files, err := ioutil.ReadDir(innerSrc)
if err != nil {
panic(err)
}
for _, f := range files {
if err := os.Symlink(filepath.Join(innerSrc, f.Name()), filepath.Join(dst, f.Name())); err != nil {
fmt.Println("ignore symlink error", filepath.Join(innerSrc, f.Name()), filepath.Join(dst, f.Name()))
}
}
}
}
continue
}
// If the file doesn't already exist, just create a simple symlink
err := os.Symlink(src, filepath.Join("vendor", path))
if err != nil {
panic(err)
}
}
}
}