diff --git a/modules/programs/go.nix b/modules/programs/go.nix index 4876f7784..7d8adebbf 100644 --- a/modules/programs/go.nix +++ b/modules/programs/go.nix @@ -9,22 +9,41 @@ let literalExpression mkIf mkOption + mkChangedOptionModule + mkRenamedOptionModule types ; cfg = config.programs.go; + keyValueFormat = pkgs.formats.keyValue { }; modeFileContent = "${cfg.telemetry.mode} ${cfg.telemetry.date}"; - in { meta.maintainers = [ lib.maintainers.rvolosatovs ]; + imports = [ + (mkChangedOptionModule [ "programs" "go" "goPath" ] [ "programs" "go" "env" "GOPATH" ] (config: [ + "${config.home.homeDirectory}/${config.programs.go.goPath}" + ])) + + (mkChangedOptionModule [ "programs" "go" "extraGoPaths" ] [ "programs" "go" "env" "GOPATH" ] ( + config: + lib.mkOrder 1500 (map (x: "${config.home.homeDirectory}/${x}") config.programs.go.extraGoPaths) + )) + + (mkChangedOptionModule [ "programs" "go" "goBin" ] [ "programs" "go" "env" "GOBIN" ] ( + config: "${config.home.homeDirectory}/${config.programs.go.goBin}" + )) + + (mkRenamedOptionModule [ "programs" "go" "goPrivate" ] [ "programs" "go" "env" "GOPRIVATE" ]) + ]; + options = { programs.go = { enable = lib.mkEnableOption "Go"; - package = lib.mkPackageOption pkgs "go" { }; + package = lib.mkPackageOption pkgs "go" { nullable = true; }; packages = mkOption { type = with types; attrsOf path; @@ -38,49 +57,51 @@ in description = "Packages to add to GOPATH."; }; - goPath = mkOption { - type = with types; nullOr str; - default = null; - example = "go"; - description = '' - Primary {env}`GOPATH` relative to - {env}`HOME`. It will be exported first and therefore - used by default by the Go tooling. + env = mkOption { + type = types.submodule { + freeformType = with types; attrsOf str; + options = { + GOPATH = mkOption { + type = with types; either str (listOf str); + apply = x: lib.concatStringsSep ":" (lib.toList x); + default = ""; + description = "List of directories that should be used by the Go tooling."; + }; + GOPRIVATE = mkOption { + type = with types; either str (listOf str); + apply = x: lib.concatStringsSep "," (lib.toList x); + default = ""; + description = '' + Controls which modules the 'go' command considers to be private (not + available publicly) and should therefore not use the proxy or checksum database. + ''; + }; + }; + }; + default = { }; + example = lib.literalExpression '' + { + GOPATH = [ + "''${config.home.homeDirectory}/mygo" + "/another/go" + "/yet/another/go" + ]; + + GOPRIVATE = [ + "*.corp.example.com" + "rsc.io/private" + ]; + + CXX = "g++"; + GCCGO = "gccgo"; + GOAMD64 = "v1"; + GOARCH = "amd64"; + GOAUTH = "netrc"; + }; ''; - }; - - extraGoPaths = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ - "extraGoPath1" - "extraGoPath2" - ]; description = '' - Extra {env}`GOPATH`s relative to {env}`HOME` appended - after [](#opt-programs.go.goPath), if that option is set. - ''; - }; - - goBin = mkOption { - type = with types; nullOr str; - default = null; - example = ".local/bin.go"; - description = "GOBIN relative to HOME"; - }; - - goPrivate = mkOption { - type = with types; listOf str; - default = [ ]; - example = [ - "*.corp.example.com" - "rsc.io/private" - ]; - description = '' - The {env}`GOPRIVATE` environment variable controls - which modules the go command considers to be private (not - available publicly) and should therefore not use the proxy - or checksum database. + Environment variables for Go. All the available options + can be found running 'go env'. ''; }; @@ -117,46 +138,56 @@ in }; }; - config = mkIf cfg.enable ( - lib.mkMerge [ - { - home.packages = [ cfg.package ]; + config = + let + firstGoPath = lib.elemAt (lib.splitString ":" cfg.env.GOPATH) 0; + finalEnv = lib.filterAttrs (_: v: v != "") cfg.env; + in + mkIf cfg.enable ( + lib.mkMerge [ + { + assertions = [ + { + assertion = + cfg.packages != { } -> cfg.env.GOPATH != "" -> lib.hasPrefix config.home.homeDirectory firstGoPath; + message = "The first element of `programs.go.env.GOPATH must be an absolute path that points to a directory inside ${config.home.homeDirectory} if `programs.go.packages` is set."; + } + ]; + home.packages = mkIf (cfg.package != null) [ cfg.package ]; - home.file = - let - goPath = if cfg.goPath != null then cfg.goPath else "go"; - mkSrc = n: v: { "${goPath}/src/${n}".source = v; }; - in - lib.foldl' (a: b: a // b) { } (lib.mapAttrsToList mkSrc cfg.packages); - } + home.file = mkIf (cfg.packages != { }) ( + let + mainGoPath = if (cfg.env.GOPATH != "") then firstGoPath else "go"; - (mkIf (cfg.goPath != null) { - home.sessionVariables.GOPATH = lib.concatStringsSep ":" ( - map builtins.toPath ( - map (path: "${config.home.homeDirectory}/${path}") ([ cfg.goPath ] ++ cfg.extraGoPaths) - ) - ); - }) + mkSrc = n: v: { "${mainGoPath}/src/${n}".source = v; }; + in + lib.foldl' (a: b: a // b) { } (lib.mapAttrsToList mkSrc cfg.packages) + ); + } - (mkIf (cfg.goBin != null) { - home.sessionVariables.GOBIN = builtins.toPath "${config.home.homeDirectory}/${cfg.goBin}"; - }) + (mkIf (cfg.env != { }) { + xdg.configFile."go/env" = { + enable = !pkgs.stdenv.hostPlatform.isDarwin; + source = keyValueFormat.generate "go-env" finalEnv; + }; - (mkIf (cfg.goPrivate != [ ]) { - home.sessionVariables.GOPRIVATE = lib.concatStringsSep "," cfg.goPrivate; - }) + home.file."Library/Application Support/go/env" = { + enable = pkgs.stdenv.hostPlatform.isDarwin; + source = keyValueFormat.generate "go-env" finalEnv; + }; + }) - (mkIf (cfg.telemetry.mode != null) { - home.file."Library/Application Support/go/telemetry/mode" = { - enable = pkgs.stdenv.hostPlatform.isDarwin; - text = modeFileContent; - }; + (mkIf (cfg.telemetry.mode != null) { + home.file."Library/Application Support/go/telemetry/mode" = { + enable = pkgs.stdenv.hostPlatform.isDarwin; + text = modeFileContent; + }; - xdg.configFile."go/telemetry/mode" = { - enable = !pkgs.stdenv.hostPlatform.isDarwin; - text = modeFileContent; - }; - }) - ] - ); + xdg.configFile."go/telemetry/mode" = { + enable = !pkgs.stdenv.hostPlatform.isDarwin; + text = modeFileContent; + }; + }) + ] + ); } diff --git a/tests/modules/programs/go/default.nix b/tests/modules/programs/go/default.nix index cbf58092d..fb53fb618 100644 --- a/tests/modules/programs/go/default.nix +++ b/tests/modules/programs/go/default.nix @@ -1 +1,9 @@ -{ go-telemetry = ./go-telemetry.nix; } +{ + go-telemetry = ./go-telemetry.nix; + go-example-config = ./example-config.nix; + go-suboptions-unset = ./suboptions-unset.nix; + go-packages-default-gopath = ./packages-default-gopath.nix; + go-packages-custom-gopath = ./packages-custom-gopath.nix; + go-packages-invalid-main-gopath = ./packages-invalid-main-gopath.nix; + go-old-options = ./old-options.nix; +} diff --git a/tests/modules/programs/go/env b/tests/modules/programs/go/env new file mode 100644 index 000000000..c6928fcba --- /dev/null +++ b/tests/modules/programs/go/env @@ -0,0 +1,7 @@ +CXX=g++ +GCCGO=gccgo +GOAMD64=v1 +GOARCH=amd64 +GOAUTH=netrc +GOPATH=/home/hm-user/mygo:/another/go:/yet/another/go +GOPRIVATE=*.corp.example.com,rsc.io/private diff --git a/tests/modules/programs/go/env-old-options b/tests/modules/programs/go/env-old-options new file mode 100644 index 000000000..fd441e423 --- /dev/null +++ b/tests/modules/programs/go/env-old-options @@ -0,0 +1,3 @@ +GOBIN=/home/hm-user/.local/bin.go +GOPATH=/home/hm-user/mygo:/home/hm-user/another/go:/home/hm-user/yet/another/go +GOPRIVATE=*.corp.example.com,rsc.io/private diff --git a/tests/modules/programs/go/env-suboptions-unset b/tests/modules/programs/go/env-suboptions-unset new file mode 100644 index 000000000..61814f3b1 --- /dev/null +++ b/tests/modules/programs/go/env-suboptions-unset @@ -0,0 +1,5 @@ +CXX=g++ +GCCGO=gccgo +GOAMD64=v1 +GOARCH=amd64 +GOAUTH=netrc diff --git a/tests/modules/programs/go/example-config.nix b/tests/modules/programs/go/example-config.nix new file mode 100644 index 000000000..8668d6832 --- /dev/null +++ b/tests/modules/programs/go/example-config.nix @@ -0,0 +1,33 @@ +{ pkgs, config, ... }: + +{ + programs.go = { + enable = true; + env = { + GOPATH = [ + "${config.home.homeDirectory}/mygo" + "/another/go" + "/yet/another/go" + ]; + GOPRIVATE = [ + "*.corp.example.com" + "rsc.io/private" + ]; + CXX = "g++"; + GCCGO = "gccgo"; + GOAMD64 = "v1"; + GOARCH = "amd64"; + GOAUTH = "netrc"; + }; + }; + + nmt.script = + let + goCfgDir = if !pkgs.stdenv.isDarwin then ".config/go" else "Library/Application Support/go"; + in + '' + assertFileExists "home-files/${goCfgDir}/env" + assertFileContent "home-files/${goCfgDir}/env" \ + ${./env} + ''; +} diff --git a/tests/modules/programs/go/old-options.nix b/tests/modules/programs/go/old-options.nix new file mode 100644 index 000000000..d8007efa1 --- /dev/null +++ b/tests/modules/programs/go/old-options.nix @@ -0,0 +1,40 @@ +{ + lib, + options, + pkgs, + config, + ... +}: + +{ + programs.go = { + enable = true; + goPath = "mygo"; + extraGoPaths = [ + "another/go" + "yet/another/go" + ]; + goBin = ".local/bin.go"; + goPrivate = [ + "*.corp.example.com" + "rsc.io/private" + ]; + }; + + test.asserts.warnings.expected = [ + "The option `programs.go.goPrivate' defined in ${lib.showFiles options.programs.go.goPrivate.files} has been renamed to `programs.go.env.GOPRIVATE'." + "The option `programs.go.goBin' defined in ${lib.showFiles options.programs.go.goBin.files} has been changed to `programs.go.env.GOBIN' that has a different type. Please read `programs.go.env.GOBIN' documentation and update your configuration accordingly." + "The option `programs.go.extraGoPaths' defined in ${lib.showFiles options.programs.go.extraGoPaths.files} has been changed to `programs.go.env.GOPATH' that has a different type. Please read `programs.go.env.GOPATH' documentation and update your configuration accordingly." + "The option `programs.go.goPath' defined in ${lib.showFiles options.programs.go.goPath.files} has been changed to `programs.go.env.GOPATH' that has a different type. Please read `programs.go.env.GOPATH' documentation and update your configuration accordingly." + ]; + + nmt.script = + let + goCfgDir = if !pkgs.stdenv.isDarwin then ".config/go" else "Library/Application\ Support/go"; + in + '' + assertFileExists "home-files/${goCfgDir}/env" + assertFileContent "home-files/${goCfgDir}/env" \ + ${./env-old-options} + ''; +} diff --git a/tests/modules/programs/go/packages-custom-gopath.nix b/tests/modules/programs/go/packages-custom-gopath.nix new file mode 100644 index 000000000..febdff47e --- /dev/null +++ b/tests/modules/programs/go/packages-custom-gopath.nix @@ -0,0 +1,22 @@ +{ config, ... }: + +{ + programs.go = { + enable = true; + env.GOPATH = [ + "${config.home.homeDirectory}/mygo" + "/another/go" + "/yet/another/go" + ]; + + packages = { + "golang.org/x/text" = ./packages/text; + "golang.org/x/time" = ./packages/time; + }; + }; + + nmt.script = '' + assertFileExists home-files/mygo/src/golang.org/x/text/main.go + assertFileExists home-files/mygo/src/golang.org/x/time/main.go + ''; +} diff --git a/tests/modules/programs/go/packages-default-gopath.nix b/tests/modules/programs/go/packages-default-gopath.nix new file mode 100644 index 000000000..46570ef7e --- /dev/null +++ b/tests/modules/programs/go/packages-default-gopath.nix @@ -0,0 +1,14 @@ +{ + programs.go = { + enable = true; + packages = { + "golang.org/x/text" = ./packages/text; + "golang.org/x/time" = ./packages/time; + }; + }; + + nmt.script = '' + assertFileExists home-files/go/src/golang.org/x/text/main.go + assertFileExists home-files/go/src/golang.org/x/time/main.go + ''; +} diff --git a/tests/modules/programs/go/packages-invalid-main-gopath.nix b/tests/modules/programs/go/packages-invalid-main-gopath.nix new file mode 100644 index 000000000..ed45f1fff --- /dev/null +++ b/tests/modules/programs/go/packages-invalid-main-gopath.nix @@ -0,0 +1,20 @@ +{ config, ... }: + +{ + programs.go = { + enable = true; + env.GOPATH = [ + "/not/my/home/mygo" + "/another/go" + "/yet/another/go" + ]; + + packages = { + "golang.org/x/text" = ./packages/text; + "golang.org/x/time" = ./packages/time; + }; + }; + test.asserts.assertions.expected = [ + "The first element of `programs.go.env.GOPATH must be an absolute path that points to a directory inside ${config.home.homeDirectory} if `programs.go.packages` is set." + ]; +} diff --git a/tests/modules/programs/go/packages/text/main.go b/tests/modules/programs/go/packages/text/main.go new file mode 100644 index 000000000..4ffdd9fcc --- /dev/null +++ b/tests/modules/programs/go/packages/text/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello Home-Manager from 'text'!") +} diff --git a/tests/modules/programs/go/packages/time/main.go b/tests/modules/programs/go/packages/time/main.go new file mode 100644 index 000000000..411199f4b --- /dev/null +++ b/tests/modules/programs/go/packages/time/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello Home-Manager from 'time'!") +} diff --git a/tests/modules/programs/go/suboptions-unset.nix b/tests/modules/programs/go/suboptions-unset.nix new file mode 100644 index 000000000..83ca58509 --- /dev/null +++ b/tests/modules/programs/go/suboptions-unset.nix @@ -0,0 +1,24 @@ +{ pkgs, config, ... }: + +{ + programs.go = { + enable = true; + env = { + CXX = "g++"; + GCCGO = "gccgo"; + GOAMD64 = "v1"; + GOARCH = "amd64"; + GOAUTH = "netrc"; + }; + }; + + nmt.script = + let + goCfgDir = if !pkgs.stdenv.isDarwin then ".config/go" else "Library/Application\ Support/go"; + in + '' + assertFileExists "home-files/${goCfgDir}/env" + assertFileContent "home-files/${goCfgDir}/env" \ + ${./env-suboptions-unset} + ''; +}