Files
gradle2nix/gradle-env.nix
Tad Fisher 8ceeeb9611 WIP
2023-12-19 13:52:14 -08:00

427 lines
13 KiB
Nix

# This file is generated by gradle2nix.
#
# Example usage (e.g. in default.nix):
#
# with (import <nixpkgs> {});
# let
# buildGradle = callPackage ./gradle-env.nix {};
# in
# buildGradle {
# envSpec = ./gradle-env.json;
#
# src = ./.;
#
# gradleFlags = [ "installDist" ];
#
# installPhase = ''
# mkdir -p $out
# cp -r app/build/install/myproject $out
# '';
# }
{ lib
, stdenv
, buildEnv
, fetchs3
, fetchurl
, gradleGen
, runCommandLocal
, symlinkJoin
, writeText
, writeTextDir
}:
{
# Path to the environment spec generated by gradle2nix (e.g. gradle-env.json).
envSpec
, pname ? "project"
, version ? null
, enableParallelBuilding ? true
# Arguments to Gradle used to build the project in buildPhase.
, gradleFlags ? [ "build" ]
# Gradle package to use instead of the one generated by gradle2nix.
, gradlePackage ? null
# Enable debugging for the Gradle build; this will cause Gradle to run a debug server
# and wait for a JVM debugging client to attach.
, enableDebug ? false
# Additional code to run in the Gradle init script (init.gradle).
, extraInit ? ""
# Override the default JDK used to run Gradle itself.
, buildJdk ? null
# Override functions which fetch dependency artifacts.
# Keys in this set are URL schemes such as "https" or "s3".
# Values are functions which take a dependency in the form
# `{ urls, sha256 }` and fetch into the Nix store. For example:
#
# {
# s3 = { name, urls, hash }: fetchs3 {
# s3url = builtins.head urls;
# # TODO This doesn't work without patching fetchs3 to accept SRI hashes
# inherit name hash;
# region = "us-west-2";
# credentials = {
# access_key_id = "foo";
# secret_access_key = "bar";
# };
# };
# }
, fetchers ? { }
, ... } @ args:
let
inherit (builtins)
attrValues concatStringsSep elemAt filter fromJSON getAttr head match
replaceStrings sort;
inherit (lib)
assertMsg concatMapStringsSep groupBy' hasSuffix hasPrefix last mapAttrs
mapAttrsToList optionalString readFile removeSuffix unique versionAtLeast
versionOlder;
inherit (lib.strings) sanitizeDerivationName;
toCoordinates = id:
let
coords = builtins.split ":" id;
in {
group = elemAt coords 0;
module = elemAt coords 2;
};
fetchers' = {
http = fetchurl;
https = fetchurl;
} // fetchers;
# Fetch urls using the scheme for the first entry only; there isn't a
# straightforward way to tell Nix to try multiple fetchers in turn
# and short-circuit on the first successful fetch.
fetch = _: { urls, hash }:
let
first = head urls;
scheme = head (builtins.match "([a-z0-9+.-]+)://.*" first);
fetch' = getAttr scheme fetchers';
urls' = filter (hasPrefix scheme) urls;
in
fetch' { urls = urls'; inherit hash; };
mkDep = id: version: artifacts:
let
coords = toCoordinates id;
modulePath = "${replaceStrings ["."] ["/"] coords.group}/${coords.module}/${version}";
in
stdenv.mkDerivation {
pname = "${coords.group}-${coords.module}";
version = version;
srcs = mapAttrsToList fetch artifacts;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontFixup = true;
dontInstall = true;
preUnpack = ''
mkdir -p "$out/${modulePath}"
'';
unpackCmd = ''
cp "$curSrc" "$out/${modulePath}/$(stripHash "$curSrc")"
'';
sourceRoot = ".";
preferLocalBuild = true;
allowSubstitutes = false;
};
mkModule = id: versions:
mapAttrsToList (version: artifacts: mkDep id version artifacts) versions;
mkModuleMetadata = id: versions:
let
modules = groupBy'
(meta: id:
let
isNewer = versionOlder meta.latest id.version;
isNewerRelease =
!(hasSuffix "-SNAPSHOT" id.version) &&
versionOlder meta.release id.version;
in {
groupId = id.group;
artifactId = id.name;
latest = if isNewer then id.version else meta.latest;
release = if isNewerRelease then id.version else meta.release;
versions = meta.versions ++ [id.version];
}
)
{
latest = "";
release = "";
versions = [];
}
(id: "${replaceStrings ["."] ["/"] id.group}/${id.name}/maven-metadata.xml")
ids;
in
attrValues (mapAttrs (path: meta:
let
versions' = sort versionOlder (unique meta.versions);
in
with meta; writeTextDir path ''
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1">
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<versioning>
${optionalString (latest != "") "<latest>${latest}</latest>"}
${optionalString (release != "") "<release>${release}</release>"}
<versions>
${concatMapStringsSep "\n " (v: "<version>${v}</version>") versions'}
</versions>
</versioning>
</metadata>
''
) modules);
# mkSnapshotMetadata = deps:
# let
# snapshotDeps = filter (dep: dep ? build && dep ? timestamp) deps;
# modules = groupBy'
# (meta: dep:
# let
# id = dep.id;
# isNewer = dep.build > meta.buildNumber;
# # Timestamp values can be bogus, e.g. jitpack.io
# updated = if (match "[0-9]{8}\.[0-9]{6}" dep.timestamp) != null
# then replaceStrings ["."] [""] dep.timestamp
# else "";
# in {
# groupId = id.group;
# artifactId = id.name;
# version = id.version;
# timestamp = if isNewer then dep.timestamp else meta.timestamp;
# buildNumber = if isNewer then dep.build else meta.buildNumber;
# lastUpdated = if isNewer then updated else meta.lastUpdated;
# versions = meta.versions or [] ++ [{
# classifier = id.classifier or "";
# extension = id.extension;
# value = "${removeSuffix "-SNAPSHOT" id.version}-${dep.timestamp}-${toString dep.build}";
# updated = updated;
# }];
# }
# )
# {
# timestamp = "";
# buildNumber = -1;
# lastUpdated = "";
# }
# (dep: "${replaceStrings ["."] ["/"] dep.id.group}/${dep.id.name}/${dep.id.version}/maven-metadata.xml")
# snapshotDeps;
# mkSnapshotVersion = version: ''
# <snapshotVersion>
# ${optionalString (version.classifier != "") "<classifier>${version.classifier}</classifier>"}
# <extension>${version.extension}</extension>
# <value>${version.value}</value>
# ${optionalString (version.updated != "") "<updated>${version.updated}</updated>"}
# </snapshotVersion>
# '';
# in
# attrValues (mapAttrs (path: meta:
# with meta; writeTextDir path ''
# <?xml version="1.0" encoding="UTF-8"?>
# <metadata modelVersion="1.1">
# <groupId>${groupId}</groupId>
# <artifactId>${artifactId}</artifactId>
# <version>${version}</version>
# <versioning>
# <snapshot>
# ${optionalString (timestamp != "") "<timestamp>${timestamp}</timestamp>"}
# ${optionalString (buildNumber != -1) "<buildNumber>${toString buildNumber}</buildNumber>"}
# </snapshot>
# ${optionalString (lastUpdated != "") "<lastUpdated>${lastUpdated}</lastUpdated>"}
# <snapshotVersions>
# ${concatMapStringsSep "\n " mkSnapshotVersion versions}
# </snapshotVersions>
# </versioning>
# </metadata>
# ''
# ) modules);
mkRepo = name: deps: symlinkJoin {
name = "${name}-gradle-env";
# paths = map mkDep deps ++ mkModuleMetadata deps ++ mkSnapshotMetadata deps;
paths = mapAttrsToList mkModule deps;
};
mkInitScript = projectSpec: gradle:
let
repos = mapAttrs (mkRepo projectSpec.name) projectSpec.dependencies;
hasDependencies = mapAttrs (type: deps: deps != []) projectSpec.dependencies;
inSettings = pred: script:
optionalString pred (
if versionAtLeast gradle.version "6.0" then ''
gradle.beforeSettings {
${script}
}
'' else ''
gradle.settingsEvaluated {
${script}
}
''
);
in
assert (assertMsg (hasDependencies.settings -> versionAtLeast gradle.version "6.0") ''
Project `${projectSpec.name}' has settings script dependencies, such as settings
plugins, which are not supported by gradle2nix for Gradle versions prior to 6.0.
Potential remedies:
- Pass `--gradle-version=<version>' to the gradle2nix command.
- Patch the `settings.gradle[.kts]' file to remove script dependencies.
'');
writeText "init.gradle" ''
static def offlineRepo(RepositoryHandler repositories, String env, String path) {
repositories.clear()
repositories.maven {
name "Nix''${env.capitalize()}Offline"
url path
metadataSources {
it.gradleMetadata()
it.mavenPom()
it.
}
}
repositories.ivy {
name "Nix''${env.capitalize()}IvyOffline"
url path
layout "maven"
metadataSources {
it.gradleMetadata()
it.ivyDescriptor()
}
}
}
${inSettings (hasDependencies.settings && (versionAtLeast gradle.version "6.0")) ''
offlineRepo(it.buildscript.repositories, "settings", "${repos.settings}")
''}
${inSettings (hasDependencies.plugin) ''
offlineRepo(it.pluginManagement.repositories, "plugin", "${repos.plugin}")
''}
${optionalString (hasDependencies.buildscript) ''
gradle.projectsLoaded {
allprojects {
buildscript {
offlineRepo(repositories, "buildscript", "${repos.buildscript}")
}
}
}
''}
${optionalString (hasDependencies.project) (
if versionAtLeast gradle.version "6.8"
then ''
gradle.beforeSettings {
it.dependencyResolutionManagement {
offlineRepo(repositories, "project", "${repos.project}")
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
}
}
''
else ''
gradle.projectsLoaded {
allprojects {
offlineRepo(repositories, "project", "${repos.project}")
}
}
''
)}
${extraInit}
'';
mkGradle = gradleSpec:
gradleGen.gradleGen {
inherit (gradleSpec) nativeVersion;
name = "gradle-${gradleSpec.version}-${gradleSpec.type}";
src = fetchurl {
inherit (gradleSpec) url sha256;
};
} // {
inherit (gradleSpec) version;
};
mkProjectEnv = projectSpec: rec {
inherit (projectSpec) name path version;
gradle = args.gradlePackage or mkGradle projectSpec.gradle;
initScript = mkInitScript projectSpec gradle;
};
gradleEnv = mapAttrs
(_: p: mkProjectEnv p)
(fromJSON (readFile envSpec));
projectEnv = gradleEnv."";
pname = args.pname or projectEnv.name;
version = args.version or projectEnv.version;
buildProject = env: flags: ''
cp ${env.initScript} "$GRADLE_USER_HOME/init.d"
gradle --offline --no-daemon --no-build-cache \
--info --full-stacktrace --warning-mode=all \
${optionalString enableParallelBuilding "--parallel"} \
${optionalString enableDebug "-Dorg.gradle.debug=true"} \
${optionalString (buildJdk != null) "-Dorg.gradle.java.home=${buildJdk.home}"} \
--init-script ${env.initScript} \
${optionalString (env.path != "") ''-p "${env.path}"''} \
${concatStringsSep " " flags}
'';
buildIncludedProjects =
concatStringsSep "\n" (mapAttrsToList
(_: env: buildProject env [ "build" ])
(removeAttrs gradleEnv [ "" ]));
buildRootProject = buildProject projectEnv gradleFlags;
# in stdenv.mkDerivation (args // {
# inherit pname version;
# nativeBuildInputs = (args.nativeBuildInputs or []) ++ [ projectEnv.gradle ];
# buildPhase = args.buildPhase or ''
# runHook preBuild
# (
# set -eux
# # Work around https://github.com/gradle/gradle/issues/1055
# TMPHOME="$(mktemp -d)"
# mkdir -p "$TMPHOME/init.d"
# export GRADLE_USER_HOME="$TMPHOME"
# ${buildIncludedProjects}
# ${buildRootProject}
# )
# runHook postBuild
# '';
# dontStrip = true;
# })
in mkRepo pname (fromJSON (readFile envSpec))