From 433120e47d016c9960dd9c2b1821e97d223a6a39 Mon Sep 17 00:00:00 2001
From: Benedikt Ritter <beneritter@gmail.com>
Date: Wed, 15 Nov 2023 19:56:39 +0100
Subject: [PATCH] gradle: add module

Introduces a new program called gradle for managing files stored in
the home directory by the [Gradle Build Tool](https://gradle.org).
Gradle uses the $HOME/.gradle folder for all it's configuration.

Features of the new program module are:

- Automatically setting programs.java.enable = true to make a Java
  installation available for running Gradle.
- Specifying an alternate Gradle home directory
- Setting of abitrary values for gradle.properties stored inside the
  Gradle home directory.
- Defining init scripts that will be linked into the init.d inside
  the Gradle home directory.

Co-authored-by: Olli Helenius <liff@iki.fi>
Co-authored-by: Robert Helgesson <robert@rycee.net>
---
 modules/misc/news.nix                         |   7 ++
 modules/modules.nix                           |   1 +
 modules/programs/gradle.nix                   | 114 ++++++++++++++++++
 tests/default.nix                             |   1 +
 .../gradle/alternate-home-settings.nix        |  26 ++++
 tests/modules/programs/gradle/default.nix     |   7 ++
 .../programs/gradle/empty-settings.nix        |  17 +++
 .../programs/gradle/example-settings.nix      |  42 +++++++
 .../gradle/external-init-script.gradle        |   1 +
 .../programs/gradle/init-scripts-settings.nix |  37 ++++++
 10 files changed, 253 insertions(+)
 create mode 100644 modules/programs/gradle.nix
 create mode 100644 tests/modules/programs/gradle/alternate-home-settings.nix
 create mode 100644 tests/modules/programs/gradle/default.nix
 create mode 100644 tests/modules/programs/gradle/empty-settings.nix
 create mode 100644 tests/modules/programs/gradle/example-settings.nix
 create mode 100644 tests/modules/programs/gradle/external-init-script.gradle
 create mode 100644 tests/modules/programs/gradle/init-scripts-settings.nix

diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index cc8019528..d8b9f4d9f 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -1341,6 +1341,13 @@ in
           A new module is available: 'programs.sapling'.
         '';
       }
+
+      {
+        time = "2023-12-20T11:41:10+00:00";
+        message = ''
+          A new module is available: 'programs.gradle'.
+        '';
+      }
     ];
   };
 }
diff --git a/modules/modules.nix b/modules/modules.nix
index f1bee95b9..8f1b98a02 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -99,6 +99,7 @@ let
     ./programs/gnome-terminal.nix
     ./programs/go.nix
     ./programs/gpg.nix
+    ./programs/gradle.nix
     ./programs/granted.nix
     ./programs/havoc.nix
     ./programs/helix.nix
diff --git a/modules/programs/gradle.nix b/modules/programs/gradle.nix
new file mode 100644
index 000000000..cfdf56db5
--- /dev/null
+++ b/modules/programs/gradle.nix
@@ -0,0 +1,114 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.gradle;
+  defaultHomeDirectory = ".gradle";
+  settingsFormat = pkgs.formats.javaProperties { };
+
+  initScript = types.submodule ({ name, config, ... }: {
+    options = {
+      text = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Text of the init script file. if this option is null
+          then `source` must be set.
+        '';
+      };
+
+      source = mkOption {
+        type = types.path;
+        description = ''
+          Path of the init script file. If
+          `text` is non-null then this option will automatically point
+          to a file containing that text.
+        '';
+      };
+    };
+
+    config.source = mkIf (config.text != null) (mkDefault (pkgs.writeTextFile {
+      inherit (config) text;
+      name = hm.strings.storeFileName name;
+    }));
+  });
+in {
+  meta.maintainers = [ maintainers.britter ];
+
+  options.programs.gradle = {
+    enable = mkEnableOption "Gradle Build Tool";
+
+    home = mkOption {
+      type = types.str;
+      default = defaultHomeDirectory;
+      description = ''
+        The Gradle home directory, relative to [](#opt-home.homeDirectory).
+
+        If set, the {env}`GRADLE_USER_HOME` environment variable will be
+        set accordingly. Defaults to {file}`.gradle`.
+      '';
+    };
+
+    package = mkPackageOption pkgs "gradle" { example = "pkgs.gradle_7"; };
+
+    settings = mkOption {
+      type = types.submodule { freeformType = settingsFormat.type; };
+      default = { };
+      example = literalExpression ''
+        {
+          "org.gradle.caching" = true;
+          "org.gradle.parallel" = true;
+          "org.gradle.jvmargs" = "-XX:MaxMetaspaceSize=384m";
+          "org.gradle.home" = pkgs.jdk17;
+        };
+      '';
+      description = ''
+        Key value pairs to write to {file}`gradle.properties` in the Gradle
+        home directory.
+      '';
+    };
+
+    initScripts = mkOption {
+      type = with types; attrsOf initScript;
+      default = { };
+      example = literalExpression ''
+        {
+          "maven-local.gradle".text = '''
+              allProject {
+                repositories {
+                  mavenLocal()
+                }
+              }
+          ''';
+          "another.init.gradle.kts".source = ./another.init.gradle.kts;
+        }
+      '';
+      description = ''
+        Definition of init scripts to link into the Gradle home directory.
+
+        For more information about init scripts, including naming conventions
+        see https://docs.gradle.org/current/userguide/init_scripts.html.
+      '';
+    };
+  };
+
+  config = let gradleHome = "${config.home.homeDirectory}/${cfg.home}";
+  in mkIf cfg.enable {
+    home.packages = [ cfg.package ];
+
+    home.file = mkMerge ([{
+      "${cfg.home}/gradle.properties" = mkIf (cfg.settings != { }) {
+        source = settingsFormat.generate "gradle.properties" cfg.settings;
+      };
+    }]
+      ++ mapAttrsToList (k: v: { "${cfg.home}/init.d/${k}".source = v.source; })
+      cfg.initScripts);
+
+    home.sessionVariables = mkIf (cfg.home != defaultHomeDirectory) {
+      GRADLE_USER_HOME = gradleHome;
+    };
+
+    programs.java.enable = true;
+  };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 7f1684d84..cebcf6c78 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -84,6 +84,7 @@ import nmt {
     ./modules/programs/git
     ./modules/programs/git-cliff
     ./modules/programs/gpg
+    ./modules/programs/gradle
     ./modules/programs/granted
     ./modules/programs/helix
     ./modules/programs/himalaya
diff --git a/tests/modules/programs/gradle/alternate-home-settings.nix b/tests/modules/programs/gradle/alternate-home-settings.nix
new file mode 100644
index 000000000..dc885ab19
--- /dev/null
+++ b/tests/modules/programs/gradle/alternate-home-settings.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  config = {
+    programs.gradle = {
+      enable = true;
+      home = ".gbt";
+      settings = { "org.gradle.caching" = true; };
+      initScripts = { "some-script.gradle".text = "println 'hello world'"; };
+    };
+
+    programs.java.package =
+      pkgs.runCommandLocal "java" { home = ""; } "mkdir $out";
+
+    test.stubs.gradle = { };
+
+    nmt.script = ''
+      assertFileContains home-path/etc/profile.d/hm-session-vars.sh \
+        'export GRADLE_USER_HOME="/home/hm-user/.gbt"'
+      assertFileExists home-files/.gbt/gradle.properties
+      assertFileExists home-files/.gbt/init.d/some-script.gradle
+    '';
+  };
+}
diff --git a/tests/modules/programs/gradle/default.nix b/tests/modules/programs/gradle/default.nix
new file mode 100644
index 000000000..b8a454914
--- /dev/null
+++ b/tests/modules/programs/gradle/default.nix
@@ -0,0 +1,7 @@
+{
+  gradle-empty-settings = ./empty-settings.nix;
+  gradle-example-settings = ./example-settings.nix;
+  gradle-init-scripts-settings = ./init-scripts-settings.nix;
+  gradle-alternate-home-settings = ./alternate-home-settings.nix;
+}
+
diff --git a/tests/modules/programs/gradle/empty-settings.nix b/tests/modules/programs/gradle/empty-settings.nix
new file mode 100644
index 000000000..8ed465751
--- /dev/null
+++ b/tests/modules/programs/gradle/empty-settings.nix
@@ -0,0 +1,17 @@
+{ pkgs, ... }:
+
+{
+  config = {
+    programs.gradle.enable = true;
+
+    programs.java.package =
+      pkgs.runCommandLocal "java" { home = ""; } "mkdir $out";
+
+    test.stubs.gradle = { };
+
+    nmt.script = ''
+      assertPathNotExists home-files/.gradle
+      assertFileNotRegex home-path/etc/profile.d/hm-session-vars.sh 'GRADLE_USER_HOME'
+    '';
+  };
+}
diff --git a/tests/modules/programs/gradle/example-settings.nix b/tests/modules/programs/gradle/example-settings.nix
new file mode 100644
index 000000000..19b8c070a
--- /dev/null
+++ b/tests/modules/programs/gradle/example-settings.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  config = {
+    programs.gradle = {
+      enable = true;
+      settings = {
+        "org.gradle.caching" = true;
+        "org.gradle.parallel" = true;
+        "org.gradle.java.home" = pkgs.jdk17;
+        "org.gradle.java.installations.paths" = "${pkgs.jdk8},${pkgs.jdk11}";
+      };
+    };
+
+    programs.java.package =
+      pkgs.runCommandLocal "java" { home = ""; } "mkdir $out";
+
+    test.stubs = {
+      gradle = { };
+      jdk = { };
+      jdk8 = { };
+      jdk11 = { };
+      jdk17 = { };
+    };
+
+    nmt.script = ''
+      assertFileExists home-files/.gradle/gradle.properties
+      assertFileContent home-files/.gradle/gradle.properties ${
+        builtins.toFile "gradle.expected" ''
+          # Generated with Nix
+
+          org.gradle.caching = true
+          org.gradle.java.home = @jdk17@
+          org.gradle.java.installations.paths = @jdk8@,@jdk11@
+          org.gradle.parallel = true
+        ''
+      }
+    '';
+  };
+}
diff --git a/tests/modules/programs/gradle/external-init-script.gradle b/tests/modules/programs/gradle/external-init-script.gradle
new file mode 100644
index 000000000..ba80b8636
--- /dev/null
+++ b/tests/modules/programs/gradle/external-init-script.gradle
@@ -0,0 +1 @@
+println 'external-init-script-1'
diff --git a/tests/modules/programs/gradle/init-scripts-settings.nix b/tests/modules/programs/gradle/init-scripts-settings.nix
new file mode 100644
index 000000000..49d9a8dce
--- /dev/null
+++ b/tests/modules/programs/gradle/init-scripts-settings.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  config = {
+    programs.gradle = {
+      enable = true;
+
+      initScripts = {
+        "inline-init-script.gradle".text = ''
+          println 'inline-init-script'
+        '';
+        "external-init-script.gradle".source = ./external-init-script.gradle;
+      };
+    };
+
+    programs.java.package =
+      pkgs.runCommandLocal "java" { home = ""; } "mkdir $out";
+
+    test.stubs.gradle = { };
+
+    nmt.script = ''
+      assertFileExists home-files/.gradle/init.d/inline-init-script.gradle
+      assertFileContent home-files/.gradle/init.d/inline-init-script.gradle ${
+        pkgs.writeText "gradle.expected" ''
+          println 'inline-init-script'
+        ''
+      }
+
+      assertFileExists home-files/.gradle/init.d/external-init-script.gradle
+      assertFileContent home-files/.gradle/init.d/external-init-script.gradle ${
+        ./external-init-script.gradle
+      }
+    '';
+  };
+}