{
  lib,
  stdenvNoCC,
  coreutils,
  fetchurl,
  substitute,
  symlinkJoin,
}:

{
  # Path to the lockfile generated by gradle2nix (e.g. gradle.lock).
  lockFile,
  # Override functions which fetch dependency artifacts.
  # Names 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 ? { },
  # Override artifacts in the offline Maven repository.
  #
  # This is an attrset is of the form:
  #
  #   {
  #     "${group}:${module}:${version}" = {
  #       "${filename}" = <override function>;
  #     }
  #   }
  #
  # The override function takes the original derivation from 'fetchers' (e.g. the result of
  # 'fetchurl') and produces a new derivation to replace it.
  #
  # Examples:
  #
  # 1. Replace a dependency's JAR artifact:
  #
  #    {
  #      "com.squareup.okio:okio:3.9.0"."okio-3.9.0.jar" = _: fetchurl {
  #        url = "https://repo.maven.apache.org/maven2/com/squareup/okio/okio/3.9.0/okio-3.9.0.jar";
  #        hash = "...";
  #        downloadToTemp = true;
  #        postFetch = "install -Dt $out/com/squareup/okio/okio/3.9.0/ $downloadedFile"
  #      };
  #    }
  #
  # 2. Patch a JAR containing native binaries:
  #
  #    {
  #      "com.android.tools.build:aapt2:8.5.0-rc02-11315950" = {
  #        "aapt2-8.5.0-rc02-11315950-linux.jar" = src: runCommandCC src.name {
  #          nativeBuildInputs = [ jdk autoPatchelfHook ];
  #          dontAutoPatchelf = true;
  #        } ''
  #          cp ${src} aapt2.jar
  #          jar xf aapt2.jar aapt2
  #          chmod +x aapt2
  #          autoPatchelf aapt2
  #          jar uf aapt2.jar aapt2
  #          cp aapt2.jar $out
  #        '';
  #      }
  #    }
  overrides ? { },
}:

let

  inherit (builtins)
    attrValues
    elemAt
    filter
    fromJSON
    getAttr
    head
    length
    mapAttrs
    replaceStrings
    ;

  inherit (lib) readFile;

  inherit (lib.strings) sanitizeDerivationName;

  toCoordinates =
    id:
    let
      coords = builtins.split ":" id;

      parseVersion =
        version:
        let
          parts = builtins.split ":" version;
          base = elemAt parts 0;
        in
        if length parts >= 2 then
          let
            snapshot = elemAt parts 2;
          in
          replaceStrings [ "-SNAPSHOT" ] [ "-${snapshot}" ] base
        else
          base;
    in
    rec {
      group = elemAt coords 0;
      module = elemAt coords 2;
      version = elemAt coords 4;
      uniqueVersion = parseVersion version;
    };

  fetchers' = {
    http = fetchurl;
    https = fetchurl;
  } // fetchers;

  fetch =
    overrides: path:
    { url, hash }:
    let
      scheme = head (builtins.match "([a-z0-9+.-]+)://.*" url);
      fetch' = getAttr scheme fetchers';
      artifact = fetch' { inherit url hash; };
      override = overrides.${path} or lib.id;
    in
    override artifact;

  mkModule =
    id: artifacts:
    let
      coords = toCoordinates id;
      modulePath = "${replaceStrings [ "." ] [ "/" ] coords.group}/${coords.module}/${coords.version}";
      moduleOverrides = overrides.${id} or { };
      fetchArtifact = fetch moduleOverrides;
    in
    stdenvNoCC.mkDerivation {
      pname = sanitizeDerivationName "${coords.group}-${coords.module}";
      version = coords.uniqueVersion;

      nativeBuildInputs = [ coreutils ];

      __structuredAttrs = true;

      srcs = mapAttrs fetchArtifact artifacts;

      dontPatch = true;
      dontConfigure = true;
      dontBuild = true;
      dontFixup = true;
      dontInstall = true;

      unpackPhase = ''
        for path in "''${!srcs[@]}"; do
          src="''${srcs[$path]}"
          dst="$(${coreutils}/bin/realpath -s -m --relative-base='.' "$out/${modulePath}/$path")"
          if [[ "$dst" == \\/* ]]; then
            echo "${id}: artifact path is invalid: $path"
            exit 1
          fi
          mkdir -p "$(dirname "$dst")"
          cp "$src" "$dst"
        done
      '';

      sourceRoot = ".";

      preferLocalBuild = true;
      allowSubstitutes = false;
    };

  modulePaths =
    let
      dependencies = fromJSON (readFile lockFile);
      modules = mapAttrs mkModule dependencies;
    in
    filter lib.isDerivation (attrValues modules);

  mavenRepo = symlinkJoin {
    name = "gradle-maven-repo";
    paths = modulePaths;
    passthru.gradleInitScript = substitute {
      src = ./init.gradle;
      substitutions = [
        "--replace"
        "@mavenRepo@"
        "${mavenRepo}"
      ];
    };
  };
in
mavenRepo
