Skip to content

Instantly share code, notes, and snippets.

@lgirault lgirault/buildSbtEnv.nix
Last active Sep 3, 2018

Embed
What would you like to do?
{ callPackage, stdenv, makeWrapper, jdk, scala, sbt, writeText,
our-sbt-plugins }:
with stdenv.lib;
rec {
prepareSbtProject = args@{
repo, name, src,
groupId,
buildInputs ? [],
sbtixBuildInputs ? [],
ourDependencies ? [],
sbtOptions ? "",
extraRepo ? [],
extraSbtPlugins ? [],
managedSbtFiles ? false,
sbtProjectDefHook ? "",
JAVA_OPTS ? "",
...}:
with callPackage (import ./mkSbtDependencies.nix) {};
let
sbtPluginsInputs =
(if name == "root-sbt-plugin" then [] else [our-sbt-plugins.root-sbt-plugin]) ++
(map (p: builtins.getAttr p our-sbt-plugins) extraSbtPlugins);
ourInputs = (map (d: d.dep) ourDependencies) ++ sbtPluginsInputs;
allSbtixInputs = sbtixBuildInputs ++ ourInputs;
fetchedDependencies = getFetchedDependencies {
inherit groupId name repo;
sbtixBuildInputs = allSbtixInputs;
};
builtDependencies = builtins.trace "==> getBuiltDependencies of ${groupId} ${name}" (
#(getBuiltDependencies sbtixBuildInputs) should'nt be needed
# if we properly separate built and fetched dependencies
# in builds description
(getBuiltDependencies allSbtixInputs));
# (getBuiltDependencies sbtixBuildInputs) ++
# (getBuiltDependencies ourInputs)));
sbtixRepoList = builtins.trace "==> sbtixRepoList of ${groupId} ${name}" (
(concatMap fetchedRepoConfig fetchedDependencies) ++
(map buildDepRepoConfig builtDependencies) ++
(map extraRepoConfig extraRepo));
sbtixReposFile = writeRepositories groupId name (unique sbtixRepoList);
sbtDerivation = (import ./mkSbtDerivation.nix) (args //
{ inherit stdenv sbtOptions managedSbtFiles extraSbtPlugins ourDependencies
sbtixReposFile printOrg sbtProjectDefHook;
});
in {
inherit groupId extraSbtPlugins;
inherit (sbtDerivation) prepareSbtLocalEnv;
body = sbtDerivation // {
inherit name src repo groupId JAVA_OPTS;
buildInputs = [ makeWrapper jdk sbt ] ++ buildInputs;
};
repos = {
builtRepos = builtDependencies;
fetchedRepos = fetchedDependencies;
};
};
prepareSbtProgram = args: prepareSbtProject ({
installPhase = ''
sbt stage
mkdir -p $out/
cp target/universal/stage/* $out/ -r
for p in $(find $out/bin/* -executable); do
wrapProgram "$p" --prefix PATH : ${jre}/bin
done
'';
} // args);
buildSbtProject = {body, repos, ...}:
let
excluded = [ "repo" "sbtixBuildInputs" "sbtOptions" "fetchedDependencies" "extraRepo" "extraFilter" ];
filteredBody = builtins.removeAttrs body excluded;
in
stdenv.mkDerivation filteredBody // repos;
buildSbtDevEnv = { body, repos, extraSbtPlugins, ...}:
let
newRepo = body.repo;
newExtraRepo = body.extraRepo or [] ++ [
{
name = "nexus-ivy-proxy-releases";
path = "https://private-nexus.com/content/groups/external/";
pattern = "[organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]";
}
{
name = "nexus-maven-proxy-releases";
path = "https://private-nexus.com/content/groups/external/";
}
];
bodyDiff = {
buildInputs = body.buildInputs ++ [ scala ];
repo = newRepo;
extraRepo = newExtraRepo;
};
newBody = body // bodyDiff;
newProject = prepareSbtProject (builtins.removeAttrs newBody ["SBT_OPTS"] );
shellHook =
let
prepareSbtGlobalEnv = ''
mkdir -p .sbt/1.0/plugins
ln -sf ${credentials} .sbt/1.0/credentials.sbt
ln -sf ${credentials} .sbt/1.0/plugins/credentials.sbt
'';
in
''
rm -rf .ivy2
rm -rf .sbt
rm -rf .staging
rm -rf $(find . -name target)
''
+ body.prepareSbtLocalEnv
+ prepareSbtGlobalEnv
+ ''
echo 'addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0")' > .sbt/1.0/plugins/plugins.sbt
echo '${newProject.body.SBT_OPTS}' > .sbtopts
'';
bodyWithShellHook = newProject.body // { inherit shellHook; };
in
buildSbtProject {
repos = newProject.repos;
body = bodyWithShellHook;
};
credentials = writeText "credentials.sbt"
''credentials += Credentials(Path.userHome / ".ivy2" / ".credentials")'';
plugins =
let pluginList = map (p: "addSbtPlugin(${p})" ) [
''"net.virtual-void" % "sbt-dependency-graph" % "0.9.0"''
];
in writeText "plugins.sbt"
concatStringsSep "\n" pluginList;
printOrg = writeText "printOrg.sbt"
''import sbt._
import Keys._
import java.io.FileWriter
val printOrg = taskKey[File]("create a file containing the fullclass path")
printOrg := Def.task {
val f = baseDirectory.value / "target" / "org.txt"
val writer = new FileWriter(f)
writer write organization.value
writer.close()
f
}.value
'';
}
{ sbt-build
, this-build ## result of the project-default.nix provided via callPackage
}:
let
D = sbt-build.dependencies.wrapHelpers;
commonConfig = {
groupId = "group";
baseSbtConf = ''
ThisBuild / organization := "our-org"
ThisBuild / scalaVersion := "2.12.6"
ThisBuild / Test / publishArtifact := true
ThisBuild / version := "1.2"
'';
repo = [
(import ./repo.nix)
(import ./project/repo.nix)
(import ./manual-repo.nix)
];
doDist = false;
#JAVA_OPTS = "-Xmx4G";
managedSbtFiles = true;
};
in {
module-a =
mics-build.prepareSbtProject ( commonConfig // {
name = "module-a";
src = ./module-a;
micsDependencies = [];
});
module-b =
mics-build.prepareSbtProject ( commonConfig // {
name = "module-b";
src = ./module-b;
micsDependencies = (map D.norm [
this-build.module-a
]) ++ (map D.test [
this-build.module-a
]);
});
}
{ callPackage, sourceFilter }:
let
helper = callPackage (import ./buildSbtEnv.nix) {};
ignoredFiles = [
{ type = "directory"; name = ".git"; }
{ type = "symlink"; name = "result"; }
{ type = "symlink"; name = "result-lib"; }
{ type = "directory"; name = ".idea"; }
{ type = "directory"; name = "target"; }
{ type = "directory"; name = ".ivy2"; }
{ type = "directory"; name = ".sbt"; }
{ type = "directory"; name = ".staging"; }
{ type = "directory"; name = ".coursier"; }
{ type = "regular"; name = ".sbtopts"; }
{ type = "regular"; name = ".gitignore"; }
{ type = "regular"; name = ".gitattributes"; }
{ type = "regular"; name = "idea.sbt"; }
{ type = "regular"; name = "readme.md"; }
];
projectProjectFilter = sourceFilter.not (path: type:
builtins.baseNameOf (builtins.dirOf path) == "project" && builtins.baseNameOf path == "project"
);
wrap = f: args@{dontExcludeProjectProject ? false, ...}:
let
extraFilter = args.extraFilter or sourceFilter.trueFilter;
filterSrc = builtins.filterSource
(builtins.foldl' sourceFilter.and sourceFilter.trueFilter
[
(sourceFilter.excludeFromList ignoredFiles)
(sourceFilter.not (sourceFilter.acceptExt "nix"))
(if dontExcludeProjectProject then sourceFilter.trueFilter else projectProjectFilter)
extraFilter
]
);
in
f (args // {src = filterSrc args.src; });
in
{
dependencies = callPackage (import ./mkSbtDependencies.nix) {};
prepareSbtProject = wrap helper.prepareSbtProject;
buildSbtProject = helper.buildSbtProject;
buildSbtDevEnv = helper.buildSbtDevEnv;
}
{stdenv, lib, runCommand, fetchurl, writeText}:
rec {
#repo shape
# e.g any repo.nix file
#{ "versioning": [{ "scalaVersion" = "2.12.6";
# "sbtVersion" = "1.1.1";
# }];
# "repos" = {
# "repo-name" = "path/schema";
# "repo-name-2" = "";
# };
# "artifacts" = {
# "store-path" = {
# url = "url the file was retrieve from";
# sha256 = "blabla";
# };
# "another-store-path" = {
# url = "url this other file was retrieve from";
# sha256 = "another-blabla";
# };
# };
#}
mergeRepoList = repoList:
with stdenv.lib;
let
mergeAttr = attr: repo:
fold (a: b: a // b) {} (catAttrs attr repo);
artifacts = mergeAttr "artifacts" repoList;
repos = mergeAttr "repos" repoList;
in
{ inherit artifacts repos; };
getFetchedDependencies = { groupId, name, repo, sbtixBuildInputs }:
with stdenv.lib;
let
mergedRepo = mergeRepoList repo;
mkRepo = name: artifacts: runCommand name {}
(let
parentDirs = filePath:
concatStringsSep "/" (init (splitString "/" filePath));
linkArtifact = outputPath: urlAttrs:
[ ''mkdir -p "$out/${parentDirs outputPath}"''
''ln -fsn "${fetchurl urlAttrs}" "$out/${outputPath}"''
];
in
lib.concatStringsSep "\n" (lib.concatLists (lib.mapAttrsToList linkArtifact artifacts)));
nixrepo = mkRepo "${name}-repo" mergedRepo.artifacts;
thisFetchedDependencies = { inherit nixrepo groupId name; repos = mergedRepo.repos; };
in
[thisFetchedDependencies] ++ concatMap (d: d.fetchedRepos) sbtixBuildInputs;
getBuiltDependencies = sbtixBuildInputs:
let
f = d: [ d.lib or d ] ++ d.builtRepos;
in with stdenv.lib;
concatMap f sbtixBuildInputs;
fetchedRepoConfig = {repos, nixrepo, groupId ? "", name}:
let
prefix = stdenv.lib.optionalString (groupId != "") "${groupId}-";
repoPatternOptional = repoPattern:
stdenv.lib.optionalString (repoPattern != "") ", ${repoPattern}";
repoPath = repoName: repoPattern:
[ "${prefix}${name}-${repoName}: file://${nixrepo}/${repoName}${repoPatternOptional repoPattern}" ];
in
lib.concatLists (lib.mapAttrsToList repoPath repos);
extraRepoConfig = d:
let
pattern = if builtins.hasAttr "pattern" d then ", ${d.pattern}" else "";
in
"${d.name}: ${d.path}${pattern}";
ivyRepoPattern = "[organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]";
buildDepRepoConfig = d:
#builtins.trace "buildDepRepoConfig of ${d.groupId} ${d.name}"
"${d.groupId}-${d.name}: file://${d}, ${ivyRepoPattern}";
writeRepositories = groupId: name: repoList:
writeText "${groupId}-${name}-sbtix-repos" ''
[repositories]
${stdenv.lib.concatStringsSep "\n " repoList}
local
'';
wrapHelpers = rec {
wrapl = d: cos: cl: {dep = d; confs = cos ; classifier = cl; };
wrap = d: co: cl: wrapl d [ co ] cl;
norm = d: wrap d "" "";
compile = d : wrap d "compile" "";
test = d : wrap d "test" "";
classify = wrapped: c : wrapped // { classifier = c; };
classifiedTest = d : wrap d "test" "tests";
} ;
}
{ stdenv
, sbtixReposFile
, sbtOptions
, withJPAHack ? false
, managedSbtFiles
, baseSbtConf ? ""
, name
, extraSbtPlugins
, sbtProjectDefHook
, ourDependencies
, printOrg
, ...}:
let
optional = stdenv.lib.optional;
optStr = cond: str: if cond then str else "";
doNotEditMessage = "DO NOT EDIT : file auto generated by nix build";
buildProperties = "# ${doNotEditMessage}
sbt.version=1.2.1";
pluginsSbt =
let pluginList = map (p: ''addSbtPlugin("com.private.sbt-plugins" % "${p}" % "0.1-SNAPSHOT")'' ) ([ "root-sbt-plugin" ] ++ extraSbtPlugins);
in
''//${doNotEditMessage}
logLevel := Level.Warn
classpathTypes += "maven-plugin"
${stdenv.lib.concatStringsSep "\n" pluginList}'';
mkOurSbtDependency =
let
confStr = c:
if c == "test" then " % Test"
else if c == "compile" then " % Compile"
else if c == "it" then " % IntegrationTest"
else if c == "" then ""
else abort "unknown sbt conf ${c}";
clasifierStr = c:
if c == "" then ""
else " classifier \"${c}\"";
in
{ dep, confs, classifier} :
let strs =
map (c:
''libraryDependencies in ThisBuild += BuildHelper.${dep.groupId}("${dep.name}")${confStr c}${clasifierStr classifier}'')
confs;
in stdenv.lib.concatStringsSep "\n" strs;
dependenciesSbt =
''//${doNotEditMessage}
${stdenv.lib.concatStringsSep "\n" (map mkOurSbtDependency ourDependencies)}
'';
d = rec {
prepareSbtLocalEnv =
if ! managedSbtFiles then ""
else let
managedBuild = ''//${doNotEditMessage}
${baseSbtConf}
val root = BuildHelper.project("${name}")
${sbtProjectDefHook}
'';
in ''
mkdir -p ./project
mkdir -p ./.sbt/1.0
ln -sf ${printOrg} ./.sbt/1.0/printOrg.sbt
echo '${buildProperties}' > ./project/build.properties
echo '${pluginsSbt}' > ./project/managedPlugins.sbt
echo '${managedBuild}' > managedBuild.sbt
echo '${dependenciesSbt}' > managedDependencies.sbt
'';
dontPatchELF = true;
dontStrip = true;
# COURSIER_CACHE env variable is needed if one wants to use non-sbtix repositories in the below repo list, which is sometimes useful.
COURSIER_CACHE = "./.coursier/cache/v1";
# set environment variable to affect all SBT commands
SBT_OPTS = ''
-Dsbt.ivy.home=./.ivy2/
-Dsbt.boot.directory=./.sbt/boot/
-Dsbt.global.base=$PWD/.sbt/1.0
-Dsbt.global.staging=./.staging
-Dsbt.override.build.repos=true
-Dsbt.repository.config=${sbtixReposFile}
${sbtOptions}
'';
JAVA_TOOL_OPTIONS = ''-Dfile.encoding=UTF8'';
outputs = ["out"];
buildPhase =
prepareSbtLocalEnv
+ ''
${optStr (! withJPAHack) "sbt compile"}
'';
checkPhase = "sbt test";
installPhase =
''sbt publishLocal
mkdir -p $out
cp ./.ivy2/local/* $out/ -r'';
doCheck = true;
};
in d
{ callPackage
, sbt-build
, stdenv
}:
let confs = builtins.removeAttrs (callPackage ./common.nix { }) ["override" "overrideDerivation"];
in
stdenv.lib.mapAttrs (name: conf: mics-build.buildSbtProject conf) confs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.