{ mitm-cache , lib , pkgs , stdenv , callPackage }: let getPkg = attrPath: lib.getAttrFromPath (lib.splitString "." (toString attrPath)) pkgs; in # the derivation to fetch/update deps for { pkg ? getPkg attrPath , pname ? null , attrPath ? pname # bwrap flags for the update script (this will be put in bash as-is) # this is relevant for downstream users , bwrapFlags ? "--ro-bind \"$PWD\" \"$PWD\"" # deps path (relative to the package directory, or absolute) , data # redirect stdout to stderr to allow the update script to be used with update script combinators , silent ? true , useBwrap ? stdenv.hostPlatform.isLinux } @ attrs: let data' = builtins.removeAttrs (if builtins.isPath data then lib.importJSON data else if builtins.isString data then lib.importJSON "${dirOf pkg.meta.position}/${data}" else data) [ "!comment" "!version" ]; parseArtifactUrl = url: let extension = lib.last (lib.splitString "." url); splitUrl = lib.splitString "/" url; artifactId = builtins.elemAt splitUrl (builtins.length splitUrl - 3); baseVer = builtins.elemAt splitUrl (builtins.length splitUrl - 2); filename = builtins.elemAt splitUrl (builtins.length splitUrl - 1); filenameNoExt = lib.removeSuffix ".${extension}" filename; verCls = lib.removePrefix "${artifactId}-" filenameNoExt; in rec { inherit artifactId baseVer filename extension; isSnapshot = lib.hasSuffix "-SNAPSHOT" baseVer; version = if isSnapshot && !lib.hasPrefix "SNAPSHOT" verCls then builtins.concatStringsSep "-" (lib.take 3 (lib.splitString "-" verCls)) else baseVer; classifier = if verCls == version then null else lib.removePrefix "${version}-" verCls; # for snapshots timestamp = builtins.elemAt (lib.splitString "-" version) 1; buildNum = builtins.elemAt (lib.splitString "-" version) 2; }; parseMetadataUrl = url: let xmlBase = lib.removeSuffix "/maven-metadata.xml" url; vMeta = lib.hasSuffix "-SNAPSHOT" xmlBase; splitBase = lib.splitString "/" xmlBase; in if vMeta then { vMeta = true; baseVer = builtins.elemAt splitBase (builtins.length splitBase - 1); artifactId = builtins.elemAt splitBase (builtins.length splitBase - 2); } else { vMeta = false; baseVer = null; artifactId = builtins.elemAt splitBase (builtins.length splitBase - 1); }; extractHashArtifact = afterHash: let nameVer = builtins.match "([^/]*)/([^/]*)(/SNAPSHOT)?(/.*)?" afterHash; artifactId = builtins.elemAt nameVer 0; version = builtins.elemAt nameVer 1; isSnapshot = builtins.elemAt nameVer 2 != null; cls = builtins.elemAt nameVer 3; in rec { inherit artifactId version isSnapshot; baseVer = if !isSnapshot then version else builtins.head (builtins.match "(.*)-([^-]*)-([^-]*)" version) + "-SNAPSHOT"; classifier = if cls == null then null else lib.removePrefix "/" cls; clsSuf = if classifier == null then "" else "-${classifier}"; }; # replace base#name/ver with base/name/ver/name-ver decompressNameVer = prefix: let splitHash = lib.splitString "#" (builtins.concatStringsSep "/" prefix); inherit (extractHashArtifact (lib.last splitHash)) artifactId baseVer version clsSuf; in if builtins.length splitHash == 1 then builtins.head splitHash else builtins.concatStringsSep "/${artifactId}/${baseVer}/" (lib.init splitHash ++ [ "${artifactId}-${version}${clsSuf}" ]); # `visit` all elements in attrs and merge into a set # attrs will be passed as parent1, parent1 will be passed as parent2 visitAttrs = parent1: prefix: attrs: builtins.foldl' (a: b: a // b) {} (lib.mapAttrsToList (visit parent1 attrs prefix) attrs); # convert a compressed deps.json into an uncompressed json used for mitm-cache.fetch visit = parent2: parent1: prefix: k: v: # groupId being present means this is a metadata xml "leaf" and we shouldn't descend further if builtins.isAttrs v && !v?groupId then visitAttrs parent1 (prefix ++ [k]) v else let url = "${decompressNameVer prefix}.${k}"; in { ${url} = if builtins.isString v then { hash = v; } else { text = let xmlBase = lib.removeSuffix "/maven-metadata.xml" url; meta = parseMetadataUrl url // v; inherit (meta) groupId vMeta artifactId baseVer; fileList = builtins.filter (x: lib.hasPrefix xmlBase x && x != url) (builtins.attrNames finalData); jarPomList = map parseArtifactUrl fileList; sortByVersion = a: b: (builtins.compareVersions a.version b.version) < 0; sortedJarPomList = lib.sort sortByVersion jarPomList; uniqueVersionFiles = builtins.map ({ i, x }: x) (builtins.filter ({ i, x }: i == 0 || (builtins.elemAt sortedJarPomList (i - 1)).version != x.version) (lib.imap0 (i: x: { inherit i x; }) sortedJarPomList)); uniqueVersions' = map (x: x.version) uniqueVersionFiles; releaseVersions = map (x: x.version) (builtins.filter (x: !x.isSnapshot) uniqueVersionFiles); latestVer = v.latest or v.release or (lib.last uniqueVersions'); releaseVer = v.release or (lib.last releaseVersions); # The very latest version isn't necessarily used by Gradle, so it may not be present in the MITM data. # In order to generate better metadata xml, if the latest version is known but wasn't fetched by Gradle, # add it anyway. uniqueVersions = uniqueVersions' ++ lib.optional (!builtins.elem releaseVer uniqueVersions') releaseVer ++ lib.optional (!builtins.elem latestVer uniqueVersions' && releaseVer != latestVer) latestVer; lastUpdated = v.lastUpdated or (if vMeta then builtins.replaceStrings ["."] [""] snapshotTs else "20240101123456"); # the following are only used for snapshots snapshotTsAndNum = lib.splitString "-" latestVer; snapshotTs = builtins.elemAt snapshotTsAndNum 1; snapshotNum = lib.last snapshotTsAndNum; indent = x: s: builtins.concatStringsSep "\n" (map (s: x + s) (lib.splitString "\n" s)); containsSpecialXmlChars = s: builtins.match ''.*[<>"'&].*'' s != null; in # make sure all user-provided data is safe assert lib.hasInfix "${builtins.replaceStrings ["."] ["/"] groupId}/${artifactId}" url; assert !containsSpecialXmlChars groupId; assert !containsSpecialXmlChars lastUpdated; if vMeta then '' ${groupId} ${artifactId} ${baseVer} ${snapshotTs} ${snapshotNum} ${lastUpdated} ${builtins.concatStringsSep "\n" (map (x: indent " " '' ${ lib.optionalString (x.classifier != null) "\n ${x.classifier}" } ${x.extension} ${x.version} ${builtins.replaceStrings ["."] [""] x.timestamp} '') sortedJarPomList)} '' else assert !containsSpecialXmlChars latestVer; assert !containsSpecialXmlChars releaseVer; '' ${groupId} ${artifactId} ${latestVer} ${releaseVer} ${builtins.concatStringsSep "\n" (map (x: " ${x}") uniqueVersions)} ${lastUpdated} ''; }; }; finalData = visitAttrs {} [] data'; in mitm-cache.fetch { name = "${pkg.pname or pkg.name}-deps"; data = finalData // { "!version" = 1; }; passthru = lib.optionalAttrs (!builtins.isAttrs data) { updateScript = callPackage ./update-deps.nix { } { inherit pkg pname attrPath bwrapFlags data silent useBwrap; }; }; }