diff --git a/README.md b/README.md index 2442040..2fcc22d 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,17 @@ Bottin requires go 1.13 or later. To build Bottin, clone this repository outside of your `$GOPATH`. Then, run `make` in the root of the repo. +## Releasing + +```bash +nix-build -A bin +nix-build -A docker +``` + +```bash +docker load < $(nix-build -A docker) +docker push ??? +``` ## Server initialization diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..cc73d86 --- /dev/null +++ b/default.nix @@ -0,0 +1,48 @@ +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 rec { + bin = pkgs.gomod.buildGoApplication { + pname = "bottin-bin"; + version = "0.1.0"; + src = builtins.filterSource + (path: type: (builtins.match ".*/test/.*\\.(go|sum|mod)" path) == null) + ./.; + modules = ./gomod2nix.toml; + + CGO_ENABLED=0; + + meta = with pkgs.lib; { + description = "Interface web pour gérer le LDAP: changer son mot de passe, ses infos de profil, inviter des gens, administration"; + homepage = "https://git.deuxfleurs.fr/Deuxfleurs/guichet"; + license = licenses.gpl3Plus; + platforms = platforms.linux; + }; + }; + pkg = pkgs.stdenv.mkDerivation { + pname = "bottin"; + version = "0.1.0"; + unpackPhase = "true"; + + installPhase = '' + mkdir -p $out/ + cp ${bin}/bin/bottin $out/bottin + ''; + }; + docker = pkgs.dockerTools.buildImage { + name = "dxflrs/bottin"; + config = { + Cmd = [ "${pkg}/bottin" ]; + }; + }; +} diff --git a/gomod2nix.toml b/gomod2nix.toml new file mode 100644 index 0000000..62a57bf --- /dev/null +++ b/gomod2nix.toml @@ -0,0 +1,153 @@ +schema = 3 + +[mod] + [mod."github.com/armon/circbuf"] + version = "v0.0.0-20150827004946-bbbad097214e" + hash = "sha256-klQjllsJZqZ2KPNx1mZT9XP+UAJkuBhmTnZdNlAflEM=" + [mod."github.com/armon/go-metrics"] + version = "v0.0.0-20180917152333-f0300d1749da" + hash = "sha256-+zqX1hlJgc+IrXRzBQDMhR8GYQdc0Oj6PiIDfctgh44=" + [mod."github.com/armon/go-radix"] + version = "v0.0.0-20180808171621-7fddfc383310" + hash = "sha256-ZHU4pyBqHHRuQJuYr2K+LqeAnLX9peX07cmSYK+GDHk=" + [mod."github.com/bgentry/speakeasy"] + version = "v0.1.0" + hash = "sha256-Gt1vj6CFovLnO6wX5u2O4UfecY9V2J9WGw1ez4HMrgk=" + [mod."github.com/davecgh/go-spew"] + version = "v1.1.1" + hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=" + [mod."github.com/fatih/color"] + version = "v1.7.0" + hash = "sha256-4In7ef7it7d+6oGUJ3pkD0V+lsL40hVtYdy2KD2ovn0=" + [mod."github.com/google/btree"] + version = "v0.0.0-20180813153112-4030bb1f1f0c" + hash = "sha256-5gr0RMnlvrzCke3kwpkf92WvW3x5nnKZesoulyoYRC0=" + [mod."github.com/google/uuid"] + version = "v1.1.1" + hash = "sha256-66PXC/RCPUyhS9PhkIPQFR3tbM2zZYDNPGXN7JJj3UE=" + [mod."github.com/hashicorp/consul/api"] + version = "v1.3.0" + hash = "sha256-fMORNFAWK2GkRbBl/KzNHrvtCfwz/7P66HzDaE4GnuE=" + [mod."github.com/hashicorp/consul/sdk"] + version = "v0.3.0" + hash = "sha256-lF47JPGfmeGjpuQw9VSNs2Mi+G7FQLKrrHteX3iXfpU=" + [mod."github.com/hashicorp/errwrap"] + version = "v1.0.0" + hash = "sha256-LGSLrefkABG1kH1i+GUWiD2/ggJxiZEJ+D2YNbhZjmo=" + [mod."github.com/hashicorp/go-cleanhttp"] + version = "v0.5.1" + hash = "sha256-c54zcHr9THj3MQk7hrDQcpjOcQi1MvXZ4Kpin6EbfR4=" + [mod."github.com/hashicorp/go-immutable-radix"] + version = "v1.0.0" + hash = "sha256-JmNxdGaJG63Ty/sVnPjqvTyA4/k5wkzZ/QvpMK2uduw=" + [mod."github.com/hashicorp/go-msgpack"] + version = "v0.5.3" + hash = "sha256-2OUYjD/Jt12TFBrtH0wRqg+lzRljDxSIhk2CqBLUczo=" + [mod."github.com/hashicorp/go-multierror"] + version = "v1.0.0" + hash = "sha256-iXzjerl96o7QDiSwQjbak8R/t+YzZeoUqm59TCmy3gI=" + [mod."github.com/hashicorp/go-rootcerts"] + version = "v1.0.0" + hash = "sha256-4NZJAT5/vocyto+dv6FmW4kFiYldmNvewowsYK/LiTI=" + [mod."github.com/hashicorp/go-sockaddr"] + version = "v1.0.0" + hash = "sha256-orG+SHVsp5lgNRCErmhMLABVFQ3ZWfYIJ/4LTFzlvao=" + [mod."github.com/hashicorp/go-syslog"] + version = "v1.0.0" + hash = "sha256-YRuq6oPMwAFVY7mvwpMDvZqGwNnb5CjBYyKI/x5mbCc=" + [mod."github.com/hashicorp/go-uuid"] + version = "v1.0.1" + hash = "sha256-s1wIvBu37z4U3qK9sdUR1CtbD39N6RwfX4HgDCpCa0s=" + [mod."github.com/hashicorp/go.net"] + version = "v0.0.1" + hash = "sha256-JKal3E+wPO+Hk838opKV4HHKB4O72Xy+77ncXlLkWRk=" + [mod."github.com/hashicorp/golang-lru"] + version = "v0.5.0" + hash = "sha256-Lo0UyOZQ89iK0Ui3Hfk5VkWqxEzLw6Lclq4EM8VlYoo=" + [mod."github.com/hashicorp/logutils"] + version = "v1.0.0" + hash = "sha256-e8t8Dm8sp/PzKClN1TOmFcrTAWNh4mZHSW7cAjVx3Bw=" + [mod."github.com/hashicorp/mdns"] + version = "v1.0.0" + hash = "sha256-ravx4tklQG43OEjPiJn68iJM9ODZ6hgU0idFCEOiJGM=" + [mod."github.com/hashicorp/memberlist"] + version = "v0.1.3" + hash = "sha256-IsxqevYulPt+2VGtlq068Jyq1YfIk4Ohh9TgakIGxnc=" + [mod."github.com/hashicorp/serf"] + version = "v0.8.2" + hash = "sha256-diRxWOouFLTO75f2E9NlrKgie/qsT+gOOrrbf4tACHw=" + [mod."github.com/jsimonetti/pwscheme"] + version = "v0.0.0-20220125093853-4d9895f5db73" + hash = "sha256-YF3RKU/4CWvLPgGzUd7Hk/2+41OUFuRWZgzQuqcsKg0=" + [mod."github.com/konsorten/go-windows-terminal-sequences"] + version = "v1.0.1" + hash = "sha256-Nwp+Cza9dIu3ogVGip6wyOjWwwaq+2hU3eYIe4R7kNE=" + [mod."github.com/mattn/go-colorable"] + version = "v0.0.9" + hash = "sha256-fVPF8VxbdggLAZnaexMl6Id1WjXKImzVySxKfa+ukts=" + [mod."github.com/mattn/go-isatty"] + version = "v0.0.3" + hash = "sha256-9ogEEd8/kl/dImldzdBmTFdepvB0dVXEzN4o8bEqhBs=" + [mod."github.com/miekg/dns"] + version = "v1.0.14" + hash = "sha256-OeijUgBaEmDapclTxfvjIqrjh4qZu3+DQpHelGGI4aA=" + [mod."github.com/mitchellh/cli"] + version = "v1.0.0" + hash = "sha256-4nG7AhRcjTRCwUCdnaNaFrAKDxEEoiihaCA4lk+uM8U=" + [mod."github.com/mitchellh/go-homedir"] + version = "v1.0.0" + hash = "sha256-eGmBNNTuDSMwicjTNgB54IEJuK1tgOLDJ3NHTpQCHzg=" + [mod."github.com/mitchellh/go-testing-interface"] + version = "v1.0.0" + hash = "sha256-/Dpv/4i5xuK8hDH+q8YTdF6Jg6NNtfO4Wqig2JCWgrY=" + [mod."github.com/mitchellh/gox"] + version = "v0.4.0" + hash = "sha256-GV3LYxzJt8YVbnSac2orlj2QR3MX/YIDrLkSkPhsjuA=" + [mod."github.com/mitchellh/iochan"] + version = "v1.0.0" + hash = "sha256-b5Tp7cw/e8mL++IjsebbmKWXtb9Hrzu4Fc6M4tZKFhU=" + [mod."github.com/mitchellh/mapstructure"] + version = "v1.1.2" + hash = "sha256-OU9HZYHtl0qaqMFd84w7snkkRuRY6UMSsfCnL5HYdw0=" + [mod."github.com/pascaldekloe/goe"] + version = "v0.0.0-20180627143212-57f6aae5913c" + hash = "sha256-2KUjqrEC/BwkTZRxImazcI/C3H7QmXfNrlt8slwdDbc=" + [mod."github.com/pkg/errors"] + version = "v0.8.1" + hash = "sha256-oe3iddfoLRwpC3ki5fifHf2ZFprtg99iNak50shiuDw=" + [mod."github.com/pmezard/go-difflib"] + version = "v1.0.0" + hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA=" + [mod."github.com/posener/complete"] + version = "v1.1.1" + hash = "sha256-heyPMSBzVlx7ZKgTyzl/xmUfZw3EZCcvGFGrRMIbIr8=" + [mod."github.com/ryanuber/columnize"] + version = "v0.0.0-20160712163229-9b3edd62028f" + hash = "sha256-RLUQcU6Z03upKe08v6rjn9/tkyrQsgmpdEmBtWaLQfk=" + [mod."github.com/sean-/seed"] + version = "v0.0.0-20170313163322-e2103e2c3529" + hash = "sha256-RQQTjvf8Y91jP5FGOyEnGMFw7zCrcSnUU4eH2CXKkT4=" + [mod."github.com/sirupsen/logrus"] + version = "v1.4.2" + hash = "sha256-3QzWUsapCmg3F7JqUuINT3/UG097uzLff6iCcCgQ43o=" + [mod."github.com/stretchr/objx"] + version = "v0.1.1" + hash = "sha256-HdGVZCuy7VprC5W9UxGbDmXqsKADMjpEDht7ilGVLco=" + [mod."github.com/stretchr/testify"] + version = "v1.3.0" + hash = "sha256-+mSebBNccNcxbY462iKTNTWmd5ZuUkUqFebccn3EtIA=" + [mod."golang.org/x/crypto"] + version = "v0.0.0-20200604202706-70a84ac30bf9" + hash = "sha256-I5ov2lV6ubB3H3pn6DFN1VPLyxeLfPUaOx2PJ9K1KH8=" + [mod."golang.org/x/net"] + version = "v0.0.0-20190404232315-eb5bcb51f2a3" + hash = "sha256-kwP+BDfPsZEeeceyg4H5LgdTDT8O8YWbkpCkpyuPJJo=" + [mod."golang.org/x/sync"] + version = "v0.0.0-20181221193216-37e7f081c4d4" + hash = "sha256-FUpv9bmGLDEjCxXdvR0CRDqOKRY0xrHRPzOsyQyvYK0=" + [mod."golang.org/x/sys"] + version = "v0.0.0-20190422165155-953cdadca894" + hash = "sha256-YkQjSZaiVFxrmXYSKmqrGeIO3RZ95fc3dWyCaR+SosY=" + [mod."golang.org/x/text"] + version = "v0.3.0" + hash = "sha256-0FFbaxF1ZuAQF3sCcA85e8MO6prFeHint36inija4NY=" diff --git a/nix/builder/default.nix b/nix/builder/default.nix new file mode 100644 index 0000000..8b8ae19 --- /dev/null +++ b/nix/builder/default.nix @@ -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; +} diff --git a/nix/builder/fetch.sh b/nix/builder/fetch.sh new file mode 100644 index 0000000..8c6bdb3 --- /dev/null +++ b/nix/builder/fetch.sh @@ -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 diff --git a/nix/builder/install/install.go b/nix/builder/install/install.go new file mode 100644 index 0000000..4f770b0 --- /dev/null +++ b/nix/builder/install/install.go @@ -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) + } + } +} diff --git a/nix/builder/parser.nix b/nix/builder/parser.nix new file mode 100644 index 0000000..eb6f75e --- /dev/null +++ b/nix/builder/parser.nix @@ -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 +] diff --git a/nix/builder/symlink/symlink.go b/nix/builder/symlink/symlink.go new file mode 100644 index 0000000..3dbb383 --- /dev/null +++ b/nix/builder/symlink/symlink.go @@ -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) + } + + } + } + +}