Skip to content

Instantly share code, notes, and snippets.

@mtfishman
Last active February 11, 2025 23:49
Show Gist options
  • Save mtfishman/c8792a3604dc958e9cdccd65ce4f624e to your computer and use it in GitHub Desktop.
Save mtfishman/c8792a3604dc958e9cdccd65ce4f624e to your computer and use it in GitHub Desktop.
Automatically update the IntegrationTest.yml workflow
using RegistryInstances: RegistryInstances
function uuid_from_pkgname(
pkgname::String; registries=RegistryInstances.reachable_registries()
)
uuid_to_registrynames = Dict{Base.UUID,Vector{String}}()
for registry in registries
registry_uuids = RegistryInstances.uuids_from_name(registry, pkgname)
if !isempty(registry_uuids)
# There should only be one package with a given UUID in a given
# registry.
registry_uuid = only(registry_uuids)
if !haskey(uuid_to_registrynames, registry_uuid)
uuid_to_registrynames[registry_uuid] = [registry.name]
else
push!(uuid_to_registrynames[registry_uuid], registry.name)
end
end
end
if !isone(length(uuid_to_registrynames))
# Multiple UUIDs exist across different registries.
# Prefer the version in the general registry.
for (uuid, registrynames) in pairs(uuid_to_registrynames)
if "General" ∈ registrynames
return uuid
end
end
end
return only(keys(uuid_to_registrynames))
end
function registryinstance_from_pkgname(
pkgname::String; registries=RegistryInstances.reachable_registries()
)
# Make sure the UUID is the same across registries.
# If multiple registries contain the package, select
# the one from General.
uuid = uuid_from_pkgname(pkgname; registries)
# Find the registries that have packages with this UUID.
registries′ = filter(registries) do registry
haskey(registry, uuid)
end
# Choose the registry with the largest registered version of
# this package.
return argmax(registries′) do registry
# Get the maximum version number registered in this registry.
return maximum(keys(RegistryInstances.registry_info(registry[uuid]).version_info))
end
end
function registryinstance_from_registryname(
registryname::String; registries=RegistryInstances.reachable_registries()
)
which_registries = findall(registries) do registry
return registry.name == registryname
end
return registries[only(which_registries)]
end
# Get the dependencies and weak dependencies of a package.
function dependencies(
pkgname::String; weakdeps=true, registries=RegistryInstances.reachable_registries()
)
uuid = uuid_from_pkgname(pkgname; registries)
registry = registryinstance_from_pkgname(pkgname; registries)
pkginfo = RegistryInstances.registry_info(registry[uuid])
latest_version = maximum(keys(pkginfo.version_info))
# Use pkginfo.compat since it generally includes Deps and WeakDeps,
# pkginfo.deps doesn't include WeakDeps.
deps = String[]
for (k, v) in pairs(pkginfo.compat)
if latest_version ∈ k
append!(deps, collect(keys(v)))
end
end
return deps
end
to_registry(registry::RegistryInstances.RegistryInstance) = registry
to_registry(registryname::String) = registryinstance_from_registryname(registryname)
function dependents(
pkgname::String; registries=RegistryInstances.reachable_registries(), weakdeps=true
)
registries = to_registry.(registries)
deps = String[]
for registry in registries
for (_, pkgentry′) in registry
pkgname′ = pkgentry′.name
if pkgname ∈ dependencies(pkgname′; weakdeps, registries)
push!(deps, pkgname′)
end
end
end
return deps
end
[deps]
Git = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2"
ITensorPkgSkeleton = "3d388ab1-018a-49f4-ae50-18094d5f71ea"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
RegistryInstances = "2792f1a3-b283-48e8-9a74-f99dce5104f3"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
include("update_integrationtest.jl")
pkgnames = [
"BackendSelection",
"BlockSparseArrays",
"DerivableInterfaces",
"DiagonalArrays",
"GradedUnitRanges",
"ITensorBase",
"LabelledNumbers",
"MapBroadcast",
"NamedDimsArrays",
"NestedPermutedDimsArrays",
"QuantumOperatorAlgebra",
"QuantumOperatorDefinitions",
"SparseArraysBase",
"SymmetrySectors",
"TensorAlgebra",
"TypeParameterAccessors",
"UnallocatedArrays",
"UnspecifiedTypes",
]
update_integrationtest(
pkgnames; registries=["ITensorRegistry"], skip_deps=["ITensorQuantumOperatorDefinitions"]
)
using Git: git, readchomp
using ITensorPkgSkeleton: ITensorPkgSkeleton
using Pkg: Pkg
using Random: randstring
using RegistryInstances: RegistryInstances
using Suppressor: @suppress
include("dependents.jl")
function dev_dir()
return get(ENV, "JULIA_PKG_DEVDIR", joinpath(first(DEPOT_PATH), "dev"))
end
function downstream_workflow_path()
return ".github/workflows/IntegrationTest.yml"
end
function update_integrationtest(pkgnames::Vector{String}; kwargs...)
for pkgname in pkgnames
update_integrationtest(pkgname; kwargs...)
end
return nothing
end
function update_integrationtest(
pkgname::String;
registries=RegistryInstances.reachable_registries(),
pull_request_branch_name="update_integrationtest_$(randstring(16))",
pull_request_commit_message="Update IntegrationTest workflow",
skip_deps=String[],
)
deps = setdiff(dependents(pkgname; registries), skip_deps)
if isempty(deps)
println("Package $(pkgname).jl has no dependents, no workflow to add.\n")
return nothing
end
println(
"Attempting to update IntegrationTest.yml for package $(pkgname).jl with dependents $(deps .* ".jl").",
)
repo_url = "https://github.com/ITensor/$(pkgname).jl"
ghusers_and_repos = map(deps) do dep
uuid = uuid_from_pkgname(dep)
repo = RegistryInstances.registry_info(registryinstance_from_pkgname(dep)[uuid]).repo
return (; ghuser=split(repo, '/')[end - 1], repo=dep)
end
# Sort them so they end up sorted in the workflow.
sort!(ghusers_and_repos)
mktempdir() do tmp_path
current_proj = Base.active_project()
@suppress begin
Pkg.activate(tmp_path)
Pkg.develop(Pkg.PackageSpec(; name=pkgname))
Pkg.activate(current_proj)
end
end
pkg_dir = joinpath(dev_dir(), pkgname)
cd(pkg_dir) do
repo_status = readchomp(`$(git()) status -s`)
if !isempty(repo_status)
println(
"Can't make a PR because there are uncommitted changes in the repository $pkg_dir. Commit or stash any changes before running.\n",
)
return nothing
end
@suppress begin
run(`$(git()) checkout main`)
run(`$(git()) pull`)
run(`$(git()) checkout -b $(pull_request_branch_name)`)
ITensorPkgSkeleton.generate(
pkgname; templates=["downstreampkgs"], downstreampkgs=ghusers_and_repos
)
end
repo_status = readchomp(`$(git()) status -s`)
if isempty(repo_status)
println("No changes to make to the workflow, skipping.\n")
return nothing
end
@suppress begin
run(`$(git()) add $(downstream_workflow_path())`)
run(`$(git()) commit -m "$(pull_request_commit_message)"`)
run(`$(git()) push --set-upstream origin $(pull_request_branch_name)`)
run(`gh repo set-default $(repo_url)`)
run(`gh pr create --fill`)
run(`$(git()) checkout main`)
end
println("*** IntegrationTest.yml updated, created pull request to $(repo_url).\n")
end
return nothing
end
@mtfishman
Copy link
Author

This workflow requires the Julia packages in the Project.toml to be installed, and also expects GitHub CLI to be installed: https://cli.github.com.

@mtfishman
Copy link
Author

To run this, put all of these files into a single directory, activate and instantiate the project, and run include("run.jl"), which will make a PR to any of the packages listed in that file if IntegrationTest.yml needs to be updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment