Dogfood v2

This commit is contained in:
Tad Fisher
2024-05-17 17:50:06 -07:00
parent 8d2ec45ad4
commit f24e295fc0
11 changed files with 3992 additions and 499 deletions

View File

@@ -21,7 +21,6 @@ dependencies {
isTransitive = false isTransitive = false
} }
//testRuntimeOnly(kotlin("reflect"))
testImplementation(libs.kotest.assertions) testImplementation(libs.kotest.assertions)
testImplementation(libs.kotest.runner) testImplementation(libs.kotest.runner)
testImplementation(libs.ktor.server.core) testImplementation(libs.ktor.server.core)

292
app/src/dist/share/gradle.nix vendored Normal file
View File

@@ -0,0 +1,292 @@
# This file is generated by gradle2nix.
#
# Example usage (e.g. in default.nix):
#
# with (import <nixpkgs> {});
# let
# buildGradle = callPackage ./gradle.nix {};
# in
# buildGradle {
# lockFile = ./gradle.lock;
#
# src = ./.;
#
# gradleFlags = [ "installDist" ];
#
# installPhase = ''
# mkdir -p $out
# cp -r app/build/install/myproject $out
# '';
# }
{ lib
, stdenv
, buildEnv
, fetchs3
, fetchurl
, gradle
, maven
, runCommandLocal
, symlinkJoin
, writeText
, writeTextDir
}:
{
# Path to the lockfile generated by gradle2nix (e.g. gradle.lock).
lockFile
, pname ? "project"
, version ? null
, enableParallelBuilding ? true
# Arguments to Gradle used to build the project in buildPhase.
, gradleFlags ? [ "build" ]
# 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, hash }` 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 hasAttr head length match
removeAttrs replaceStrings sort;
inherit (lib)
assertMsg concatMapStringsSep findFirst foldl' groupBy' hasSuffix hasPrefix last mapAttrs
mapAttrsToList optionalAttrs optionalString readFile removeSuffix unique versionAtLeast
versionOlder;
inherit (lib.strings) sanitizeDerivationName;
lockedDeps = fromJSON (readFile lockFile);
toCoordinates = id:
let
coords = builtins.split ":" id;
in rec {
group = elemAt coords 0;
module = elemAt coords 2;
version = elemAt coords 4;
versionParts = parseVersion version;
};
parseVersion = version:
let
parts = builtins.split ":" version;
base = elemAt parts 0;
in
{
inherit base;
exact = base;
}
// optionalAttrs (length parts >= 2) (
let
snapshot = elemAt parts 2;
exact = replaceStrings [ "-SNAPSHOT" ] [ "-${snapshot}" ] base;
parts = builtins.split "-" timestamp;
timestamp = findFirst (match "[0-9]{8}\.[0-9]{6}") parts;
buildNumber = let lastPart = last parts; in if match "[0-9]+" lastPart then lastPart else null;
in
{ inherit snapshot exact timestamp buildNumber; }
);
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 = name: { 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; };
mkModule = id: artifacts:
let
coords = toCoordinates id;
modulePath = "${replaceStrings ["."] ["/"] coords.group}/${coords.module}/${coords.version}";
in
stdenv.mkDerivation {
pname = sanitizeDerivationName "${coords.group}-${coords.module}";
version = coords.versionParts.exact;
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;
};
offlineRepo = symlinkJoin {
name = if version != null then "${pname}-${version}-gradle-repo" else "${pname}-gradle-repo";
paths = mapAttrsToList mkModule lockedDeps;
};
initScript =
let
inSettings = pred: script:
optionalString pred (
if versionAtLeast gradle.version "6.0" then ''
gradle.beforeSettings {
${script}
}
'' else ''
gradle.settingsEvaluated {
${script}
}
''
);
in
writeText "init.gradle" ''
static def offlineRepo(RepositoryHandler repositories) {
repositories.clear()
repositories.mavenLocal {
url 'file:${offlineRepo}'
metadataSources {
gradleMetadata()
mavenPom()
artifact()
}
}
}
${inSettings (versionAtLeast gradle.version "6.0") ''
offlineRepo(it.buildscript.repositories)
''}
${inSettings true ''
offlineRepo(it.pluginManagement.repositories)
''}
gradle.projectsLoaded {
allprojects {
buildscript {
offlineRepo(repositories)
}
}
}
${if versionAtLeast gradle.version "6.8"
then ''
gradle.beforeSettings {
it.dependencyResolutionManagement {
offlineRepo(repositories)
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
}
}
''
else ''
gradle.projectsLoaded {
allprojects {
offlineRepo(repositories)
}
}
''
}
${extraInit}
'';
buildProject = flags: ''
gradle --offline --no-daemon --no-build-cache \
--info --full-stacktrace --warning-mode=all \
--no-configuration-cache \
-Dmaven.repo.local=${offlineRepo} \
${optionalString enableParallelBuilding "--parallel"} \
${optionalString enableDebug "-Dorg.gradle.debug=true"} \
${optionalString (buildJdk != null) "-Dorg.gradle.java.home=${buildJdk.home}"} \
--init-script ${initScript} \
${concatStringsSep " " flags}
'';
in stdenv.mkDerivation ({
dontStrip = true;
nativeBuildInputs = (args.nativeBuildInputs or []) ++ [ gradle ];
buildPhase = args.buildPhase or ''
runHook preBuild
(
set -eux
${optionalString (versionOlder gradle.version "8.0") ''
# Work around https://github.com/gradle/gradle/issues/1055
TMPHOME="$(mktemp -d)"
mkdir -p "$TMPHOME/init.d"
export GRADLE_USER_HOME="$TMPHOME"
cp ${initScript} $TMPHOME/
''}
gradle --offline --no-daemon --no-build-cache \
--info --full-stacktrace --warning-mode=all \
--no-configuration-cache --console=plain \
-Dmaven.repo.local=${offlineRepo} \
${optionalString enableParallelBuilding "--parallel"} \
${optionalString enableDebug "-Dorg.gradle.debug=true"} \
${optionalString (buildJdk != null) "-Dorg.gradle.java.home=${buildJdk.home}"} \
--init-script ${initScript} \
${concatStringsSep " " gradleFlags}
)
runHook postBuild
'';
passthru = (args.passthru or {}) // {
inherit offlineRepo;
};
} // (removeAttrs args [
"nativeBuildInputs"
"passthru"
"lockFile"
"gradleFlags"
"gradle"
"enableDebug"
"extraInit"
"buildJdk"
"fetchers"
]))

View File

@@ -2,7 +2,7 @@ package org.nixos.gradle2nix
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.context import com.github.ajalt.clikt.core.context
import com.github.ajalt.clikt.output.CliktHelpFormatter import com.github.ajalt.clikt.output.MordantHelpFormatter
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.default
@@ -10,7 +10,6 @@ import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.multiple import com.github.ajalt.clikt.parameters.options.multiple
import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.validate import com.github.ajalt.clikt.parameters.options.validate
import com.github.ajalt.clikt.parameters.types.choice
import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.clikt.parameters.types.enum
import com.github.ajalt.clikt.parameters.types.file import com.github.ajalt.clikt.parameters.types.file
import java.io.File import java.io.File
@@ -69,14 +68,20 @@ class Gradle2Nix : CliktCommand(
help = "Path to write generated files (default: PROJECT-DIR)") help = "Path to write generated files (default: PROJECT-DIR)")
.file(canBeFile = false, canBeDir = true) .file(canBeFile = false, canBeDir = true)
val envFile: String by option( val lockFile: String by option(
"--env", "-e", "--lock-file", "-l",
metavar = "FILENAME", metavar = "FILENAME",
help = "Prefix for environment files (.json and .nix)") help = "Name of the generated lock file"
.default("gradle-env") ).default("gradle.lock")
val nixFile: String by option(
"--nix-file", "-n",
metavar = "FILENAME",
help = "Name of the generated Nix file"
).default("gradle.nix")
private val logLevel: LogLevel by option( private val logLevel: LogLevel by option(
"--log", "-l", "--log",
metavar = "LEVEL", metavar = "LEVEL",
help = "Print messages with priority of at least LEVEL") help = "Print messages with priority of at least LEVEL")
.enum<LogLevel>() .enum<LogLevel>()
@@ -118,22 +123,20 @@ class Gradle2Nix : CliktCommand(
init { init {
context { context {
helpFormatter = CliktHelpFormatter(showDefaultValues = true) helpFormatter = { MordantHelpFormatter(it, showDefaultValues = true) }
} }
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
override fun run() { override fun run() {
val appHome = System.getProperty("org.nixos.gradle2nix.share")
if (appHome == null) {
System.err.println("Error: could not locate the /share directory in the gradle2nix installation")
}
val gradleHome =
System.getenv("GRADLE_USER_HOME")?.let(::File) ?: File("${System.getProperty("user.home")}/.gradle")
val logger = Logger(logLevel = logLevel, stacktrace = stacktrace) val logger = Logger(logLevel = logLevel, stacktrace = stacktrace)
val appHome = System.getProperty("org.nixos.gradle2nix.share")?.let(::File)
?: error("could not locate the /share directory in the gradle2nix installation")
val gradleHome =
System.getenv("GRADLE_USER_HOME")?.let(::File) ?: File("${System.getProperty("user.home")}/.gradle")
val config = Config( val config = Config(
File(appHome), appHome,
gradleHome, gradleHome,
gradleVersion, gradleVersion,
gradleJdk, gradleJdk,
@@ -156,6 +159,8 @@ class Gradle2Nix : CliktCommand(
} else { } else {
metadata.deleteOnExit() metadata.deleteOnExit()
} }
} else {
metadata.deleteOnExit()
} }
val buildSrcs = connect(config).use { connection -> val buildSrcs = connect(config).use { connection ->
@@ -187,11 +192,19 @@ class Gradle2Nix : CliktCommand(
logger.error("dependency parsing failed", e) logger.error("dependency parsing failed", e)
} }
val json = config.outDir.resolve("$envFile.json") config.outDir.mkdirs()
logger.info("Writing environment to $json")
json.outputStream().buffered().use { output -> val outLockFile = config.outDir.resolve(lockFile)
logger.info("Writing lock file to $outLockFile")
outLockFile.outputStream().buffered().use { output ->
JsonFormat.encodeToStream(env, output) JsonFormat.encodeToStream(env, output)
} }
val inNixFile = config.appHome.resolve("gradle.nix").takeIf { it.exists() }
?: error("Couldn't locate gradle.nix in the the gradle2nix installation: ${config.appHome}")
val outNixFile = config.outDir.resolve(nixFile)
logger.info("Writing Nix builder to $outNixFile")
inNixFile.copyTo(outNixFile, overwrite = true)
} }
} }

View File

@@ -3,10 +3,12 @@
with pkgs; with pkgs;
let let
buildGradle = callPackage ./gradle-env.nix {}; buildGradle = callPackage ./gradle.nix {};
gradle2nix = buildGradle { gradle2nix = buildGradle {
envSpec = ./gradle-env.json; pname = "gradle2nix";
version = "2.0.0";
lockFile = ./gradle.lock;
src = lib.cleanSourceWith { src = lib.cleanSourceWith {
filter = lib.cleanSourceFilter; filter = lib.cleanSourceFilter;
@@ -35,7 +37,4 @@ let
}; };
}; };
in buildGradle { in gradle2nix
envSpec = ./gradle-env.json;
pname = "gradle2nix";
}

27
flake.lock generated
View File

@@ -1,15 +1,29 @@
{ {
"nodes": { "nodes": {
"flake-compat": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1701680307, "lastModified": 1710146030,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -20,11 +34,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1702855317, "lastModified": 1715954240,
"narHash": "sha256-5EXeUkoWvrfbZQQLVRn7Ebb9LOt3DkVm6T0M31/VhtM=", "narHash": "sha256-eC0zRYkazBc2cKz1VSzMLvCc9uHXlSQEr1Be+c5UV1w=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "3a16c6447466f4034c2d75fe7014477142c9513e", "rev": "c2ea18b50a4c7fcb7c90402166769bf3b82b5aa9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -36,6 +50,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }

View File

@@ -2,20 +2,25 @@
description = "Wrap Gradle builds with Nix"; description = "Wrap Gradle builds with Nix";
inputs = { inputs = {
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
}; };
outputs = { self, flake-utils, nixpkgs }: outputs = { self, flake-utils, nixpkgs, ... }:
flake-utils.lib.eachDefaultSystem (system: flake-utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages.${system}; let
in rec { pkgs = nixpkgs.legacyPackages.${system};
packages.gradle2nix = import ./default.nix { inherit pkgs; }; buildGradle = pkgs.callPackage ./gradle.nix {};
defaultPackage = packages.gradle2nix;
in {
packages.default = pkgs.callPackage ./gradle2nix.nix {
inherit buildGradle;
};
apps.default = { apps.default = {
type = "app"; type = "app";
program = "${packages.gradle2nix}/bin/gradle2nix"; program = "${self.packages.gradle2nix}/bin/gradle2nix";
}; };
}); });
} }

View File

@@ -1,453 +0,0 @@
# 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
, maven
, 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 hasAttr head length match
replaceStrings sort;
inherit (lib)
assertMsg concatMapStringsSep foldl' groupBy' hasSuffix hasPrefix last mapAttrs
mapAttrsToList optionalAttrs 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;
};
parseVersion = version:
let
parts = builtins.split ":" version;
base = elemAt parts 0;
in
{
inherit base;
exact = base;
}
// optionalAttrs (length parts >= 2) (
let
snapshot = elemAt parts 2;
exact = replaceStrings [ "-SNAPSHOT" ] [ "-${snapshot}" ] base;
parts = builtins.split "-" timestamp;
timestamp = findFirst (match "[0-9]{8}\.[0-9]{6}") parts;
buildNumber = let lastPart = last parts; in if match "[0-9]+" lastPart then lastPart else null;
in
{ inherit snapshot exact timestamp buildNumber; }
);
snapshotVersion = { revision, snapshot ? null, ... }:
if snapshot == null
then revision
else replaceStrings ["SNAPSHOT"] [snapshot] revision;
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; };
mkModuleMetadata = deps:
let
metadata = group: module: versions:
let
latest = foldl'
(l: v: if l == null || versionOlder l v then v else l)
null
versions;
release = foldl'
(l: v: if !(hasSuffix "-SNAPSHOT" v) && (l == null || versionOlder l v) then v else l)
null
versions;
path = "${replaceStrings ["."] ["/"] group}/${module}/maven-metadata.xml";
in
writeTextDir path ''
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1">
<groupId>${group}</groupId>
<artifactId>${module}</artifactId>
<versioning>
${optionalString (latest != null) "<latest>${latest}</latest>"}
${optionalString (release != null) "<release>${release}</release>"}
<versions>
${concatMapStringsSep "\n " (v: "<version>${v}</version>") versions}
</versions>
</versioning>
</metadata>
'';
groupedModules = groupBy
({ organisation, module, ... }: "${organisation}:${module}")
(mapAttrsToList (_: dep: dep.attrs) deps);
in
map
# mkSnapshotMetadata = { group, module }: version: artifacts:
# let
# versions' = filter (hasAttr "timestamp") versions;
# in
# map ({ base, exact, snapshot, timestamp, buildNumber }:
# ) versions';
mkModule = { attrs, artifacts }:
let
modulePath = "${replaceStrings ["."] ["/"] attrs.orgPath}/${attrs.module}/${attrs.revision}";
in
stdenv.mkDerivation {
pname = "${attrs.group}-${attrs.module}";
version = snapshotVersion attrs.revision;
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;
};
mkModules = deps:
mapAttrsToList (_: m: mkModule m) deps;
# 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-repo";
paths = mkModules deps ++ mkModuleMetadata deps ++ mkSnapshotMetadata 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))

3292
gradle.lock Normal file

File diff suppressed because it is too large Load Diff

292
gradle.nix Normal file
View File

@@ -0,0 +1,292 @@
# This file is generated by gradle2nix.
#
# Example usage (e.g. in default.nix):
#
# with (import <nixpkgs> {});
# let
# buildGradle = callPackage ./gradle.nix {};
# in
# buildGradle {
# lockFile = ./gradle.lock;
#
# src = ./.;
#
# gradleFlags = [ "installDist" ];
#
# installPhase = ''
# mkdir -p $out
# cp -r app/build/install/myproject $out
# '';
# }
{ lib
, stdenv
, buildEnv
, fetchs3
, fetchurl
, gradle
, maven
, runCommandLocal
, symlinkJoin
, writeText
, writeTextDir
}:
{
# Path to the lockfile generated by gradle2nix (e.g. gradle.lock).
lockFile
, pname ? "project"
, version ? null
, enableParallelBuilding ? true
# Arguments to Gradle used to build the project in buildPhase.
, gradleFlags ? [ "build" ]
# 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, hash }` 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 hasAttr head length match
removeAttrs replaceStrings sort;
inherit (lib)
assertMsg concatMapStringsSep findFirst foldl' groupBy' hasSuffix hasPrefix last mapAttrs
mapAttrsToList optionalAttrs optionalString readFile removeSuffix unique versionAtLeast
versionOlder;
inherit (lib.strings) sanitizeDerivationName;
lockedDeps = fromJSON (readFile lockFile);
toCoordinates = id:
let
coords = builtins.split ":" id;
in rec {
group = elemAt coords 0;
module = elemAt coords 2;
version = elemAt coords 4;
versionParts = parseVersion version;
};
parseVersion = version:
let
parts = builtins.split ":" version;
base = elemAt parts 0;
in
{
inherit base;
exact = base;
}
// optionalAttrs (length parts >= 2) (
let
snapshot = elemAt parts 2;
exact = replaceStrings [ "-SNAPSHOT" ] [ "-${snapshot}" ] base;
parts = builtins.split "-" timestamp;
timestamp = findFirst (match "[0-9]{8}\.[0-9]{6}") parts;
buildNumber = let lastPart = last parts; in if match "[0-9]+" lastPart then lastPart else null;
in
{ inherit snapshot exact timestamp buildNumber; }
);
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 = name: { 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; };
mkModule = id: artifacts:
let
coords = toCoordinates id;
modulePath = "${replaceStrings ["."] ["/"] coords.group}/${coords.module}/${coords.version}";
in
stdenv.mkDerivation {
pname = sanitizeDerivationName "${coords.group}-${coords.module}";
version = coords.versionParts.exact;
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;
};
offlineRepo = symlinkJoin {
name = if version != null then "${pname}-${version}-gradle-repo" else "${pname}-gradle-repo";
paths = mapAttrsToList mkModule lockedDeps;
};
initScript =
let
inSettings = pred: script:
optionalString pred (
if versionAtLeast gradle.version "6.0" then ''
gradle.beforeSettings {
${script}
}
'' else ''
gradle.settingsEvaluated {
${script}
}
''
);
in
writeText "init.gradle" ''
static def offlineRepo(RepositoryHandler repositories) {
repositories.clear()
repositories.mavenLocal {
url 'file:${offlineRepo}'
metadataSources {
gradleMetadata()
mavenPom()
artifact()
}
}
}
${inSettings (versionAtLeast gradle.version "6.0") ''
offlineRepo(it.buildscript.repositories)
''}
${inSettings true ''
offlineRepo(it.pluginManagement.repositories)
''}
gradle.projectsLoaded {
allprojects {
buildscript {
offlineRepo(repositories)
}
}
}
${if versionAtLeast gradle.version "6.8"
then ''
gradle.beforeSettings {
it.dependencyResolutionManagement {
offlineRepo(repositories)
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
}
}
''
else ''
gradle.projectsLoaded {
allprojects {
offlineRepo(repositories)
}
}
''
}
${extraInit}
'';
buildProject = flags: ''
gradle --offline --no-daemon --no-build-cache \
--info --full-stacktrace --warning-mode=all \
--no-configuration-cache \
-Dmaven.repo.local=${offlineRepo} \
${optionalString enableParallelBuilding "--parallel"} \
${optionalString enableDebug "-Dorg.gradle.debug=true"} \
${optionalString (buildJdk != null) "-Dorg.gradle.java.home=${buildJdk.home}"} \
--init-script ${initScript} \
${concatStringsSep " " flags}
'';
in stdenv.mkDerivation ({
dontStrip = true;
nativeBuildInputs = (args.nativeBuildInputs or []) ++ [ gradle ];
buildPhase = args.buildPhase or ''
runHook preBuild
(
set -eux
${optionalString (versionOlder gradle.version "8.0") ''
# Work around https://github.com/gradle/gradle/issues/1055
TMPHOME="$(mktemp -d)"
mkdir -p "$TMPHOME/init.d"
export GRADLE_USER_HOME="$TMPHOME"
cp ${initScript} $TMPHOME/
''}
gradle --offline --no-daemon --no-build-cache \
--info --full-stacktrace --warning-mode=all \
--no-configuration-cache --console=plain \
-Dmaven.repo.local=${offlineRepo} \
${optionalString enableParallelBuilding "--parallel"} \
${optionalString enableDebug "-Dorg.gradle.debug=true"} \
${optionalString (buildJdk != null) "-Dorg.gradle.java.home=${buildJdk.home}"} \
--init-script ${initScript} \
${concatStringsSep " " gradleFlags}
)
runHook postBuild
'';
passthru = (args.passthru or {}) // {
inherit offlineRepo;
};
} // (removeAttrs args [
"nativeBuildInputs"
"passthru"
"lockFile"
"gradleFlags"
"gradle"
"enableDebug"
"extraInit"
"buildJdk"
"fetchers"
]))

View File

@@ -1,21 +1,19 @@
[versions] [versions]
gradle = "8.7" gradle = "8.7"
junit = "5.8.2"
kotlin = "1.9.22" kotlin = "1.9.22"
ktor = "2.3.11" ktor = "2.3.11"
kotest = "5.9.0"
[libraries] [libraries]
clikt = "com.github.ajalt:clikt:+" clikt = "com.github.ajalt.clikt:clikt:4.4.0"
gradle-toolingApi = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle" } gradle-toolingApi = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle" }
junit-jupiter-api = "org.junit.jupiter:junit-jupiter-api:+" kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
junit-jupiter-engine = "org.junit.jupiter:junit-jupiter-engine:+" kotest-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
junit-jupiter-params = "org.junit.jupiter:junit-jupiter-params:+" kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
junit-platformLauncher = "org.junit.platform:junit-platform-launcher:+"
kotest-runner = "io.kotest:kotest-runner-junit5:+"
kotest-assertions = "io.kotest:kotest-assertions-core:+"
kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:+"
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
okio = "com.squareup.okio:okio:+" okio = "com.squareup.okio:okio:3.9.0"
serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
slf4j-api = "org.slf4j:slf4j-api:+" slf4j-api = "org.slf4j:slf4j-api:+"
slf4j-simple = "org.slf4j:slf4j-simple:+" slf4j-simple = "org.slf4j:slf4j-simple:+"

41
gradle2nix.nix Normal file
View File

@@ -0,0 +1,41 @@
{ lib
, gradle
, buildGradle
}:
buildGradle {
pname = "gradle2nix";
version = "2.0.0";
lockFile = ./gradle.lock;
src = lib.cleanSourceWith {
filter = lib.cleanSourceFilter;
src = lib.cleanSourceWith {
filter = path: type: let baseName = builtins.baseNameOf path; in !(
(type == "directory" && (
baseName == "build" ||
baseName == ".idea" ||
baseName == ".gradle"
)) ||
(lib.hasSuffix ".iml" baseName)
);
src = ./.;
};
};
gradleFlags = [ "installDist" ];
installPhase = ''
mkdir -p $out
cp -r app/build/install/gradle2nix/* $out/
'';
meta = with lib; {
inherit (gradle.meta) platforms;
description = "Wrap Gradle builds with Nix";
homepage = "https://github.com/tadfisher/gradle2nix";
license = licenses.asl20;
maintainers = with maintainers; [ tadfisher ];
mainProgram = "gradle2nix";
};
}