sbt: allow managing the ~/.sbt/repositories file

sbt allows overriding the default repositories to use to resolve
dependencies. This is often used with proxies and/or private
repositories to host internal packages.

This change adds a `repositories` attribute to `sbt` to allow
specifying the values that will go in `~/.sbt/repositories` file.

To support the above change we also deprecate the `baseConfigPath`
option in favour of `baseUserConfigPath` which points one level higher
by default. This allows not using relative paths to refer to the
top-level configuration directory.

Also adds tests for the new option and the deprecation of the previous
one.
This commit is contained in:
Philippe Laflamme 2022-10-02 22:52:33 -04:00 committed by Robert Helgesson
parent 6427ae9578
commit 599e22b1c7
No known key found for this signature in database
GPG key ID: 36BDAA14C2797E89
5 changed files with 167 additions and 6 deletions

View file

@ -16,6 +16,17 @@ let
import scala.sys.process._ import scala.sys.process._
${concatStrings (map renderCredential creds)}''; ${concatStrings (map renderCredential creds)}'';
renderRepository = value:
if isString value then ''
${value}
'' else ''
${concatStrings (mapAttrsToList (name: value: "${name}: ${value}") value)}
'';
renderRepositories = repos: ''
[repositories]
${concatStrings (map renderRepository cfg.repositories)}'';
sbtTypes = { sbtTypes = {
plugin = types.submodule { plugin = types.submodule {
options = { options = {
@ -68,6 +79,11 @@ let
cfg = config.programs.sbt; cfg = config.programs.sbt;
in { in {
imports = [
(mkRemovedOptionModule [ "programs" "sbt" "baseConfigPath" ]
"Use programs.sbt.baseUserConfigPath instead, but note that the semantics are slightly different.")
];
meta.maintainers = [ maintainers.kubukoz ]; meta.maintainers = [ maintainers.kubukoz ];
options.programs.sbt = { options.programs.sbt = {
@ -80,10 +96,13 @@ in {
description = "The package with sbt to be installed."; description = "The package with sbt to be installed.";
}; };
baseConfigPath = mkOption { baseUserConfigPath = mkOption {
type = types.str; type = types.str;
default = ".sbt/1.0"; default = ".sbt";
description = "Where the plugins and credentials should be located."; description = ''
Where the sbt configuration files should be located, relative
<envar>HOME</envar>.
'';
}; };
plugins = mkOption { plugins = mkOption {
@ -123,19 +142,63 @@ in {
A list of credentials to define in the sbt configuration directory. A list of credentials to define in the sbt configuration directory.
''; '';
}; };
repositories = mkOption {
type = with types;
listOf
(either (enum [ "local" "maven-central" "maven-local" ]) (attrsOf str));
default = [ ];
example = literalExpression ''
[
"local"
{ my-ivy-proxy-releases = "http://repo.company.com/ivy-releases/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]" }
{ my-maven-proxy-releases = "http://repo.company.com/maven-releases/" }
"maven-central"
]
'';
description = ''
A list of repositories to use when resolving dependencies. Defined as a
list of pre-defined repository or custom repository as a set of name to
URL. The list will be used populate the <code>~/.sbt/repositories</code>
file in the order specified.
</para><para>
Pre-defined repositories must be one of <code>local</code>,
<code>maven-local</code>, <code>maven-central</code>.
</para><para>
Custom repositories are defined as
<code language="nix">{ name-of-repo = "https://url.to.repo.com"}</code>.
</para><para>
See
<link xlink:href="https://www.scala-sbt.org/1.x/docs/Launcher-Configuration.html#3.+Repositories+Section"/>
about this configuration section and
<link xlink:href="https://www.scala-sbt.org/1.x/docs/Proxy-Repositories.html"/>
to read about proxy repositories.
'';
};
}; };
config = mkIf cfg.enable (mkMerge [ config = mkIf cfg.enable (mkMerge [
{ home.packages = [ cfg.package ]; } { home.packages = [ cfg.package ]; }
(mkIf (cfg.plugins != [ ]) { (mkIf (cfg.plugins != [ ]) {
home.file."${cfg.baseConfigPath}/plugins/plugins.sbt".text = home.file."${cfg.baseUserConfigPath}/1.0/plugins/plugins.sbt".text =
concatStrings (map renderPlugin cfg.plugins); concatStrings (map renderPlugin cfg.plugins);
}) })
(mkIf (cfg.credentials != [ ]) { (mkIf (cfg.credentials != [ ]) {
home.file."${cfg.baseConfigPath}/credentials.sbt".text = home.file."${cfg.baseUserConfigPath}/1.0/credentials.sbt".text =
renderCredentials cfg.credentials; renderCredentials cfg.credentials;
}) })
(mkIf (cfg.repositories != [ ]) {
home.file."${cfg.baseUserConfigPath}/repositories".text =
renderRepositories cfg.repositories;
})
]); ]);
} }

View file

@ -1,4 +1,7 @@
{ {
sbt-plugins = ./plugins.nix;
sbt-credentials = ./credentials.nix; sbt-credentials = ./credentials.nix;
sbt-deprecated-options = ./deprecated-options.nix;
sbt-plugins = ./plugins.nix;
sbt-repositories = ./repositories.nix;
sbt-user-config-path = ./user-config-path.nix;
} }

View file

@ -0,0 +1,18 @@
{ ... }:
{
programs.sbt = {
enable = true;
baseConfigPath = "gone";
};
test.stubs.sbt = { };
test.asserts.assertions.expected = [
(let offendingFile = toString ./deprecated-options.nix;
in ''
The option definition `programs.sbt.baseConfigPath' in `${offendingFile}' no longer has any effect; please remove it.
Use programs.sbt.baseUserConfigPath instead, but note that the semantics are slightly different.
'')
];
}

View file

@ -0,0 +1,38 @@
{ pkgs, ... }:
let
repositories = [
"local"
{ my-maven-proxy = "http://repo.mavenproxy.io/a/b/c/d"; }
"maven-local"
{
my-ivy-proxy =
"http://repo.company.com/ivy-releases/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]";
}
"maven-central"
];
expectedRepositories = builtins.toFile "repositories" ''
[repositories]
local
my-maven-proxy: http://repo.mavenproxy.io/a/b/c/d
maven-local
my-ivy-proxy: http://repo.company.com/ivy-releases/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
maven-central
'';
repositoriesSbtPath = ".sbt/repositories";
in {
config = {
programs.sbt = {
enable = true;
repositories = repositories;
package = pkgs.writeScriptBin "sbt" "";
};
nmt.script = ''
assertFileExists "home-files/${repositoriesSbtPath}"
assertFileContent "home-files/${repositoriesSbtPath}" "${expectedRepositories}"
'';
};
}

View file

@ -0,0 +1,39 @@
{ config, lib, pkgs, ... }:
with lib;
let
plugins = [{
org = "a";
artifact = "b";
version = "c";
}];
credentials = [{
realm = "a";
host = "b";
user = "c";
passwordCommand = "d";
}];
repositories = [ "local" ];
baseSbtPath = ".config/sbt";
in {
config = {
programs.sbt = {
enable = true;
plugins = plugins;
credentials = credentials;
repositories = repositories;
baseUserConfigPath = ".config/sbt";
package = pkgs.writeScriptBin "sbt" "";
};
nmt.script = ''
assertFileExists "home-files/${baseSbtPath}/1.0/plugins/plugins.sbt"
assertFileExists "home-files/${baseSbtPath}/1.0/credentials.sbt"
assertFileExists "home-files/${baseSbtPath}/repositories"
'';
};
}