From 61c79b4b96c9be21e5d92c3679c299da3a07c75f Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Mon, 31 Aug 2026 10:12:04 -0400 Subject: [PATCH] Repair restored sublibrary downgrade metadata Restore the original QA project after downgrade and rebuild only its local Manifest entry. This preserves downgraded dependency versions while removing promoted test extras from the package's own dependency metadata. Co-Authored-By: Chris Rackauckas Co-Authored-By: OpenAI Codex Agent-Harness: Codex CLI 0.151.0 Agent-Model: unknown Agent-Session: local session ID 01a04fa1-cfe0-7260-b416-72fe8a15d17d --- .github/workflows/sublibrary-downgrade.yml | 20 +++++++ scripts/restore_downgraded_project.jl | 69 ++++++++++++++++++++++ test/runtests.jl | 64 ++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 scripts/restore_downgraded_project.jl diff --git a/.github/workflows/sublibrary-downgrade.yml b/.github/workflows/sublibrary-downgrade.yml index 5c5174c..0dec7bf 100644 --- a/.github/workflows/sublibrary-downgrade.yml +++ b/.github/workflows/sublibrary-downgrade.yml @@ -167,6 +167,13 @@ jobs: [ -n "${{ inputs.skip }}" ] && effective="$effective,${{ inputs.skip }}" echo "Effective downgrade skip: $effective" echo "EFFECTIVE_SKIP=$effective" >> "$GITHUB_ENV" + - name: "Save project metadata for QA" + if: "${{ inputs.group-env-value == 'QA' }}" + shell: bash + run: | + save_dir="$RUNNER_TEMP/sciml-downgrade-original-project" + mkdir -p "$save_dir" + cp "${{ matrix.project }}/Project.toml" "$save_dir/Project.toml" - uses: julia-actions/julia-downgrade-compat@v2 with: projects: ${{ matrix.project }} @@ -180,6 +187,19 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 with: project: ${{ matrix.project }} + - name: "Checkout SciML/.github for the downgrade metadata helper" + if: "${{ inputs.group-env-value == 'QA' }}" + uses: actions/checkout@v7 + with: + repository: SciML/.github + path: .sciml-dotgithub + - name: "Restore project metadata before QA" + if: "${{ inputs.group-env-value == 'QA' }}" + shell: bash + run: | + save_dir="$RUNNER_TEMP/sciml-downgrade-original-project" + cp "$save_dir/Project.toml" "${{ matrix.project }}/Project.toml" + julia --startup-file=no .sciml-dotgithub/scripts/restore_downgraded_project.jl "${{ matrix.project }}" - uses: julia-actions/julia-runtest@v1 with: project: ${{ matrix.project }} diff --git a/scripts/restore_downgraded_project.jl b/scripts/restore_downgraded_project.jl new file mode 100644 index 0000000..d17c384 --- /dev/null +++ b/scripts/restore_downgraded_project.jl @@ -0,0 +1,69 @@ +#!/usr/bin/env julia + +using TOML + +function restored_manifest_entry(project::AbstractDict, path::AbstractString) + entry = Dict{String, Any}( + "path" => path, + "uuid" => project["uuid"], + ) + hard_deps = sort!(collect(keys(get(project, "deps", Dict{String, Any}())))) + isempty(hard_deps) || (entry["deps"] = hard_deps) + for key in ("weakdeps", "extensions") + values = get(project, key, nothing) + values isa AbstractDict && !isempty(values) && (entry[key] = deepcopy(values)) + end + haskey(project, "version") && (entry["version"] = project["version"]) + return entry +end + +function local_manifest_entry(entries, manifest_dir::AbstractString, project_dir::AbstractString) + matches = findall(entries) do entry + path = get(entry, "path", nothing) + path isa AbstractString || return false + return realpath(abspath(manifest_dir, path)) == realpath(project_dir) + end + length(matches) == 1 || error( + "expected exactly one local manifest entry for $project_dir, found $(length(matches))", + ) + return only(matches) +end + +""" + restore_downgraded_project(project_dir) + +Repair the local package entry in a downgrade-generated `Manifest.toml` after +restoring the package's original `Project.toml`. Resolved package versions and +test-only manifest entries are retained. +""" +function restore_downgraded_project(project_dir::AbstractString) + project_dir = normpath(abspath(project_dir)) + project_path = joinpath(project_dir, "Project.toml") + manifest_path = joinpath(project_dir, "Manifest.toml") + project = TOML.parsefile(project_path) + manifest = TOML.parsefile(manifest_path) + + package_name = project["name"] + entries = manifest["deps"][package_name] + entry_index = local_manifest_entry(entries, dirname(manifest_path), project_dir) + existing_path = entries[entry_index]["path"] + entries[entry_index] = restored_manifest_entry(project, existing_path) + pop!(manifest, "project_hash", nothing) + + temporary_path, io = mktemp(dirname(manifest_path)) + try + TOML.print(io, manifest; sorted = true) + close(io) + mv(temporary_path, manifest_path; force = true) + catch + close(io) + rm(temporary_path; force = true) + rethrow() + end + return nothing +end + +if abspath(PROGRAM_FILE) == @__FILE__ + length(ARGS) == 1 || error("usage: restore_downgraded_project.jl PROJECT_DIR") + restore_downgraded_project(only(ARGS)) +end diff --git a/test/runtests.jl b/test/runtests.jl index 1a5bc3e..cc520f2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,6 +8,70 @@ include(SCRIPT) const DEVELOP_SCRIPT = joinpath(@__DIR__, "..", "scripts", "develop_sources.jl") include(DEVELOP_SCRIPT) +const RESTORE_DOWNGRADE_SCRIPT = + joinpath(@__DIR__, "..", "scripts", "restore_downgraded_project.jl") +include(RESTORE_DOWNGRADE_SCRIPT) + +@testset "restore downgraded project manifest metadata" begin + root = mktempdir() + write( + joinpath(root, "Project.toml"), """ + name = "Root" + uuid = "00000000-0000-0000-0000-000000000001" + version = "1.2.3" + + [deps] + Runtime = "00000000-0000-0000-0000-000000000002" + + [weakdeps] + Weak = "00000000-0000-0000-0000-000000000003" + + [extensions] + RootWeakExt = "Weak" + """, + ) + write( + joinpath(root, "Manifest.toml"), """ + julia_version = "1.10.0" + manifest_format = "2.0" + project_hash = "stale" + + [[deps.Root]] + deps = ["Runtime", "TestOnly"] + path = "." + uuid = "00000000-0000-0000-0000-000000000001" + version = "1.2.3" + + [[deps.Runtime]] + git-tree-sha1 = "runtime-tree" + uuid = "00000000-0000-0000-0000-000000000002" + version = "2.0.0" + + [[deps.TestOnly]] + git-tree-sha1 = "test-tree" + uuid = "00000000-0000-0000-0000-000000000004" + version = "3.0.0" + """, + ) + + before = TOML.parsefile(joinpath(root, "Manifest.toml")) + restore_downgraded_project(root) + after = TOML.parsefile(joinpath(root, "Manifest.toml")) + + @test !haskey(after, "project_hash") + @test after["deps"]["Runtime"] == before["deps"]["Runtime"] + @test after["deps"]["TestOnly"] == before["deps"]["TestOnly"] + root_entry = only(after["deps"]["Root"]) + @test root_entry["path"] == "." + @test root_entry["uuid"] == "00000000-0000-0000-0000-000000000001" + @test root_entry["version"] == "1.2.3" + @test root_entry["deps"] == ["Runtime"] + @test root_entry["weakdeps"] == Dict( + "Weak" => "00000000-0000-0000-0000-000000000003", + ) + @test root_entry["extensions"] == Dict("RootWeakExt" => "Weak") +end + # Build a fixture monorepo `lib/` tree in a temp dir. # A (base, no internal deps) # B deps A