From 1912e21e41e882e2db8fca8aacfe7f64430ab2ce Mon Sep 17 00:00:00 2001
From: Gabriel Arazas <foodogsquared@foodogsquared.one>
Date: Thu, 29 Aug 2024 17:41:42 +0800
Subject: [PATCH] lib/builders: add Hugo site builder

Good timing with the restructure, too. :D
---
 lib/builders/default.nix                 |   1 +
 lib/builders/hugo-build-site/default.nix | 310 +++++++++++++++++++++++
 2 files changed, 311 insertions(+)
 create mode 100644 lib/builders/hugo-build-site/default.nix

diff --git a/lib/builders/default.nix b/lib/builders/default.nix
index 77cf3822..0ece6780 100644
--- a/lib/builders/default.nix
+++ b/lib/builders/default.nix
@@ -4,4 +4,5 @@
   makeXDGMimeAssociationList = pkgs.callPackage ./xdg/make-association-list.nix { };
   makeXDGPortalConfiguration = pkgs.callPackage ./xdg/make-portal-config.nix { };
   makeXDGDesktopEntry = pkgs.callPackage ./xdg/make-desktop-entry.nix { };
+  buildHugoSite = pkgs.callPackage ./hugo-build-site { };
 }
diff --git a/lib/builders/hugo-build-site/default.nix b/lib/builders/hugo-build-site/default.nix
new file mode 100644
index 00000000..696bdb57
--- /dev/null
+++ b/lib/builders/hugo-build-site/default.nix
@@ -0,0 +1,310 @@
+{
+  hugo,
+  go,
+  cacert,
+  git,
+  lib,
+  stdenv,
+}:
+
+# A modified Go builder for generating a website with Hugo. Since it relies on
+# Hugo modules (which is basically wrapper around Go modules), this can be used
+# for Hugo projects that heavily uses them.
+#
+# Take note, this doesn't work for Hugo projects with remote resources
+# right in the content since Hugo allows network access when generating
+# the website.
+{
+  name ? "${args'.pname}-${args'.version}",
+
+  nativeBuildInputs ? [ ],
+  passthru ? { },
+
+  # A function to override the goModules derivation
+  overrideModAttrs ? (_oldAttrs: { }),
+
+  # path to go.mod and go.sum directory
+  modRoot ? "./",
+
+  # vendorHash is the SRI hash of the vendored dependencies
+  #
+  # if vendorHash is null, then we won't fetch any dependencies and
+  # rely on the vendor folder within the source.
+  vendorHash ? throw (
+    if args' ? vendorSha256 then
+      "buildGoModule: Expect vendorHash instead of vendorSha256"
+    else
+      "buildGoModule: vendorHash is missing"
+  ),
+
+  # Whether to delete the vendor folder supplied with the source.
+  deleteVendor ? false,
+
+  # Whether to fetch (go mod download) and proxy the vendor directory.
+  # This is useful if your code depends on c code and go mod tidy does not
+  # include the needed sources to build or if any dependency has case-insensitive
+  # conflicts which will produce platform dependant `vendorHash` checksums.
+  proxyVendor ? false,
+
+  # We want parallel builds by default
+  enableParallelBuilding ? true,
+
+  # Do not enable this without good reason
+  # IE: programs coupled with the compiler
+  allowGoReference ? false,
+
+  CGO_ENABLED ? go.CGO_ENABLED,
+
+  meta ? { },
+
+  ldflags ? [ ],
+
+  GOFLAGS ? [ ],
+
+  ...
+}@args':
+
+let
+  args = removeAttrs args' [
+    "overrideModAttrs"
+    "vendorSha256"
+    "vendorHash"
+  ];
+
+  GO111MODULE = "on";
+  GOTOOLCHAIN = "local";
+
+  hugoModules =
+    if (vendorHash == null) then
+      ""
+    else
+      (stdenv.mkDerivation {
+        name = "${name}-hugo-modules";
+
+        nativeBuildInputs = (args.nativeBuildInputs or [ ]) ++ [
+          hugo
+          go
+          git
+          cacert
+        ];
+
+        inherit (args) src;
+        inherit (go) GOOS GOARCH;
+        inherit GO111MODULE GOTOOLCHAIN;
+
+        # The following inheritence behavior is not trivial to expect, and some may
+        # argue it's not ideal. Changing it may break vendor hashes in Nixpkgs and
+        # out in the wild. In anycase, it's documented in:
+        # doc/languages-frameworks/go.section.md
+        prePatch = args.prePatch or "";
+        patches = args.patches or [ ];
+        patchFlags = args.patchFlags or [ ];
+        postPatch = args.postPatch or "";
+        preBuild = args.preBuild or "";
+        postBuild = args.modPostBuild or "";
+        sourceRoot = args.sourceRoot or "";
+        setSourceRoot = args.setSourceRoot or "";
+        env = args.env or { };
+
+        impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
+          "GIT_PROXY_COMMAND"
+          "SOCKS_SERVER"
+          "GOPROXY"
+        ];
+
+        configurePhase =
+          args.modConfigurePhase or ''
+            runHook preConfigure
+            export GOCACHE=$TMPDIR/go-cache
+            export GOPATH="$TMPDIR/go"
+            cd "${modRoot}"
+            runHook postConfigure
+          '';
+
+        buildPhase =
+          args.modBuildPhase or (
+            ''
+              runHook preBuild
+            ''
+            + lib.optionalString deleteVendor ''
+              if [ ! -d _vendor ]; then
+                echo "_vendor folder does not exist, 'deleteVendor' is not needed"
+                exit 10
+              else
+                rm -rf _vendor
+              fi
+            ''
+            + ''
+              if [ -d _vendor ]; then
+                echo "_vendor folder exists, please set 'vendorHash = null;' in your expression"
+                exit 10
+              fi
+
+              ${
+                if proxyVendor then
+                  ''
+                    mkdir -p "''${GOPATH}/pkg/mod/cache/download"
+                    hugo mod vendor
+                  ''
+                else
+                  ''
+                    if (( "''${NIX_DEBUG:-0}" >= 1 )); then
+                      hugoModVendorFlags+=(-v)
+                    fi
+                    hugo mod vendor "''${hugoModVendorFlags[@]}"
+                  ''
+              }
+
+              mkdir -p _vendor
+
+              runHook postBuild
+            ''
+          );
+
+        installPhase =
+          args.modInstallPhase or ''
+            runHook preInstall
+
+            ${
+              if proxyVendor then
+                ''
+                  rm -rf "''${GOPATH}/pkg/mod/cache/download/sumdb"
+                  cp -r --reflink=auto "''${GOPATH}/pkg/mod/cache/download" $out
+                ''
+              else
+                ''
+                  cp -r --reflink=auto _vendor $out
+                ''
+            }
+
+            if ! [ "$(ls -A $out)" ]; then
+              echo "_vendor folder is empty, please set 'vendorHash = null;' in your expression"
+              exit 10
+            fi
+
+            runHook postInstall
+          '';
+
+        dontFixup = true;
+
+        outputHashMode = "recursive";
+        outputHash = vendorHash;
+        # Handle empty vendorHash; avoid
+        # error: empty hash requires explicit hash algorithm
+        outputHashAlgo = if vendorHash == "" then "sha256" else null;
+      }).overrideAttrs
+        overrideModAttrs;
+
+  package = stdenv.mkDerivation (
+    args
+    // {
+      nativeBuildInputs = [
+        hugo
+        git
+        go
+      ] ++ nativeBuildInputs;
+
+      inherit (go) GOOS GOARCH;
+
+      GOFLAGS =
+        GOFLAGS
+        ++
+          lib.warnIf (lib.any (lib.hasPrefix "-mod=") GOFLAGS)
+            "use `proxyVendor` to control Go module/vendor behavior instead of setting `-mod=` in GOFLAGS"
+            (lib.optional (!proxyVendor) "-mod=vendor")
+        ++
+          lib.warnIf (builtins.elem "-trimpath" GOFLAGS)
+            "`-trimpath` is added by default to GOFLAGS by buildGoModule when allowGoReference isn't set to true"
+            (lib.optional (!allowGoReference) "-trimpath");
+      inherit
+        CGO_ENABLED
+        enableParallelBuilding
+        GO111MODULE
+        GOTOOLCHAIN
+        ;
+
+      # If not set to an explicit value, set the buildid empty for reproducibility.
+      ldflags = ldflags ++ lib.optional (!lib.any (lib.hasPrefix "-buildid=") ldflags) "-buildid=";
+
+      configurePhase =
+        args.configurePhase or (
+          ''
+            runHook preConfigure
+
+            export GOCACHE=$TMPDIR/go-cache
+            export GOPATH="$TMPDIR/go"
+            export GOPROXY=off
+            export GOSUMDB=off
+            cd "$modRoot"
+          ''
+          + lib.optionalString (vendorHash != null) ''
+            ${
+              if proxyVendor then
+                ''
+                  export GOPROXY=file://${hugoModules}
+                ''
+              else
+                ''
+                  rm -rf _vendor
+                  cp -r --reflink=auto ${hugoModules} _vendor
+                ''
+            }
+          ''
+          + ''
+
+            # currently pie is only enabled by default in pkgsMusl
+            # this will respect the `hardening{Disable,Enable}` flags if set
+            if [[ $NIX_HARDENING_ENABLE =~ "pie" ]]; then
+              export GOFLAGS="-buildmode=pie $GOFLAGS"
+            fi
+
+            runHook postConfigure
+          ''
+        );
+
+      buildPhase =
+        args.buildPhase or ''
+          runHook preBuild
+          hugo "''${buildFlags[@]}" --destination public
+          runHook postBuild
+        '';
+
+      doCheck = args.doCheck or true;
+      checkPhase =
+        args.checkPhase or ''
+          runHook preCheck
+
+          runHook postCheck
+        '';
+
+      installPhase =
+        args.installPhase or ''
+          runHook preInstall
+
+          mkdir -p $out
+          cp -r public/* $out
+
+          runHook postInstall
+        '';
+
+      strictDeps = true;
+
+      disallowedReferences = lib.optional (!allowGoReference) go;
+
+      passthru = passthru // {
+        inherit
+          go
+          hugo
+          hugoModules
+          vendorHash
+          ;
+      };
+
+      meta = {
+        # Add default meta information
+        platforms = go.meta.platforms or lib.platforms.all;
+      } // meta;
+    }
+  );
+in
+package