{ lib , julia , python3 , runCommand , augmentedRegistry , packageNames , packageOverrides , packageImplications }: let # The specific package resolution code depends on the Julia version # These are pretty similar and could be combined to reduce duplication resolveCode = if lib.versionOlder julia.version "1.7" then resolveCode1_6 else resolveCode1_8; resolveCode1_6 = '' import Pkg.API: check_package_name import Pkg.Types: Context!, PRESERVE_NONE, manifest_info, project_deps_resolve!, registry_resolve!, stdlib_resolve!, ensure_resolved import Pkg.Operations: _resolve, assert_can_add, is_dep, update_package_add foreach(pkg -> check_package_name(pkg.name, :add), pkgs) pkgs = deepcopy(pkgs) # deepcopy for avoid mutating PackageSpec members Context!(ctx) project_deps_resolve!(ctx, pkgs) registry_resolve!(ctx, pkgs) stdlib_resolve!(pkgs) ensure_resolved(ctx, pkgs, registry=true) assert_can_add(ctx, pkgs) for (i, pkg) in pairs(pkgs) entry = manifest_info(ctx, pkg.uuid) pkgs[i] = update_package_add(ctx, pkg, entry, is_dep(ctx, pkg)) end foreach(pkg -> ctx.env.project.deps[pkg.name] = pkg.uuid, pkgs) pkgs, deps_map = _resolve(ctx, pkgs, PRESERVE_NONE) ''; resolveCode1_8 = '' import Pkg.API: handle_package_input! import Pkg.Types: PRESERVE_NONE, UUID, VersionSpec, project_deps_resolve!, registry_resolve!, stdlib_resolve!, ensure_resolved import Pkg.Operations: _resolve, assert_can_add, update_package_add import TOML foreach(handle_package_input!, pkgs) # The handle_package_input! call above clears pkg.path, so we have to apply package overrides after overrides = Dict{String, String}(${builtins.concatStringsSep ", " (lib.mapAttrsToList (name: path: ''"${name}" => "${path}"'') packageOverrides)}) println("Package overrides: ") println(overrides) for pkg in pkgs if pkg.name in keys(overrides) pkg.path = overrides[pkg.name] # Try to read the UUID from $(pkg.path)/Project.toml. If successful, put the package into ctx.env.project.deps. # This is necessary for the ensure_resolved call below to succeed, and will allow us to use an override even # if it does not appear in the registry. # See https://github.com/NixOS/nixpkgs/issues/279853 project_toml = joinpath(pkg.path, "Project.toml") if isfile(project_toml) toml_data = TOML.parsefile(project_toml) if haskey(toml_data, "uuid") ctx.env.project.deps[pkg.name] = UUID(toml_data["uuid"]) end end end end project_deps_resolve!(ctx.env, pkgs) registry_resolve!(ctx.registries, pkgs) stdlib_resolve!(pkgs) ensure_resolved(ctx, ctx.env.manifest, pkgs, registry=true) assert_can_add(ctx, pkgs) for (i, pkg) in pairs(pkgs) entry = Pkg.Types.manifest_info(ctx.env.manifest, pkg.uuid) is_dep = any(uuid -> uuid == pkg.uuid, [uuid for (name, uuid) in ctx.env.project.deps]) pkgs[i] = update_package_add(ctx, pkg, entry, is_dep) end foreach(pkg -> ctx.env.project.deps[pkg.name] = pkg.uuid, pkgs) # Save the original pkgs for later. We might need to augment it with the weak dependencies orig_pkgs = pkgs pkgs, deps_map = _resolve(ctx.io, ctx.env, ctx.registries, pkgs, PRESERVE_NONE, ctx.julia_version) if VERSION >= VersionNumber("1.9") while true # Check for weak dependencies, which appear on the RHS of the deps_map but not in pkgs. # Build up weak_name_to_uuid uuid_to_name = Dict() for pkg in pkgs uuid_to_name[pkg.uuid] = pkg.name end weak_name_to_uuid = Dict() for (uuid, deps) in pairs(deps_map) for (dep_name, dep_uuid) in pairs(deps) if !haskey(uuid_to_name, dep_uuid) weak_name_to_uuid[dep_name] = dep_uuid end end end if isempty(weak_name_to_uuid) break end # We have nontrivial weak dependencies, so add each one to the initial pkgs and then re-run _resolve println("Found weak dependencies: $(keys(weak_name_to_uuid))") orig_uuids = Set([pkg.uuid for pkg in orig_pkgs]) for (name, uuid) in pairs(weak_name_to_uuid) if uuid in orig_uuids continue end pkg = PackageSpec(name, uuid) push!(orig_uuids, uuid) push!(orig_pkgs, pkg) ctx.env.project.deps[name] = uuid entry = Pkg.Types.manifest_info(ctx.env.manifest, uuid) orig_pkgs[length(orig_pkgs)] = update_package_add(ctx, pkg, entry, false) end global pkgs, deps_map = _resolve(ctx.io, ctx.env, ctx.registries, orig_pkgs, PRESERVE_NONE, ctx.julia_version) end end ''; juliaExpression = packageNames: '' import Pkg Pkg.Registry.add(Pkg.RegistrySpec(path="${augmentedRegistry}")) import Pkg.Types: Context, PackageSpec input = ${lib.generators.toJSON {} packageNames} if isfile("extra_package_names.txt") append!(input, readlines("extra_package_names.txt")) end input = unique(input) println("Resolving packages: " * join(input, " ")) pkgs = [PackageSpec(pkg) for pkg in input] ctx = Context() ${resolveCode} open(ENV["out"], "w") do io for spec in pkgs println(io, "- name: " * spec.name) println(io, " uuid: " * string(spec.uuid)) println(io, " version: " * string(spec.version)) if endswith(spec.name, "_jll") && haskey(deps_map, spec.uuid) println(io, " depends_on: ") for (dep_name, dep_uuid) in pairs(deps_map[spec.uuid]) println(io, " \"$(dep_name)\": \"$(dep_uuid)\"") end end end end ''; in runCommand "julia-package-closure.yml" { buildInputs = [julia (python3.withPackages (ps: with ps; [pyyaml]))]; } '' mkdir home export HOME=$(pwd)/home echo "Resolving Julia packages with the following inputs" echo "Julia: ${julia}" echo "Registry: ${augmentedRegistry}" # Prevent a warning where Julia tries to download package server info export JULIA_PKG_SERVER="" julia -e '${juliaExpression packageNames}'; # See if we need to add any extra package names based on the closure # and the packageImplications python ${./python}/find_package_implications.py "$out" '${lib.generators.toJSON {} packageImplications}' extra_package_names.txt if [ -f extra_package_names.txt ]; then echo "Re-resolving with additional package names" julia -e '${juliaExpression packageNames}'; fi ''