diff --git a/.github/workflows/compat-prune-monthly.yml b/.github/workflows/compat-prune-monthly.yml new file mode 100644 index 0000000..4e8c7b4 --- /dev/null +++ b/.github/workflows/compat-prune-monthly.yml @@ -0,0 +1,32 @@ +name: Compat prune (monthly) + +# The organization's monthly [compat] prune: every CT* package audited together, one PR per +# repository with something to drop, one tracking issue here in CTActions. The logic is the +# reusable compat-prune.yml; see docs/WORKFLOWS.md §3.2. + +on: + schedule: + - cron: '0 4 1 * *' + workflow_dispatch: + inputs: + dry_run: + description: 'Only audit and report: no PR, no issue' + type: boolean + default: true + +concurrency: + group: compat-prune-monthly + cancel-in-progress: false + +jobs: + prune: + uses: ./.github/workflows/compat-prune.yml + with: + # Dry run on every trigger until the GitHub App is set up; then the schedule + # publishes: `github.event_name == 'workflow_dispatch' && inputs.dry_run`. + dry_run: ${{ github.event_name != 'workflow_dispatch' || inputs.dry_run }} + # the scripts of the same commit as this workflow + ctactions_ref: ${{ github.sha }} + secrets: + CT_APP_CLIENT_ID: ${{ secrets.CT_APP_CLIENT_ID }} + CT_APP_PRIVATE_KEY: ${{ secrets.CT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/compat-prune-tests.yml b/.github/workflows/compat-prune-tests.yml new file mode 100644 index 0000000..e2f434f --- /dev/null +++ b/.github/workflows/compat-prune-tests.yml @@ -0,0 +1,51 @@ +name: Compat prune tests + +# Self-test of compat-prune, in CTActions itself: the test suite of scripts/compat-prune on +# every audited Julia version (unit, non-regression and integration tests), then a real +# dry-run audit of the ecosystem through the reusable workflow. Runs when the scripts or the +# workflows change; nothing is written anywhere (dry run). + +on: + pull_request: + paths: + - 'scripts/compat-prune/**' + - '.github/workflows/compat-prune*.yml' + push: + branches: [main] + paths: + - 'scripts/compat-prune/**' + - '.github/workflows/compat-prune*.yml' + workflow_dispatch: + +concurrency: + group: compat-prune-tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + version: ['1.10', '1.12', '1.13'] + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + - name: Add the General registry + run: julia --startup-file=no -e 'using Pkg; Pkg.Registry.add("General")' + - name: Tests (with integration) + env: + COMPAT_PRUNE_INTEGRATION: '1' + run: julia --startup-file=no scripts/compat-prune/test/runtests.jl + + dry-run: + needs: tests + uses: ./.github/workflows/compat-prune.yml + with: + dry_run: true + ctactions_ref: ${{ github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/compat-prune.yml b/.github/workflows/compat-prune.yml new file mode 100644 index 0000000..bee0417 --- /dev/null +++ b/.github/workflows/compat-prune.yml @@ -0,0 +1,185 @@ +name: Compat prune + +# Finds the [compat] specifiers that no supported Julia version ever selects across the +# control-toolbox packages, proves that dropping them leaves every resolved manifest +# unchanged, and (unless dry_run) opens one PR per affected repository plus one tracking +# issue (in CTActions by default), both updated in place by later runs. The prune is the +# organization's: it is called monthly by `compat-prune-monthly.yml`, here in CTActions, and +# by the self-test `compat-prune-tests.yml`; see docs/WORKFLOWS.md §3.2. +# +# The `audit` job is read-only: it resolves the joint, test and docs environments of every +# package on every Julia version (Pkg's resolver only: nothing is downloaded or installed), +# classifies the multi-specifier entries, validates the prunes, applies them to fresh +# checkouts of the default branches, and uploads the result (`compat-prune` artifact, +# report in the job summary). Scripts and tests: scripts/compat-prune/. +# +# The `publish` job (skipped on dry runs) is the only one that writes, with a token of the +# organization's GitHub App (secrets CT_APP_CLIENT_ID, CT_APP_PRIVATE_KEY; permissions: +# contents, pull requests, issues). It re-applies the prunes to fresh checkouts, then keeps +# one branch `ct/compat-prune` and one PR per repository, and one tracking issue in +# `tracking_repo`: a rerun updates them in place, and closes what has nothing left. + +on: + workflow_call: + inputs: + packages: + description: 'JSON list of the audited packages (registered names)' + required: false + type: string + default: '["CTBase","CTParser","CTModels","CTLie","CTFlows","CTDirect","CTSolvers","OptimalControl"]' + julia_versions: + description: 'JSON list of the Julia versions (juliaup channels); the last one is the newest' + required: false + type: string + default: '["1.10","1.12","1.13"]' + dry_run: + description: 'Only audit and report: no PR, no issue' + required: false + type: boolean + default: true + ctactions_ref: + description: 'Ref of CTActions the scripts are checked out from' + required: false + type: string + default: main + tracking_repo: + description: 'Repository of the tracking issue' + required: false + type: string + default: control-toolbox/CTActions + secrets: + CT_APP_CLIENT_ID: + description: 'Client ID of the GitHub App that publishes (not needed for a dry run)' + required: false + CT_APP_PRIVATE_KEY: + description: 'Private key of that GitHub App' + required: false + +jobs: + audit: + runs-on: ubuntu-latest + timeout-minutes: 120 + permissions: + contents: read + env: + PACKAGES: ${{ inputs.packages }} + JULIA_VERSIONS: ${{ inputs.julia_versions }} + SCRIPTS: ctactions/scripts/compat-prune + OUT: ${{ github.workspace }}/compat-prune + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v6 + with: + repository: control-toolbox/CTActions + ref: ${{ inputs.ctactions_ref }} + path: ctactions + + - uses: julia-actions/install-juliaup@v3 + with: + channel: release + + - name: Install the audited Julia versions + run: | + for v in $(jq -r '.[]' <<< "$JULIA_VERSIONS"); do juliaup add "$v"; done + juliaup status + + # ct-registry is public: added over HTTPS, no SSH key needed. The registries are + # updated once more by audit.jl, then frozen for the whole audit. + - name: Add the registries + run: | + julia --startup-file=no -e 'using Pkg + Pkg.Registry.add("General") + Pkg.Registry.add(url="https://github.com/control-toolbox/ct-registry.git")' + + - name: Test the scripts + run: julia --startup-file=no "$SCRIPTS/test/runtests.jl" + + - name: Audit + run: julia --startup-file=no "$SCRIPTS/audit.jl" "$PACKAGES" "$JULIA_VERSIONS" "$OUT" + + - name: Apply the prunes to fresh checkouts of the default branches + run: | + "$SCRIPTS/checkout.sh" "$OUT" checkouts + julia --startup-file=no "$SCRIPTS/edit.jl" "$OUT" checkouts + for d in checkouts/*/; do + git -C "$d" diff --src-prefix="a/$(basename "$d")/" --dst-prefix="b/$(basename "$d")/" + done > "$OUT/edits.diff" + + - name: Report + run: | + RUN_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + julia --startup-file=no "$SCRIPTS/report.jl" "$OUT" + { + cat "$OUT/report.md" + echo + echo "
Edits on the default branches (diff)" + echo + echo '```diff' + cat "$OUT/edits.diff" + echo '```' + echo "
" + } >> "$GITHUB_STEP_SUMMARY" + + - uses: actions/upload-artifact@v7 + with: + name: compat-prune + path: compat-prune/ + retention-days: 30 + + publish: + needs: audit + if: ${{ !inputs.dry_run }} + runs-on: ubuntu-latest + timeout-minutes: 30 + # Two publications at once would race on the same branches and issue. + concurrency: + group: compat-prune-publish + cancel-in-progress: false + permissions: + contents: read + env: + SCRIPTS: ctactions/scripts/compat-prune + OUT: ${{ github.workspace }}/compat-prune + steps: + - name: Check the App credentials + env: + CLIENT_ID: ${{ secrets.CT_APP_CLIENT_ID }} + PRIVATE_KEY: ${{ secrets.CT_APP_PRIVATE_KEY }} + run: | + if [ -z "$CLIENT_ID" ] || [ -z "$PRIVATE_KEY" ]; then + echo "::error::Publishing needs the secrets CT_APP_CLIENT_ID and CT_APP_PRIVATE_KEY (or run with dry_run: true)." + exit 1 + fi + + - uses: actions/checkout@v6 + with: + repository: control-toolbox/CTActions + ref: ${{ inputs.ctactions_ref }} + path: ctactions + + - uses: actions/download-artifact@v8 + with: + name: compat-prune + path: compat-prune + + - uses: julia-actions/install-juliaup@v3 + with: + channel: release + + - uses: actions/create-github-app-token@v3 + id: app + with: + client-id: ${{ secrets.CT_APP_CLIENT_ID }} + private-key: ${{ secrets.CT_APP_PRIVATE_KEY }} + owner: control-toolbox + + - name: Publish + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + TRACKING_REPO: ${{ inputs.tracking_repo }} + run: | + gh auth setup-git + "$SCRIPTS/checkout.sh" "$OUT" checkouts + julia --startup-file=no "$SCRIPTS/edit.jl" "$OUT" checkouts + RUN_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + julia --startup-file=no "$SCRIPTS/publish.jl" "$OUT" checkouts "$TRACKING_REPO" | tee -a "$GITHUB_STEP_SUMMARY" diff --git a/README.md b/README.md index 2bdcb1e..cf0dd40 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ repository and for org-wide updates — is described in |---|---|---| | `occidata-runner-maintenance.yml` | Weekly (Mon 02:30 UTC) | Purge Julia cache, update TeXLive, rotate logs on self-hosted runner | | `occidata-runner-watchdog.yml` | Daily (03:15 UTC) | Restart, through SSH, the Occidata runner jobs (one per organization) that are neither pending nor running | +| `compat-prune-monthly.yml` | Monthly (1st, 04:00 UTC) | Drop the `[compat]` specifiers no supported Julia version selects, across all CT\* packages: one PR per repository, one tracking issue here (logic: reusable `compat-prune.yml`, scripts and tests in `scripts/compat-prune/`) | The Occidata watchdog requires these CTActions repository or organization secrets: diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md index 9302bd4..d04b62e 100644 --- a/docs/WORKFLOWS.md +++ b/docs/WORKFLOWS.md @@ -336,15 +336,18 @@ jobs: ### 3.2 Maintenance workflows that live *in* `CTActions` (not called) -These run on `CTActions` itself, on a schedule, to keep the **self-hosted runners** -healthy. They are not `workflow_call` reusables. +These run on `CTActions` itself: on a schedule, to keep the **self-hosted runners** +healthy or to act on the whole organization, or on its own PRs, to test the scripts behind +a workflow. No package calls them. | Workflow (`CTActions`) | Role | Trigger | | --- | --- | --- | | `occidata-runner-maintenance.yml` | Purge Julia compile cache, update TeXLive, rotate logs on the `occidata` self-hosted runner | weekly cron, `workflow_dispatch` | | `occidata-runner-watchdog.yml` | Over SSH, run `scripts/start_runners.sh` (control-toolbox/occidata), which restarts every Occidata runner instance whose SLURM job is neither pending nor running | daily cron, `workflow_dispatch` | +| `compat-prune-monthly.yml` | The organization's `[compat]` prune: runs the reusable `compat-prune.yml` on every CT\* package; one PR per repository with something to drop, one tracking issue in CTActions | monthly cron (1st, 04:00 UTC), `workflow_dispatch` (`dry_run`) | +| `compat-prune-tests.yml` | Self-test of `compat-prune`: the test suite of `scripts/compat-prune` on every audited Julia version, then a real dry-run audit through `compat-prune.yml` | PR or push touching `scripts/compat-prune/` or `compat-prune*.yml`, `workflow_dispatch` | -`occidata` started as the target of this maintenance workflow only; since CTFlows.jl's +`occidata` started as the target of the maintenance workflow only; since CTFlows.jl's `occidata-runner` job (§3.1, added 2026-08-24) it is also a live GPU CI target for every package's test matrix that needs one, and (2026-09-02, control-toolbox/OptimalControl.jl#885) a documentation-build target too — not just a runner kept healthy for its own sake. Two @@ -381,6 +384,82 @@ organization. It runs in `cpu` mode: no `$OCCIDATA_GPU_RUN`, so no GPU ever. Ins their modes and how to add one are documented in control-toolbox/occidata, `docs/gha-runner-config.md` § 9. +**`[compat]` entries are pruned for the whole organization, once a month.** +`compat-helper.yml` runs with `KeepEntry()`: a new major is added next to the old ones +(`JET = "0.9, 0.11, 0.12"`), and +`ci.yml` tests the newest on CompatHelper PRs (`force_latest_compatible_version=auto`, +except on Julia 1.10, CTActions#93). Old specifiers therefore pile up, and none can be +dropped from inside one package: `JET 0.9` is still what Julia 1.10 selects, and +CTSolvers' `CUDSS 0.7` is what OptimalControl's tests select. The prune is therefore the +organization's, decided for all CT\* packages at once (control-toolbox/OptimalControl.jl#986), +and lives here: `compat-prune-monthly.yml` runs the reusable `compat-prune.yml` (also run +by the self-test) on the 1st of every month, and keeps its tracking issue in CTActions +(input `tracking_repo`). No package carries a caller. + +- **What is audited.** The version of each package that the joint resolution of all of + them selects, read from its registered tree (not from a branch), and 17 environments: the + joint one, plus the test environment (`test/Project.toml` or `[targets] test`) and the + docs environment of every package, each on every Julia version of `julia_versions`. + Resolution calls Pkg's resolver only: nothing is downloaded or installed, and one + registry snapshot serves the whole audit (about 4 minutes on a runner). +- **What is pruned.** In an entry with several specifiers, those older than the newest one + in use, that no environment selects on any Julia version, counting only the environments + where the entry applies (where its package is at its audited version, or the very `test/` + or `docs/` environment of the file). Every prune is then simulated, by intersecting the + compat of the environments where it applies, and kept only if all manifests stay + identical. A prune therefore changes nothing that is resolved: the PRs can be merged in + any order and need no release. +- **What is reported.** The tracking issue (hidden marker ``) + opens with the record of the audit (environments resolved per Julia version, entries + examined by status, registry snapshot, and, folded, the entries kept or idle with why), + then lists the prunes per repository with their PR, those merged but not yet released, the + *drifts* (the default branch no longer has the audited line: untouched), the prunes + rejected by the simulation, the *withheld* ones (an environment failed to resolve: nothing + is pruned that run), and the *blocked* entries (the newest specifier is never selected, + with the packages that cap it: information only). +- **Idempotence.** One branch `ct/compat-prune` and one PR per repository, one issue: a + rerun rewrites them in place (the branch is pushed only when its content changes), closes + the PR and deletes the branch of a repository with nothing left, and closes the issue + (reopened, never recreated) when nothing is left at all. The edits are re-applied to the + current default branch, one `[compat]` line at a time, never copied from the audit. +- **Dry run.** `dry_run: true` (the default) runs the read-only `audit` job only: report + and diff in the job summary, everything in the `compat-prune` artifact. The `publish` + job writes with a token of the organization's GitHub App (`CT_APP_CLIENT_ID`, + `CT_APP_PRIVATE_KEY`; permissions: contents, pull requests, issues, on the CT\* + repositories) and is the only one that does. + +`ct-registry` is added over HTTPS (it is public), so no `SSH_KEY` is needed. The scripts +live in `scripts/compat-prune/` and run locally the same way (juliaup channels): + +```bash +julia scripts/compat-prune/audit.jl '["CTBase","CTParser","CTModels","CTLie","CTFlows","CTDirect","CTSolvers","OptimalControl"]' '["1.10","1.12","1.13"]' /tmp/audit +COMPAT_PRUNE_INTEGRATION=1 julia scripts/compat-prune/test/runtests.jl # unit, non-regression, integration +``` + +The monthly workflow: + +```yaml +# .github/workflows/compat-prune-monthly.yml (CTActions) +on: + schedule: + - cron: '0 4 1 * *' + workflow_dispatch: + inputs: + dry_run: { type: boolean, default: true } +jobs: + prune: + uses: ./.github/workflows/compat-prune.yml + with: + dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }} + ctactions_ref: ${{ github.sha }} + secrets: + CT_APP_CLIENT_ID: ${{ secrets.CT_APP_CLIENT_ID }} + CT_APP_PRIVATE_KEY: ${{ secrets.CT_APP_PRIVATE_KEY }} +``` + +Until the GitHub App is set up, every trigger is a dry run +(`dry_run: ${{ github.event_name != 'workflow_dispatch' || inputs.dry_run }}`). + ### 3.3 Non-centralized / per-repo special workflows | Workflow | Where | Why it isn't centralized | @@ -495,6 +574,7 @@ runner is the one maintained by the scheduled `CTActions` maintenance workflows | `DOCUMENTER_KEY` | Documentation, CompatHelper, TagBot | Deploy docs / sign tags (Documenter deploy key) | | `CODECOV_TOKEN` → `codecov-secret` | Coverage | Upload coverage to Codecov | | `PROJECT_TOKEN` → `project_token` | AddToProject | Write to the org project board | +| `CT_APP_CLIENT_ID`, `CT_APP_PRIVATE_KEY` | `compat-prune-monthly.yml` in CTActions (`publish` job) | Token of the organization's GitHub App: push `ct/compat-prune` and open PRs in the CT\* repositories, keep the tracking issue | | `GITHUB_TOKEN` | (built-in) most | Default repo-scoped token | Use `secrets: inherit` (as `UpdateReadme` does) only when the reusable workflow needs diff --git a/scripts/compat-prune/audit.jl b/scripts/compat-prune/audit.jl new file mode 100644 index 0000000..e79ecab --- /dev/null +++ b/scripts/compat-prune/audit.jl @@ -0,0 +1,136 @@ +# Run a whole audit: one registry snapshot, every Julia version. +# +# usage: julia audit.jl '' '' +# +# Each Julia version is run as `julia +` (juliaup). Steps: +# 1. update the registries once; no later step updates them (see `resolve_env`) +# 2. collect.jl and envs.jl, on the last (newest) Julia version +# 3. resolve.jl: the baseline of every environment, on each Julia version +# 4. classify.jl, from all baselines +# 5. validate.jl on each Julia version, repeated with the rejected prunes removed until no +# version rejects anything new (a smaller set of prunes is re-checked as a whole) +# Output: /audit.toml (accepted prunes, rejected prunes, blockers, other entries, and +# the record of the audit itself: registries, coverage, entries), next to the intermediate +# files of every step. + +using Dates, Pkg, TOML + +include(joinpath(@__DIR__, "common.jl")) + +short(v) = join(split(v, ".")[1:2], ".") + +""" + registry_snapshot() -> Vector{Dict} + +Name and state of every reachable registry: the tree hash of a registry installed from a +tarball (General, through the package server), the commit of one cloned with git. +""" +function registry_snapshot() + out = Dict{String,Any}[] + for reg in Pkg.Registry.reachable_registries() + state = if reg.tree_info !== nothing + "tree $(reg.tree_info)" + elseif isdir(joinpath(reg.path, ".git")) + "commit " * readchomp(`git -C $(reg.path) rev-parse HEAD`) + else + "unknown" + end + push!(out, Dict("name" => reg.name, "state" => state)) + end + return sort(out; by=r -> r["name"]) +end + +""" + coverage(baselines) -> Vector{Dict} + +For each Julia version (`julia` => parsed baseline), how many environments resolved, and +which failed. +""" +coverage(baselines) = [Dict("julia" => j, "resolved" => count(r -> r["ok"], values(b["env"])), "total" => length(b["env"]), + "failed" => sort([n for (n, r) in b["env"] if !r["ok"]])) + for (j, b) in sort(collect(baselines); by=first)] + +""" + summarize(; date, julia, index, classified, rejected, blocked, registries, coverage) -> Dict + +The content of audit.toml: the accepted prunes (classified minus rejected), the rejected +ones, the blocker probes, the other entries (keep, idle, withheld), and the record of the +audit: registries, coverage, and how many multi-specifier entries were examined, with one +status each. +""" +function summarize(; date, julia, index, classified, rejected, blocked, registries, coverage) + gone = Set(entry_key.(rejected)) + prunes = [p for p in classified["prune"] if !(entry_key(p) in gone)] + others = [r for r in classified["other"] if r["status"] != "blocked"] + # one status per entry: a blocked entry may also carry a prune of its obsolete lanes + status = Dict{Any,String}() + for p in classified["prune"] + status[entry_key(p)] = entry_key(p) in gone ? "rejected" : "prune" + end + for r in classified["other"]; status[entry_key(r)] = r["status"]; end + entries = Dict{String,Any}(s => count(==(s), values(status)) for s in ("prune", "rejected", "keep", "blocked", "idle", "withheld")) + entries["examined"] = length(status) + return Dict{String,Any}( + "date" => date, "julia" => julia, "packages" => index["packages"], + "prune" => prunes, "rejected" => rejected, "blocked" => blocked, "other" => others, + "record" => Dict{String,Any}("registries" => registries, "coverage" => coverage, "entries" => entries), + ) +end + +function run_step(v, script, args...) + println("\n\$ julia +$v $script ", join(args, " ")) + run(`julia +$v --startup-file=no $(joinpath(@__DIR__, script)) $args`) +end + +if abspath(PROGRAM_FILE) == @__FILE__ + packages, versions, out = ARGS[1], ARGS[2], abspath(ARGS[3]) + jlv = parse_list(versions) + + Pkg.Registry.update() + registries = registry_snapshot() + mkpath(out) + + run_step(last(jlv), "collect.jl", packages, out) + run_step(last(jlv), "envs.jl", out) + for v in jlv + run_step(v, "resolve.jl", out, joinpath(out, "baseline-$(short(v)).toml")) + end + run_step(last(jlv), "classify.jl", out, joinpath(out, "classified.toml"), + (joinpath(out, "baseline-$(short(v)).toml") for v in jlv)...) + + rejected = Dict{String,Any}[] + blocked = Dict{String,Any}[] + for round in 1:5 + open(io -> TOML.print(io, Dict("rejected" => rejected)), joinpath(out, "rejected.toml"), "w") + new = Dict{String,Any}[] + empty!(blocked) + for v in jlv + res = joinpath(out, "validated-$(short(v)).toml") + blockers = v == last(jlv) ? ("--blockers",) : () + run_step(v, "validate.jl", out, joinpath(out, "classified.toml"), + joinpath(out, "baseline-$(short(v)).toml"), res, joinpath(out, "rejected.toml"), blockers...) + r = TOML.parsefile(res) + append!(new, r["rejected"]) + append!(blocked, r["blocked"]) + end + known = Set(entry_key.(rejected)) + fresh = [p for p in new if !(entry_key(p) in known)] + isempty(fresh) && break + round == 5 && error("validation did not settle after 5 rounds") + # one record per prune, the first reason found + for p in fresh + entry_key(p) in known || (push!(rejected, p); push!(known, entry_key(p))) + end + end + + baselines = Dict(short(v) => TOML.parsefile(joinpath(out, "baseline-$(short(v)).toml")) for v in jlv) + audit = summarize(; date=Dates.format(now(UTC), "yyyy-mm-dd HH:MM") * " UTC", julia=jlv, + index=TOML.parsefile(joinpath(out, "index.toml")), + classified=TOML.parsefile(joinpath(out, "classified.toml")), + rejected, blocked, registries, coverage=coverage(baselines)) + open(io -> TOML.print(io, audit), joinpath(out, "audit.toml"), "w") + e = audit["record"]["entries"] + println("\n", e["examined"], " entries examined: ", length(audit["prune"]), " prune(s) accepted, ", + length(rejected), " rejected, ", e["blocked"], " blocked, ", e["keep"], " kept, ", e["idle"], " idle, ", + e["withheld"], " withheld") +end diff --git a/scripts/compat-prune/checkout.sh b/scripts/compat-prune/checkout.sh new file mode 100755 index 0000000..47e8a3d --- /dev/null +++ b/scripts/compat-prune/checkout.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Shallow clone of the default branch of every audited package. +# +# usage: checkout.sh +# +# Reads /index.toml; clones $COMPAT_PRUNE_GIT_BASE/.git (default +# https://github.com; tests point it at local repositories) into /. +# Pushing later relies on git's credentials (`gh auth setup-git` in the publish job). +set -euo pipefail +audit=$1 +checkouts=$2 +mkdir -p "$checkouts" +julia --startup-file=no -e 'using TOML + for (n, p) in sort(collect(TOML.parsefile(ARGS[1])["packages"]); by=first) + println(n, " ", p["repo"], " ", p["default_branch"]) + end' "$audit/index.toml" | +while read -r name repo branch; do + git clone --quiet --depth 1 --branch "$branch" "${COMPAT_PRUNE_GIT_BASE:-https://github.com}/$repo.git" "$checkouts/$name" +done diff --git a/scripts/compat-prune/classify.jl b/scripts/compat-prune/classify.jl new file mode 100644 index 0000000..2e654af --- /dev/null +++ b/scripts/compat-prune/classify.jl @@ -0,0 +1,130 @@ +# Classify every multi-specifier compat entry from the baselines of all Julia versions. +# +# usage: julia classify.jl ... +# +# A specifier of the entry of `owner` on `dep` is *used* when, in an environment where that +# entry applies (see `applies` in common.jl) and on some Julia version, the resolved `dep` +# falls in it. Then, ordering specifiers by their lower bound: +# prune unused specifiers older than the newest used one: obsolete lanes; +# blocked the newest specifier is unused: something caps `dep` below it (reported, the +# blocker is identified by validate.jl); +# idle `dep` is resolved nowhere the entry applies (e.g. a weak dependency no audited +# environment loads): nothing can be proven, nothing is touched. +# Unused specifiers between the newest used one and the newest declared one are blocked +# lanes too, never pruned. If any baseline environment failed to resolve, the lanes it would +# have used are unknown: every prune is then withheld, and the failure is reported. + +include("common.jl") + +"Specifiers of a compat string, in declaration order." +specifiers(s) = String.(strip.(split(s, ","))) + +"Lower bound of one specifier, for ordering (`\"0.7 - 0.8\"` → v0.7, `\"^1.2\"` → v1.2)." +lower(s) = VersionNumber(match(r"\d+(\.\d+){0,2}", s).match) + +""" + classify_entry(owner, file, dep, compat, results, audited) -> Dict + +`results` maps `"@"` to the parsed result of that environment. Returns a record +with `status` (`prune`, `blocked`, `keep`, `idle`), the specifiers used, and the evidence: +where each used specifier was selected. +""" +function classify_entry(owner, file, dep, compat, results, audited) + parts = specifiers(compat) + evidence = Dict(p => String[] for p in parts) + for (key, r) in sort(collect(results); by=first) + r["ok"] || continue + name = first(split(key, "@")) + v = get(r["versions"], dep, nothing) + (v === nothing || !applies(owner, file, name, r["versions"], audited)) && continue + for p in parts + VersionNumber(v) in spec(p) && push!(evidence[p], "$key=$v") + end + end + record = Dict{String,Any}("owner" => owner, "file" => file, "dep" => dep, "before" => compat) + used = [p for p in parts if !isempty(evidence[p])] + record["evidence"] = Dict(p => evidence[p] for p in used) + isempty(used) && return merge(record, Dict("status" => "idle")) + newest_used = maximum(lower, used) + obsolete = [p for p in parts if isempty(evidence[p]) && lower(p) < newest_used] + if maximum(lower, parts) > newest_used + record["status"] = "blocked" + record["newest"] = parts[argmax(lower.(parts))] + record["applies_in"] = sort(unique(first.(split.(reduce(vcat, values(evidence)), "@")))) + # obsolete lanes of a blocked entry are still obsolete: report both + isempty(obsolete) || (record["prunable"] = obsolete) + return record + end + isempty(obsolete) && return merge(record, Dict("status" => "keep")) + return merge(record, Dict("status" => "prune", "removed" => obsolete, + "after" => join(setdiff(parts, obsolete), ", "))) +end + +"Every multi-specifier entry of the audited files, classified." +function classify(root, results, audited) + records = Dict{String,Any}[] + for owner in sort(collect(keys(audited))), file in AUDITED_FILES + path = joinpath(root, "registered", owner, file) + isfile(path) || continue + for (dep, compat) in sort(collect(get(TOML.parsefile(path), "compat", Dict()))) + dep == "julia" && continue + length(specifiers(compat)) > 1 || continue + push!(records, classify_entry(owner, file, dep, compat, results, audited)) + end + end + return records +end + +""" + withhold(prunes, results) -> (prunes, withheld) + +Withhold every prune when a baseline environment (any `"@"` of `results`) failed. +""" +function withhold(prunes, results) + failed = sort([k for (k, r) in results if !r["ok"]]) + isempty(failed) && return prunes, Dict{String,Any}[] + why = "baseline failed in $(join(failed, ", ")): lanes used there are unknown" + return Dict{String,Any}[], [merge(p, Dict("status" => "withheld", "reason" => why)) for p in prunes] +end + +"Baseline results of every Julia version, keyed `\"@\"`." +function load_results(files) + results = Dict{String,Any}() + for f in files + b = TOML.parsefile(f) + j = join(split(b["julia"], ".")[1:2], ".") + for (name, r) in b["env"]; results["$name@$j"] = r; end + end + return results +end + +""" + classify_all(root, results) -> (prunes, others) + +Classify every entry; obsolete lanes of blocked entries become prunes of their own; every +prune is withheld when a baseline failed. +""" +function classify_all(root, results) + _, audited, _ = load_collected(root) + records = classify(root, results, audited) + prunes = [r for r in records if r["status"] == "prune"] + for r in records + r["status"] == "blocked" && haskey(r, "prunable") || continue + parts = specifiers(r["before"]) + push!(prunes, merge(r, Dict("status" => "prune", "removed" => r["prunable"], + "after" => join(setdiff(parts, r["prunable"]), ", ")))) + end + prunes, withheld = withhold(prunes, results) + others = vcat([r for r in records if r["status"] != "prune"], withheld) + return prunes, others +end + +if abspath(PROGRAM_FILE) == @__FILE__ + root, outfile = ARGS[1], ARGS[2] + prunes, others = classify_all(root, load_results(ARGS[3:end])) + open(io -> TOML.print(io, Dict("prune" => prunes, "other" => others)), outfile, "w") + for r in sort(vcat(prunes, others); by=r -> (r["owner"], r["file"], r["dep"])) + println(rpad("$(r["owner"]) $(r["file"])", 34), rpad(r["dep"], 26), rpad(r["before"], 18), r["status"], + haskey(r, "after") ? " → $(r["after"])" : "") + end +end diff --git a/scripts/compat-prune/collect.jl b/scripts/compat-prune/collect.jl new file mode 100644 index 0000000..31245c7 --- /dev/null +++ b/scripts/compat-prune/collect.jl @@ -0,0 +1,62 @@ +# Collect the audited files of every package. +# +# usage: julia collect.jl '' +# +# The audited version of each package is the one the joint environment (all packages +# together) selects on the running Julia, i.e. what a user of the root package gets. Its +# files are read from the registered tree (manifest `git-tree-sha1`), not from a tag or a +# branch: default branches may have moved on, or a version may have been registered from +# another branch. The same files are also read from the default branch, to detect drift +# before editing anything. +# +# Output: +# /index.toml name => uuid, version, repo, tree, default_branch +# /registered// files of the registered tree +# /default// files of the default branch + +include("common.jl") + +"Download the audited files present in tree `treeish` of `repo` into `dest`; return their paths." +function fetch_files(repo, treeish, dest) + jq = ".tree[] | select(.type == \"blob\") | .path + \" \" + .sha" + listing = gh_jq("repos/$repo/git/trees/$treeish?recursive=1", jq) + blobs = Dict(Tuple(split(l)) for l in split(listing, '\n') if !isempty(l)) + found = String[] + for f in AUDITED_FILES + haskey(blobs, f) || continue + path = joinpath(dest, f) + mkpath(dirname(path)) + write(path, gh_raw("repos/$repo/git/blobs/$(blobs[f])")) + push!(found, f) + end + return found +end + +""" + collect_files(out, lookup, versions, trees) -> Dict + +Fetch the registered and default-branch files of every package of `lookup` (name => +(uuid, repo)) into `out`, write `/index.toml` and return the index. +""" +function collect_files(out, lookup, versions, trees) + index = Dict{String,Any}() + for n in sort(collect(keys(lookup))) + uuid, repo = lookup[n] + branch = gh_jq("repos/$repo", ".default_branch") + registered = fetch_files(repo, trees[n], joinpath(out, "registered", n)) + default = fetch_files(repo, branch, joinpath(out, "default", n)) + index[n] = Dict("uuid" => uuid, "version" => versions[n], "repo" => repo, "tree" => trees[n], + "default_branch" => branch, "files" => registered, "default_files" => default) + println(rpad(n, 16), rpad(versions[n], 14), repo, " [", join(registered, ", "), "]") + end + open(io -> TOML.print(io, Dict("julia" => string(VERSION), "packages" => index)), joinpath(out, "index.toml"), "w") + return index +end + +if abspath(PROGRAM_FILE) == @__FILE__ + packages, out = parse_list(ARGS[1]), ARGS[2] + lookup = Dict(n => registry_lookup(n) for n in packages) + joint = resolve_env(Dict(n => lookup[n][1] for n in packages), Dict()) + joint["ok"] || error("The joint environment does not resolve on Julia $VERSION:\n$(joint["error"])") + collect_files(out, lookup, joint["versions"], joint["trees"]) +end diff --git a/scripts/compat-prune/common.jl b/scripts/compat-prune/common.jl new file mode 100644 index 0000000..53b322e --- /dev/null +++ b/scripts/compat-prune/common.jl @@ -0,0 +1,167 @@ +# Helpers shared by the compat-prune scripts. Stdlib only. + +using Pkg, TOML + +# Resolution only: environments are resolved by calling Pkg's resolver directly (see +# `resolve_env`), which never downloads, installs, precompiles, or updates a registry. An +# audit therefore sees one registry snapshot from start to end: `audit.jl` updates the +# registries once, before anything else. + +# The command runner behind `gh`: a function of the argument vector returning standard +# output. Tests replace it with a fake to run without network; a second include keeps it. +@isdefined(GH) || global GH = Ref{Any}(args -> read(`gh $args`, String)) + +"Run `gh` with `args` and return its standard output." +gh(args...) = GH[](collect(String, args)) + +"`gh api` call returning the raw body (blobs, file contents)." +gh_raw(path) = gh("api", path, "-H", "Accept: application/vnd.github.raw") + +"`gh api` call returning one field extracted with `--jq`." +gh_jq(path, jq) = strip(gh("api", path, "--jq", jq)) + +""" + resolve_env(deps, compat) -> Dict + +Resolve a throwaway environment whose `[deps]` are `deps` (name => uuid) and whose +`[compat]` is `compat` (name => specifier), on the running Julia. Returns +`Dict("ok" => true, "versions" => …, "trees" => …)` (name => version string, name => +git-tree-sha1 of the registered packages), or `Dict("ok" => false, "error" => …)` when the +requirements are unsatisfiable. + +`Pkg.resolve` is not used: it downloads every source and artifact of the result (GBs of CUDA +on a fresh runner), and `Pkg.offline(true)` would restrict it to the versions already +installed. `Pkg.Operations.resolve_versions!` only resolves; it is internal, but its +signature is the same from Julia 1.10 to 1.13 (checked by the integration tests). +""" +function resolve_env(deps::AbstractDict, compat::AbstractDict) + dir = mktempdir() + open(joinpath(dir, "Project.toml"), "w") do io + TOML.print(io, Dict("deps" => deps, "compat" => compat)) + end + current = Base.active_project() + try + Pkg.activate(dir; io=devnull) + ctx = Pkg.Types.Context() + pkgs = Pkg.Operations.load_direct_deps(ctx.env) + Pkg.Operations.resolve_versions!(ctx.env, ctx.registries, pkgs, ctx.julia_version, false) + return Dict("ok" => true, + "versions" => Dict(p.name => string(p.version) for p in pkgs), + "trees" => Dict(p.name => string(p.tree_hash) for p in pkgs if p.tree_hash !== nothing)) + catch e + # Only an unsatisfiable resolution is a result; anything else is a bug of the caller. + e isa Pkg.Resolve.ResolverError || rethrow() + return Dict("ok" => false, "error" => sprint(showerror, e)) + finally + Pkg.activate(current; io=devnull) + rm(dir; recursive=true, force=true) + end +end + +""" + registry_lookup(name) -> (uuid, repo) + +UUID and GitHub `owner/repo` of a registered package, read from the first reachable registry +that knows it. +""" +function registry_lookup(name) + for reg in Pkg.Registry.reachable_registries() + uuids = Pkg.Registry.uuids_from_name(reg, name) + isempty(uuids) && continue + entry = reg[uuids[1]] + # Signature changed in Julia 1.13: registry_info(entry) → registry_info(reg, entry). + info = applicable(Pkg.Registry.registry_info, reg, entry) ? + Pkg.Registry.registry_info(reg, entry) : Pkg.Registry.registry_info(entry) + m = match(r"github\.com[:/]([^/]+/[^/]+?)(?:\.git)?$", info.repo) + m === nothing && error("$name: repository $(info.repo) is not on GitHub") + return (string(uuids[1]), String(m[1])) + end + error("$name is not in any reachable registry") +end + +"The files audited in every repository." +const AUDITED_FILES = ("Project.toml", "test/Project.toml", "docs/Project.toml") + +"Identity of a compat entry: the owner, the file it is declared in, the dependency." +entry_key(p) = (p["owner"], p["file"], p["dep"]) + +const spec = Pkg.Types.semver_spec + +"Render a VersionSpec back into a compat string (bounds carry no prerelease, so `-` only separates)." +render(vs) = join([replace(string(r), r"\s*-\s*" => " - ") for r in vs.ranges], ", ") + +""" + applies(owner, file, name, versions, audited) -> Bool + +Does the compat entry of `owner` read from `file` constrain environment `name`, whose +resolved versions are `versions`? An entry of `Project.toml` applies wherever `owner` is +resolved at its audited version (another version carries other entries); an entry of +`test/` or `docs/Project.toml` applies only in that very environment. +""" +function applies(owner, file, name, versions, audited) + file == "Project.toml" && return get(versions, owner, nothing) == audited[owner] + return name == "$owner/$(first(splitpath(file)))" +end + +""" + simulate(name, env, narrowings, uuids; resolver=resolve_env) -> Dict + +Resolve environment `name` (an entry of `envs.toml`) with each `dep => specifier` of +`narrowings` intersected into its compat. Pkg intersects the compat of every package of an +environment, so this is exactly what dropping specifiers from an entry that applies here +would do, without touching any file. Returns `Dict("ok" => true, "versions" => …)` or +`Dict("ok" => false, "error" => …)`; an empty intersection or a pinned package resolved at +another version than the audited one are failures too. `resolver(deps, compat)` is +`resolve_env`, replaced in tests. +""" +function simulate(name, env, narrowings, uuids; resolver=resolve_env) + deps, compat = copy(env["deps"]), copy(env["compat"]) + for (dep, s) in narrowings + compat[dep] = render(spec(get(compat, dep, ">= 0")) ∩ spec(s)) + deps[dep] = uuids[dep] # a transitive dep must be listed to carry a compat entry + end + empty = sort([k for (k, v) in compat if isempty(v)]) + isempty(empty) || return Dict("ok" => false, "error" => "empty compat intersection for $(join(empty, ", "))") + res = resolver(deps, compat) + res["ok"] || return res + versions = res["versions"] + off = ["$k $(versions[k]) (audited $v)" for (k, v) in env["pins"] if versions[k] != v] + isempty(off) || return Dict("ok" => false, "error" => "pinned versions not selected: $(join(off, ", "))") + return Dict("ok" => true, "versions" => versions) +end + +""" + narrowings(prunes, name, versions, audited) -> Dict + +The `dep => kept specifiers` that `prunes` impose on environment `name` (baseline +`versions`): intersection over every prune whose entry applies here and whose dep is present. +""" +function narrowings(prunes, name, versions, audited) + out = Dict{String,String}() + for p in prunes + haskey(versions, p["dep"]) && applies(p["owner"], p["file"], name, versions, audited) || continue + out[p["dep"]] = haskey(out, p["dep"]) ? render(spec(out[p["dep"]]) ∩ spec(p["after"])) : p["after"] + end + return out +end + +""" + load_collected(root) -> (envs, audited, uuids) + +`envs.toml`, the audited versions, and the uuid of every package named in an audited file +(`[deps]`, `[weakdeps]`, `[extras]`): a pruned dep is always declared by its owner, even +when it is only a transitive dependency of the environments. +""" +function load_collected(root) + envs = TOML.parsefile(joinpath(root, "envs.toml"))["env"] + audited = Dict(n => p["version"] for (n, p) in TOML.parsefile(joinpath(root, "index.toml"))["packages"]) + uuids = Dict{String,Any}() + for (dir, _, files) in walkdir(joinpath(root, "registered")), f in files + t = TOML.parsefile(joinpath(dir, f)) + for s in ("deps", "weakdeps", "extras"); merge!(uuids, get(t, s, Dict())); end + end + return envs, audited, uuids +end + +"Parse a JSON list of strings such as `[\"a\",\"b\"]` (inputs of the reusable workflow)." +parse_list(s) = String[m[1] for m in eachmatch(r"\"([^\"]+)\"", s)] diff --git a/scripts/compat-prune/edit.jl b/scripts/compat-prune/edit.jl new file mode 100644 index 0000000..2fbec1e --- /dev/null +++ b/scripts/compat-prune/edit.jl @@ -0,0 +1,98 @@ +# Apply the accepted prunes to checkouts of the default branches, textually. +# +# usage: julia edit.jl +# +# / is a fresh checkout of the default branch of package . The +# prunes of /audit.toml are applied in place, line by line, to the files as they +# are *now*: never by copying a collected file, which would revert whatever the branch +# received since the audit read it. The outcome of every prune goes to +# /edits.toml: +# edited the line on the default branch is the audited one: it is rewritten; +# already the default branch already carries the pruned value (merged, awaiting release); +# drift the line differs, or the entry, the file or the checkout is missing: untouched. +# Only the value of the one `[compat]` line changes: order, quoting, spacing and comments of +# the file are kept, and the result is re-parsed to check that nothing else moved. + +include("common.jl") + +"Specifiers of a compat string, normalised for comparison." +normalised(s) = sort(String.(strip.(split(s, ",")))) + +const SECTION = r"^\s*\[\s*([^\[\]]+?)\s*\]\s*(#.*)?$" + +""" + edit_compat(text, dep, before, after) -> (text, status, detail) + +Rewrite the `[compat]` entry of `dep` from `before` to `after` in the TOML source `text`. +`status` is `"edited"`, `"already"` or `"drift"`; `text` is unchanged unless `"edited"`. +""" +function edit_compat(text, dep, before, after) + lines = split(text, '\n'; keepempty=true) + name = replace(dep, r"[^A-Za-z0-9_]" => s"\\\0") + entry = Regex("^(\\s*\"?$name\"?\\s*=\\s*)([\"'])(.*?)\\2(.*)\$") + section = "" + for (i, l) in enumerate(lines) + m = match(SECTION, l) + m === nothing || (section = m[1]; continue) + section == "compat" || continue + m = match(entry, l) + m === nothing && continue + value = m[3] + normalised(value) == normalised(after) && return (text, "already", "already `$value`") + normalised(value) == normalised(before) || + return (text, "drift", "`$value` on the default branch, `$before` audited") + lines[i] = m[1] * m[2] * after * m[2] * m[4] + edited = join(lines, '\n') + check_edit(text, edited, dep, after) + return (edited, "edited", "") + end + return (text, "drift", "no `$dep` entry in `[compat]` on the default branch") +end + +"Assert that `edited` differs from `original` by the compat entry of `dep` only." +function check_edit(original, edited, dep, after) + a, b = TOML.parse(original), TOML.parse(edited) + b["compat"][dep] == after || error("edit of $dep did not produce `$after`") + a["compat"][dep] = after + a == b || error("editing $dep changed more than its compat entry") + return nothing +end + +""" + plan_edits(audit, read_file) -> (records, texts) + +Apply every accepted prune of `audit` to the files returned by `read_file(owner, file)` +(`nothing` when absent). `records` are the prunes with their `status` and `detail`; `texts` +maps `(owner, file)` to the edited text of every file with at least one edit. +""" +function plan_edits(audit, read_file) + texts = Dict{Tuple{String,String},String}() + records = Dict{String,Any}[] + for p in sort(audit["prune"]; by=entry_key) + k = (p["owner"], p["file"]) + text = haskey(texts, k) ? texts[k] : read_file(k...) + if text === nothing + push!(records, merge(p, Dict("status" => "drift", "detail" => "file not on the default branch"))) + continue + end + edited, status, detail = edit_compat(text, p["dep"], p["before"], p["after"]) + status == "edited" && (texts[k] = edited) + push!(records, merge(p, Dict("status" => status, "detail" => detail))) + end + return records, texts +end + +"Read `//`, or `nothing` when absent." +read_checkout(dir) = (owner, file) -> (path = joinpath(dir, owner, file); isfile(path) ? read(path, String) : nothing) + +if abspath(PROGRAM_FILE) == @__FILE__ + root, checkouts = ARGS[1], ARGS[2] + records, texts = plan_edits(TOML.parsefile(joinpath(root, "audit.toml")), read_checkout(checkouts)) + for ((owner, file), text) in texts + write(joinpath(checkouts, owner, file), text) + end + open(io -> TOML.print(io, Dict("edit" => records)), joinpath(root, "edits.toml"), "w") + for s in ("edited", "already", "drift") + println(rpad(s, 8), count(r -> r["status"] == s, records)) + end +end diff --git a/scripts/compat-prune/envs.jl b/scripts/compat-prune/envs.jl new file mode 100644 index 0000000..bc0def4 --- /dev/null +++ b/scripts/compat-prune/envs.jl @@ -0,0 +1,77 @@ +# Build the audited environments from the collected files. +# +# usage: julia envs.jl (writes /envs.toml) +# +# Environments: +# joint every package together (what a user of the root package resolves) +# /test the test environment of a package: `test/Project.toml` when the package has +# one, else the package plus its `[targets] test` with the package's own compat +# /docs `docs/Project.toml` plus the package itself (developed by `docs/make.jl`) +# +# The package that owns an environment (every package, for `joint`) is pinned to its audited +# version, so that the files read are exactly the ones resolved. Other packages float within +# the environment's own compat: `CTParser/test` may well require an older `CTModels`. +# Compat cannot name a prerelease (`=1.1.8-beta` does not parse), but `=1.1.8` admits +# `1.1.8-beta`: the pin drops the suffix and `resolve.jl` checks that the audited version is +# the one selected. + +include("common.jl") + +"Keep the compat entries that apply to `deps` (plus `julia`)." +restrict(compat, deps) = Dict(k => v for (k, v) in compat if k == "julia" || haskey(deps, k)) + +function test_env(name, uuid, dir) + file = joinpath(dir, "test", "Project.toml") + if isfile(file) + t = TOML.parsefile(file) + deps = merge(Dict(name => uuid), get(t, "deps", Dict())) + return deps, restrict(get(t, "compat", Dict()), deps) + end + t = TOML.parsefile(joinpath(dir, "Project.toml")) + known = merge(get(t, "deps", Dict()), get(t, "weakdeps", Dict()), get(t, "extras", Dict())) + targets = get(get(t, "targets", Dict()), "test", collect(keys(get(t, "extras", Dict())))) + deps = merge(Dict(name => uuid), Dict(k => known[k] for k in targets)) + return deps, restrict(get(t, "compat", Dict()), deps) +end + +function docs_env(name, uuid, dir) + file = joinpath(dir, "docs", "Project.toml") + isfile(file) || return nothing + t = TOML.parsefile(file) + deps = merge(Dict(name => uuid), get(t, "deps", Dict())) + return deps, restrict(get(t, "compat", Dict()), deps) +end + +""" + build_envs(root) -> Dict + +The audited environments of a collected directory, as written to `envs.toml`: name => +`owner`, `kind`, `deps`, `compat`, `pins` (the packages pinned to their audited version). +""" +function build_envs(root) + index = TOML.parsefile(joinpath(root, "index.toml"))["packages"] + pins = Dict(n => VersionNumber(p["version"]) for (n, p) in index) + envs = Dict{String,Any}() + envs["joint"] = (Dict(n => p["uuid"] for (n, p) in index), Dict{String,Any}()) + for (n, p) in index + dir = joinpath(root, "registered", n) + envs["$n/test"] = test_env(n, p["uuid"], dir) + docs = docs_env(n, p["uuid"], dir) + docs === nothing || (envs["$n/docs"] = docs) + end + out = Dict{String,Any}() + for (name, (deps, compat)) in envs + owner, kind = name == "joint" ? ("", "joint") : split(name, "/") + pinned = Dict(string(k) => string(pins[k]) for k in (kind == "joint" ? keys(pins) : (owner,))) + compat = merge(compat, Dict(k => (v = VersionNumber(v); "=$(v.major).$(v.minor).$(v.patch)") for (k, v) in pinned)) + out[name] = Dict("owner" => owner, "kind" => kind, "deps" => deps, "compat" => compat, "pins" => pinned) + end + return out +end + +if abspath(PROGRAM_FILE) == @__FILE__ + root = ARGS[1] + out = build_envs(root) + open(io -> TOML.print(io, Dict("env" => out)), joinpath(root, "envs.toml"), "w") + println(length(out), " environments: ", join(sort(collect(keys(out))), ", ")) +end diff --git a/scripts/compat-prune/publish.jl b/scripts/compat-prune/publish.jl new file mode 100644 index 0000000..045c051 --- /dev/null +++ b/scripts/compat-prune/publish.jl @@ -0,0 +1,142 @@ +# Publish an audit: one PR per repository with edits, one tracking issue. +# +# usage: julia publish.jl +# +# Run after edit.jl on fresh checkouts (/, cloned with a token allowed +# to push and to open PRs and issues). Idempotent, so that a rerun changes nothing: +# - the tracking issue of is the one whose body carries MARKER, open or +# closed: its body is rewritten, it is reopened when there is something to prune and +# closed (with a comment) when there is nothing left; it is never created twice; +# - each repository has one bot-owned branch, BRANCH, rebuilt from its default branch: it +# is pushed only when its content would change; its open PR is updated, or created; +# when the repository has nothing left to prune, the PR is closed and the branch deleted. +# Every GitHub call goes through `gh` (GH runner, faked in tests); git runs for real. + +include("common.jl") +include("report.jl") + +const BRANCH = "ct/compat-prune" +const ISSUE_TITLE = "[compat] prune report" +const PR_TITLE = "chore(compat): drop [compat] specifiers no supported Julia version selects" +const BOT = ("github-actions[bot]", "41898282+github-actions[bot]@users.noreply.github.com") + +"Run git in `dir` and return its standard output." +git(dir, args...) = read(`git -C $dir $args`, String) + +"Tree of `rev` in `dir`, or `nothing` when it does not exist." +function tree(dir, rev) + t = readchomp(ignorestatus(`git -C $dir rev-parse --verify --quiet $("$rev^{tree}")`)) + return isempty(t) ? nothing : t +end + +""" + prepare_branch(dir, message) -> Bool + +Commit the working-tree changes of checkout `dir` on BRANCH (from its current HEAD) and +tell whether the remote BRANCH differs, i.e. whether a push is needed. +""" +function prepare_branch(dir, message) + git(dir, "checkout", "--quiet", "-B", BRANCH) + git(dir, "add", "--all") + git(dir, "-c", "user.name=$(BOT[1])", "-c", "user.email=$(BOT[2])", "commit", "--quiet", "-m", message) + remote = remote_tree(dir) + return remote != tree(dir, "HEAD") +end + +"Tree of the remote BRANCH of checkout `dir`, or `nothing` when it does not exist." +function remote_tree(dir) + isempty(strip(git(dir, "ls-remote", "--heads", "origin", BRANCH))) && return nothing + git(dir, "fetch", "--quiet", "origin", "$BRANCH:refs/remotes/origin/$BRANCH") + return tree(dir, "refs/remotes/origin/$BRANCH") +end + +"Commit message of the edits of `owner`." +commit_message(owner, edits) = join(vcat("chore(compat): drop [compat] specifiers no supported Julia version selects", "", + sort(["$(e["file"]): $(e["dep"]) = \"$(e["before"])\" → \"$(e["after"])\"" for e in edits if e["owner"] == owner && e["status"] == "edited"])), "\n") + +"Number and URL of the open PR from BRANCH in `repo`, or `nothing`." +function find_pr(repo) + out = strip(gh("pr", "list", "--repo", repo, "--head", BRANCH, "--state", "open", "--json", "number,url", + "--jq", ".[] | \"\\(.number) \\(.url)\"")) + isempty(out) && return nothing + number, url = split(first(split(out, '\n'))) + return (number, url) +end + +"Number and state (`open`/`closed`) of the tracking issue of `repo`, or `nothing`." +function find_issue(repo) + jq = ".[] | select(.pull_request == null) | select((.body // \"\") | contains(\"$MARKER\")) | \"\\(.number) \\(.state)\"" + out = strip(gh("api", "--paginate", "repos/$repo/issues?state=all&per_page=100", "--jq", jq)) + isempty(out) && return nothing + number, state = split(first(split(out, '\n'))) + return (number, state) +end + +""" + publish(root, checkouts, tracking; run_url="") -> Dict + +Publish the audit of `root`, whose edits.toml was produced by edit.jl on `checkouts`. +Returns what was done: `prs` (owner => URL), `pushed`, `closed` (owners), `issue`. +""" +function publish(root, checkouts, tracking; run_url="") + audit = TOML.parsefile(joinpath(root, "audit.toml")) + edits = TOML.parsefile(joinpath(root, "edits.toml"))["edit"] + index = audit["packages"] + done = Dict{String,Any}("prs" => Dict{String,String}(), "pushed" => String[], "closed" => String[]) + with_edits = Set(e["owner"] for e in edits if e["status"] == "edited") + + # The issue first: the PRs link to it. + issue = find_issue(tracking) + if issue === nothing && !isempty(with_edits) + url = strip(gh("issue", "create", "--repo", tracking, "--title", ISSUE_TITLE, + "--body", render_report(audit, edits; run_url))) + issue = (last(split(url, "/")), "open") + end + issue_url = issue === nothing ? "" : "https://github.com/$tracking/issues/$(issue[1])" + + for owner in sort(collect(keys(index))) + repo, base = index[owner]["repo"], index[owner]["default_branch"] + pr = find_pr(repo) + if !(owner in with_edits) + if pr !== nothing + gh("pr", "close", pr[1], "--repo", repo, "--delete-branch", + "--comment", "Nothing left to prune in this repository$(isempty(issue_url) ? "" : " (see $issue_url)").") + push!(done["closed"], owner) + end + continue + end + dir = joinpath(checkouts, owner) + if prepare_branch(dir, commit_message(owner, edits)) + git(dir, "push", "--quiet", "--force", "origin", BRANCH) + push!(done["pushed"], owner) + end + body = render_pr_body(owner, edits; issue_url, run_url) + if pr === nothing + url = strip(gh("pr", "create", "--repo", repo, "--base", base, "--head", BRANCH, "--title", PR_TITLE, "--body", body)) + else + gh("pr", "edit", pr[1], "--repo", repo, "--body", body) + url = pr[2] + end + done["prs"][owner] = url + end + + if issue !== nothing + gh("issue", "edit", issue[1], "--repo", tracking, "--body", render_report(audit, edits; prs=done["prs"], run_url)) + if isempty(with_edits) && issue[2] == "open" + gh("issue", "close", issue[1], "--repo", tracking, "--comment", "Nothing left to prune.") + elseif !isempty(with_edits) && issue[2] == "closed" + gh("issue", "reopen", issue[1], "--repo", tracking) + end + end + done["issue"] = issue_url + return done +end + +if abspath(PROGRAM_FILE) == @__FILE__ + root, checkouts, tracking = ARGS + done = publish(root, checkouts, tracking; run_url=get(ENV, "RUN_URL", "")) + open(io -> TOML.print(io, done), joinpath(root, "published.toml"), "w") + println("issue: ", isempty(done["issue"]) ? "none" : done["issue"]) + for (owner, url) in sort(collect(done["prs"])); println("PR ", rpad(owner, 16), url, owner in done["pushed"] ? " (pushed)" : ""); end + for owner in done["closed"]; println("PR ", rpad(owner, 16), "closed"); end +end diff --git a/scripts/compat-prune/report.jl b/scripts/compat-prune/report.jl new file mode 100644 index 0000000..e85680a --- /dev/null +++ b/scripts/compat-prune/report.jl @@ -0,0 +1,159 @@ +# Render the audit as the Markdown body of the tracking issue. +# +# usage: julia report.jl [] (writes /report.md) +# +# , written by the publishing step, maps a package name to the URL of its PR. The +# output is deterministic (everything sorted) so that an unchanged audit gives an unchanged +# body, and the hidden marker lets the publishing step find the issue again. + +include("common.jl") + +const MARKER = "" +const WORKFLOW_URL = "https://github.com/control-toolbox/CTActions/blob/main/.github/workflows/compat-prune.yml" + +"Markdown table from a header and rows of strings." +table(header, rows) = join(["| " * join(header, " | ") * " |", "| " * join(fill("---", length(header)), " | ") * " |", + ("| " * join(r, " | ") * " |" for r in rows)...], "\n") + +"`0.12.3 on 1.12, 1.13; 0.9.18 on 1.10`: the versions selected for the kept lanes, per Julia version." +function selected(evidence) + on = Dict{String,Set{String}}() + for list in values(evidence), e in list + env_julia, v = split(e, "=") + push!(get!(on, v, Set{String}()), last(split(env_julia, "@"))) + end + vs = sort(collect(keys(on)); by=VersionNumber, rev=true) + join(["$v on $(join(sort(collect(on[v])), ", "))" for v in vs], "; ") +end + +code(s) = "`$s`" +entry(r) = "$(r["owner"]) $(code(r["file"])) $(code(r["dep"]))" + +"Short form of a registry state: `tree 0123456789ab`." +short_state(s) = (m = match(r"^(\w+) ([0-9a-f]{12})[0-9a-f]*$", s); m === nothing ? s : "$(m[1]) $(m[2])") + +""" + render_record(record, others) -> String + +The `## Audit` section: environments resolved per Julia version, entries examined and their +status, registry snapshot, and (folded) the entries left as they are, with why. +""" +function render_record(record, others) + cov = [[c["julia"], "$(c["resolved"]) / $(c["total"])" * (isempty(c["failed"]) ? "" : " (failed: $(join(c["failed"], ", ")))")] + for c in record["coverage"]] + e = record["entries"] + entries = "$(e["examined"]) multi-specifier `[compat]` entries examined: $(e["prune"]) with lanes to prune, " * + "$(e["rejected"]) rejected, $(e["blocked"]) blocked, $(e["keep"]) kept (every lane in use), " * + "$(e["idle"]) idle (resolved nowhere they apply), $(e["withheld"]) withheld." + regs = "Registries: " * join(["$(r["name"]) ($(short_state(r["state"])))" for r in record["registries"]], ", ") * "." + left = sort([r for r in others if r["status"] in ("keep", "idle")]; by=entry_key) + parts = ["## Audit", table(["Julia", "environments resolved"], cov), entries, regs] + if !isempty(left) + why(r) = r["status"] == "keep" ? "every lane in use: " * selected(r["evidence"]) : "not resolved where the entry applies" + push!(parts, "
Entries left as they are ($(length(left)))\n\n" * + table(["entry", "compat", "status", "why"], [[entry(r), code(r["before"]), r["status"], why(r)] for r in left]) * + "\n\n
") + end + return join(parts, "\n\n") +end + +""" + render_report(audit, edits; prs=Dict(), run_url="") -> String + +Markdown body of the tracking issue: what can be pruned (per repository, with its PR), what +waits for a release, what drifted, what was rejected or withheld, and the blockers. +""" +function render_report(audit, edits; prs=Dict(), run_url="") + by_status(s) = sort([e for e in edits if e["status"] == s]; by=entry_key) + edited, already, drift = by_status("edited"), by_status("already"), by_status("drift") + rejected = sort(get(audit, "rejected", []); by=entry_key) + withheld = sort([r for r in get(audit, "other", []) if r["status"] == "withheld"]; by=entry_key) + blocked = get(audit, "blocked", []) + out = String[MARKER] + + run = isempty(run_url) ? "" : " ([run]($run_url))" + push!(out, """ + Audit of the `[compat]` entries of the control-toolbox packages, by the + [`compat-prune`]($WORKFLOW_URL) workflow on $(audit["date"])$run, for Julia \ + $(join(audit["julia"], ", ")). This body is rewritten by every run: do not edit it. + + A prune drops `[compat]` specifiers that no environment selects, on no Julia version, and + leaves every resolved manifest (joint, tests and docs of every package) unchanged. PRs can + therefore be merged in any order, and need no release of their own.""") + + haskey(audit, "record") && push!(out, render_record(audit["record"], get(audit, "other", []))) + + push!(out, "## Summary\n\n" * table(["", "entries"], [ + ["to prune (open PRs)", string(length(edited))], + ["merged, awaiting release", string(length(already))], + ["drift (not edited)", string(length(drift))], + ["rejected by validation", string(length(rejected))], + ["withheld", string(length(withheld))], + ["blocked (information)", string(length(unique(entry_key.(blocked))))], + ])) + + if !isempty(edited) + sections = String["## To prune"] + for owner in sort(unique(e["owner"] for e in edited)) + pr = haskey(prs, owner) ? " — $(prs[owner])" : "" + rows = [[code(e["file"]), code(e["dep"]), code(e["before"]), code(e["after"]), selected(e["evidence"])] + for e in edited if e["owner"] == owner] + push!(sections, "### $owner$pr\n\n" * table(["file", "dependency", "before", "after", "selected"], rows)) + end + push!(out, join(sections, "\n\n")) + end + isempty(already) || push!(out, "## Merged, awaiting release\n\n" * + table(["entry", "after"], [[entry(e), code(e["after"])] for e in already])) + isempty(drift) || push!(out, "## Drift\n\nThe default branch differs from the registered version: not edited.\n\n" * + table(["entry", "audited", "detail"], [[entry(e), code(e["before"]), e["detail"]] for e in drift])) + isempty(rejected) || push!(out, "## Rejected\n\n" * + table(["entry", "where", "reason"], [[entry(r), r["where"], replace(r["reason"], "\n" => " ", "|" => "\\|")] for r in rejected])) + isempty(withheld) || push!(out, "## Withheld\n\n$(uppercasefirst(first(withheld)["reason"])).\n\n" * + table(["entry", "would drop"], [[entry(r), join(code.(r["removed"]), ", ")] for r in withheld])) + if !isempty(blocked) + rows = Vector{String}[] + for k in sort(unique(entry_key.(blocked))) + probes = [b for b in blocked if entry_key(b) == k] + names = sort(unique(reduce(vcat, [b["blockers"] for b in probes]))) + b = first(probes) + push!(rows, [entry(b), code(b["newest"]), isempty(names) ? "resolver preference" : join(names, ", ")]) + end + push!(out, "## Blocked\n\nThe newest specifier is never selected: information only, nothing is edited.\n\n" * + table(["entry", "newest", "capped by"], rows)) + end + + versions = [[n, p["version"], "[$(p["repo"])](https://github.com/$(p["repo"]))"] for (n, p) in sort(collect(audit["packages"]); by=first)] + push!(out, "## Audited versions\n\n" * table(["package", "version", "repository"], versions)) + return join(out, "\n\n") * "\n" +end + +""" + render_pr_body(owner, edits; issue_url="", run_url="") -> String + +Body of the PR of `owner`: its edited entries, why they can go, where the audit is. +""" +function render_pr_body(owner, edits; issue_url="", run_url="") + mine = sort([e for e in edits if e["owner"] == owner && e["status"] == "edited"]; by=entry_key) + rows = [[code(e["file"]), code(e["dep"]), code(e["before"]), code(e["after"]), selected(e["evidence"])] for e in mine] + links = String[] + isempty(issue_url) || push!(links, "tracking issue: $issue_url") + isempty(run_url) || push!(links, "[audit run]($run_url)") + return join([MARKER, """ + Drops the `[compat]` specifiers that no supported Julia version selects, in any + environment of the control-toolbox packages (joint, tests and docs of every package). + Without them every audited manifest is unchanged: this PR changes no version that + these environments resolve, needs no release of its own, and can be merged in any + order with the PRs of the other packages.""", + table(["file", "dependency", "before", "after", "selected"], rows), + "Opened by the [`compat-prune`]($WORKFLOW_URL) workflow" * (isempty(links) ? "" : " ($(join(links, ", ")))") * + ". The branch is rewritten by every run: push nothing to it."], "\n\n") * "\n" +end + +if abspath(PROGRAM_FILE) == @__FILE__ + root = ARGS[1] + prs = length(ARGS) ≥ 2 ? TOML.parsefile(ARGS[2]) : Dict() + audit = TOML.parsefile(joinpath(root, "audit.toml")) + edits = TOML.parsefile(joinpath(root, "edits.toml"))["edit"] + write(joinpath(root, "report.md"), render_report(audit, edits; prs, run_url=get(ENV, "RUN_URL", ""))) + println("report written to ", joinpath(root, "report.md")) +end diff --git a/scripts/compat-prune/resolve.jl b/scripts/compat-prune/resolve.jl new file mode 100644 index 0000000..4651c23 --- /dev/null +++ b/scripts/compat-prune/resolve.jl @@ -0,0 +1,38 @@ +# Resolve every audited environment on the running Julia. +# +# usage: julia resolve.jl [ ] +# +# Without prunes this is the baseline. With prunes, they are simulated all together (see +# `simulate` and `narrowings` in common.jl); environments that failed at baseline are copied +# as they are, there is nothing to compare them with. + +include("common.jl") + +""" + resolve_all(root; prunes=[], baseline=Dict(), sim=simulate) -> Dict + +Result of every environment of a collected directory, the baseline when `prunes` is empty. +""" +function resolve_all(root; prunes=[], baseline=Dict(), sim=simulate) + envs, audited, uuids = load_collected(root) + results = Dict{String,Any}() + for name in sort(collect(keys(envs))) + if isempty(prunes) + r = sim(name, envs[name], Dict(), uuids) + else + base = baseline[name] + r = base["ok"] ? sim(name, envs[name], narrowings(prunes, name, base["versions"], audited), uuids) : base + end + results[name] = r + println(VERSION, " ", rpad(name, 24), r["ok"] ? "ok" : "UNSAT") + end + return results +end + +if abspath(PROGRAM_FILE) == @__FILE__ + root, outfile = ARGS[1], ARGS[2] + prunes = length(ARGS) ≥ 4 ? TOML.parsefile(ARGS[3])["prune"] : [] + baseline = length(ARGS) ≥ 4 ? TOML.parsefile(ARGS[4])["env"] : Dict() + results = resolve_all(root; prunes, baseline) + open(io -> TOML.print(io, Dict("julia" => string(VERSION), "env" => results)), outfile, "w") +end diff --git a/scripts/compat-prune/test/fixtures/collected/default/Alpha/Project.toml b/scripts/compat-prune/test/fixtures/collected/default/Alpha/Project.toml new file mode 100644 index 0000000..1b46810 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/default/Alpha/Project.toml @@ -0,0 +1,12 @@ +name = "Alpha" +uuid = "aaaaaaaa-0000-4000-8000-000000000001" +version = "0.3.2" + +[deps] +Beta = "bbbbbbbb-0000-4000-8000-000000000002" +Dep1 = "11111111-0000-4000-8000-000000000001" + +[compat] +Beta = "1" +Dep1 = "0.5, 0.6, 0.7" # lanes kept by CompatHelper +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/collected/default/Alpha/docs/Project.toml b/scripts/compat-prune/test/fixtures/collected/default/Alpha/docs/Project.toml new file mode 100644 index 0000000..c5518e8 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/default/Alpha/docs/Project.toml @@ -0,0 +1,5 @@ +[deps] +Doc = "33333333-0000-4000-8000-000000000003" + +[compat] +Doc = "0.9, 1" diff --git a/scripts/compat-prune/test/fixtures/collected/default/Beta/Project.toml b/scripts/compat-prune/test/fixtures/collected/default/Beta/Project.toml new file mode 100644 index 0000000..4033244 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/default/Beta/Project.toml @@ -0,0 +1,24 @@ +name = "Beta" +uuid = "bbbbbbbb-0000-4000-8000-000000000002" +version = "1.2.0-beta" + +[deps] +Dep1 = "11111111-0000-4000-8000-000000000001" + +[weakdeps] +Weak = "44444444-0000-4000-8000-000000000004" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Tool = "22222222-0000-4000-8000-000000000002" +Unused = "55555555-0000-4000-8000-000000000005" + +[compat] +Dep1 = "0.7" +Tool = "2" +Unused = "9" +Weak = "0.1, 0.2" +julia = "1.10" + +[targets] +test = ["Test", "Tool", "Weak"] diff --git a/scripts/compat-prune/test/fixtures/collected/index.toml b/scripts/compat-prune/test/fixtures/collected/index.toml new file mode 100644 index 0000000..f5d57ce --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/index.toml @@ -0,0 +1,19 @@ +julia = "1.13.1" + +[packages.Alpha] +uuid = "aaaaaaaa-0000-4000-8000-000000000001" +version = "0.3.1" +repo = "example-org/Alpha.jl" +tree = "1111111111111111111111111111111111111111" +default_branch = "main" +files = ["Project.toml", "test/Project.toml", "docs/Project.toml"] +default_files = ["Project.toml", "docs/Project.toml"] + +[packages.Beta] +uuid = "bbbbbbbb-0000-4000-8000-000000000002" +version = "1.2.0-beta" +repo = "example-org/Beta.jl" +tree = "2222222222222222222222222222222222222222" +default_branch = "dev" +files = ["Project.toml"] +default_files = ["Project.toml"] diff --git a/scripts/compat-prune/test/fixtures/collected/registered/Alpha/Project.toml b/scripts/compat-prune/test/fixtures/collected/registered/Alpha/Project.toml new file mode 100644 index 0000000..0dbbcb2 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/registered/Alpha/Project.toml @@ -0,0 +1,12 @@ +name = "Alpha" +uuid = "aaaaaaaa-0000-4000-8000-000000000001" +version = "0.3.1" + +[deps] +Beta = "bbbbbbbb-0000-4000-8000-000000000002" +Dep1 = "11111111-0000-4000-8000-000000000001" + +[compat] +Beta = "1" +Dep1 = "0.5, 0.6, 0.7" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/collected/registered/Alpha/docs/Project.toml b/scripts/compat-prune/test/fixtures/collected/registered/Alpha/docs/Project.toml new file mode 100644 index 0000000..c5518e8 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/registered/Alpha/docs/Project.toml @@ -0,0 +1,5 @@ +[deps] +Doc = "33333333-0000-4000-8000-000000000003" + +[compat] +Doc = "0.9, 1" diff --git a/scripts/compat-prune/test/fixtures/collected/registered/Alpha/test/Project.toml b/scripts/compat-prune/test/fixtures/collected/registered/Alpha/test/Project.toml new file mode 100644 index 0000000..8b24135 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/registered/Alpha/test/Project.toml @@ -0,0 +1,7 @@ +[deps] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Tool = "22222222-0000-4000-8000-000000000002" + +[compat] +Tool = "1, 2" +Unlisted = "3" diff --git a/scripts/compat-prune/test/fixtures/collected/registered/Beta/Project.toml b/scripts/compat-prune/test/fixtures/collected/registered/Beta/Project.toml new file mode 100644 index 0000000..1a314c0 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/collected/registered/Beta/Project.toml @@ -0,0 +1,24 @@ +name = "Beta" +uuid = "bbbbbbbb-0000-4000-8000-000000000002" +version = "1.2.0-beta" + +[deps] +Dep1 = "11111111-0000-4000-8000-000000000001" + +[weakdeps] +Weak = "44444444-0000-4000-8000-000000000004" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Tool = "22222222-0000-4000-8000-000000000002" +Unused = "55555555-0000-4000-8000-000000000005" + +[compat] +Dep1 = "0.6, 0.7" +Tool = "2" +Unused = "9" +Weak = "0.1, 0.2" +julia = "1.10" + +[targets] +test = ["Test", "Tool", "Weak"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.10.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.10.toml new file mode 100644 index 0000000..e902fe4 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.10.toml @@ -0,0 +1,699 @@ +julia = "1.10.11" + +[env."CTParser/docs"] +ok = true + + [env."CTParser/docs".versions] + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + MLStyle = "0.4.17" + Markdown = "stdlib" + Printf = "stdlib" + Test = "stdlib" + Random = "stdlib" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "stdlib" + Documenter = "1.19.0" + Unicode = "stdlib" + +[env."CTModels/docs"] +ok = true + + [env."CTModels/docs".versions] + RecipesBase = "1.3.4" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + DocumenterVitepress = "0.3.8" + Logging = "stdlib" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + Random = "stdlib" + Plots = "1.41.7" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + JET = "0.9.18" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + LiveServer = "1.6.0" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTLie/test"] +ok = true + + [env."CTLie/test".versions] + ForwardDiff = "1.4.6" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + Markdown = "stdlib" + CTLie = "0.2.3" + LinearAlgebra = "stdlib" + JET = "0.9.18" + Mooncake = "0.5.64" + Printf = "stdlib" + Test = "stdlib" + Random = "stdlib" + BenchmarkTools = "1.8.0" + Aqua = "0.8.18" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Logging = "stdlib" + Reexport = "1.2.2" + CUDA = "6.4.2" + DifferentiationInterface = "0.7.21" + GPUArraysCore = "0.2.1" + SparseArrays = "1.10.0" + Unicode = "stdlib" + MacroTools = "0.5.16" + KernelAbstractions = "0.9.45" + +[env."CTDirect/test"] +ok = true + + [env."CTDirect/test".versions] + RecipesBase = "1.3.4" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + SparseConnectivityTracer = "1.2.4" + Logging = "stdlib" + GPUArraysCore = "0.2.1" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + CUDSS = "0.8.1" + Random = "stdlib" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.3.1" + MadNLP = "0.10.1" + SplitApplyCombine = "1.3.0" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + AMDGPU = "2.8.0" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTDirect/docs"] +ok = true + + [env."CTDirect/docs".versions] + ExaModels = "0.12.1" + CTDirect = "1.1.8-beta" + CTParser = "0.9.5-beta" + ForwardDiff = "1.4.6" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + RecipesBase = "1.3.4" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + Markdown = "stdlib" + MLStyle = "0.4.17" + LinearAlgebra = "stdlib" + Printf = "stdlib" + Test = "stdlib" + Random = "stdlib" + SparseConnectivityTracer = "1.2.4" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocumenterMermaid = "0.2.1" + ADNLPModels = "0.8.15" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "stdlib" + Reexport = "1.2.2" + Documenter = "1.19.0" + DifferentiationInterface = "0.7.21" + SparseArrays = "1.10.0" + Unicode = "stdlib" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.19.8" + KernelAbstractions = "0.9.45" + +[env."CTFlows/docs"] +ok = true + + [env."CTFlows/docs".versions] + RecipesBase = "1.3.4" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + DocumenterVitepress = "0.3.8" + Logging = "stdlib" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + OrdinaryDiffEqRosenbrock = "2.7.5" + Random = "stdlib" + Plots = "1.41.7" + KernelAbstractions = "0.9.45" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + Makie = "0.24.16" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + OrdinaryDiffEqTsit5 = "2.1.5" + CairoMakie = "0.15.16" + LiveServer = "1.6.0" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."OptimalControl/docs"] +ok = true + + [env."OptimalControl/docs".versions] + RecipesBase = "1.3.4" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + NLPModelsKnitro = "0.10.6" + LinearAlgebra = "stdlib" + Printf = "stdlib" + NonlinearSolve = "4.32.0" + SparseConnectivityTracer = "1.2.4" + DocumenterVitepress = "0.3.8" + Logging = "stdlib" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + OrdinaryDiffEqVerner = "2.4.2" + OrdinaryDiffEqRosenbrock = "2.7.5" + CUDSS = "0.7.0" + Random = "stdlib" + NLPModelsIpopt = "0.11.3" + Plots = "1.41.7" + ADNLPModels = "0.8.15" + CUDA = "6.2.0" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + Literate = "2.21.0" + OrdinaryDiffEq = "7.8.1" + CTLie = "0.2.3" + UnoSolver = "0.3.8" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + MadNCL = "0.2.3" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + OrdinaryDiffEqTsit5 = "2.1.5" + CairoMakie = "0.15.16" + LiveServer = "1.6.0" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTModels/test"] +ok = true + + [env."CTModels/test".versions] + RecipesBase = "1.3.4" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + Aqua = "0.8.18" + Logging = "stdlib" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + Random = "stdlib" + BenchmarkTools = "1.8.0" + Plots = "1.41.7" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + JET = "0.9.18" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + CairoMakie = "0.15.16" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTLie/docs"] +ok = true + + [env."CTLie/docs".versions] + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Markdown = "stdlib" + CTLie = "0.2.3" + LinearAlgebra = "stdlib" + Printf = "stdlib" + Test = "stdlib" + DocumenterInterLinks = "1.1.0" + Random = "stdlib" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + DocumenterVitepress = "0.3.8" + ADTypes = "1.24.0" + Logging = "stdlib" + Documenter = "1.19.0" + DifferentiationInterface = "0.7.21" + LiveServer = "1.6.0" + Unicode = "stdlib" + MacroTools = "0.5.16" + +[env."CTSolvers/test"] +ok = true + + [env."CTSolvers/test".versions] + RecipesBase = "1.3.4" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + SparseConnectivityTracer = "1.2.4" + Aqua = "0.8.18" + Logging = "stdlib" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + CUDSS = "0.8.1" + Random = "stdlib" + BenchmarkTools = "1.8.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.4.1" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + UnoSolver = "0.3.8" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + Enzyme = "0.13.216" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + MadNCL = "0.2.3" + JET = "0.9.18" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTParser/test"] +ok = true + + [env."CTParser/test".versions] + ExaModels = "0.12.1" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + CTBase = "0.29.3" + RecipesBase = "1.3.4" + MLStyle = "0.4.17" + Markdown = "stdlib" + SolverCore = "0.3.10" + LinearAlgebra = "stdlib" + Printf = "stdlib" + CUDSS = "0.8.1" + Test = "stdlib" + Random = "stdlib" + BenchmarkTools = "1.8.0" + Aqua = "0.8.18" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "stdlib" + Reexport = "1.2.2" + CUDA = "6.4.2" + MadNLP = "0.10.1" + Interpolations = "0.16.3" + GPUArraysCore = "0.2.1" + SparseArrays = "1.10.0" + Unicode = "stdlib" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.18.0" + KernelAbstractions = "0.9.45" + MadNLPGPU = "0.10.2" + +[env."OptimalControl/test"] +ok = true + + [env."OptimalControl/test".versions] + RecipesBase = "1.3.4" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + NonlinearSolve = "4.32.0" + SparseConnectivityTracer = "1.2.4" + Logging = "stdlib" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + OrdinaryDiffEqVerner = "2.4.2" + OrdinaryDiffEqRosenbrock = "2.7.5" + CUDSS = "0.7.0" + Random = "stdlib" + BenchmarkTools = "1.8.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.2.0" + MadNLP = "0.10.1" + SplitApplyCombine = "1.3.0" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + OrdinaryDiffEq = "7.8.1" + CTLie = "0.2.3" + UnoSolver = "0.3.8" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + MadNCL = "0.2.3" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTBase/test"] +ok = true + + [env."CTBase/test".versions] + RecipesBase = "1.3.4" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + Aqua = "0.8.18" + Logging = "stdlib" + Colors = "0.13.3" + MacroTools = "0.5.16" + OrderedCollections = "2.0.3" + Markdown = "stdlib" + Random = "stdlib" + BenchmarkTools = "1.8.0" + Plots = "1.41.7" + Coverage = "1.8.2" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Makie = "0.24.16" + JET = "0.9.18" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env.joint] +ok = true + + [env.joint.versions] + ExaModels = "0.12.1" + CTDirect = "1.1.8-beta" + CTParser = "0.9.5-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + RecipesBase = "1.3.4" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + MLStyle = "0.4.17" + Markdown = "stdlib" + CTLie = "0.2.3" + LinearAlgebra = "stdlib" + Printf = "stdlib" + Random = "stdlib" + SparseConnectivityTracer = "1.2.4" + CTSolvers = "0.5.8" + ADNLPModels = "0.8.15" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + Reexport = "1.2.2" + DifferentiationInterface = "0.7.21" + GPUArraysCore = "0.2.1" + SparseArrays = "1.10.0" + Unicode = "stdlib" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.19.8" + KernelAbstractions = "0.9.45" + +[env."CTBase/docs"] +ok = true + + [env."CTBase/docs".versions] + RecipesBase = "1.3.4" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + Printf = "stdlib" + DocumenterVitepress = "0.3.8" + Logging = "stdlib" + Colors = "0.13.3" + MacroTools = "0.5.16" + OrderedCollections = "2.0.3" + Markdown = "stdlib" + Random = "stdlib" + Plots = "1.41.7" + Coverage = "1.8.2" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Makie = "0.24.16" + JET = "0.9.18" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + LiveServer = "1.5.0" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTSolvers/docs"] +ok = true + + [env."CTSolvers/docs".versions] + RecipesBase = "1.3.4" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + NLPModelsKnitro = "0.10.6" + LinearAlgebra = "stdlib" + Printf = "stdlib" + SparseConnectivityTracer = "1.2.4" + DocumenterVitepress = "0.3.8" + Logging = "stdlib" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + OrdinaryDiffEqVerner = "2.4.2" + CUDSS = "0.8.1" + Random = "stdlib" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.4.1" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + UnoSolver = "0.3.8" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + Enzyme = "0.13.216" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + MadNCL = "0.2.3" + JET = "0.9.18" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + OrdinaryDiffEqTsit5 = "2.1.5" + LiveServer = "1.6.0" + SparseArrays = "1.10.0" + Unicode = "stdlib" + +[env."CTFlows/test"] +ok = true + + [env."CTFlows/test".versions] + RecipesBase = "1.3.4" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "stdlib" + DiffEqGPU = "3.21.5" + Printf = "stdlib" + NonlinearSolve = "4.32.0" + Aqua = "0.8.18" + Logging = "stdlib" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "stdlib" + Random = "stdlib" + Plots = "1.41.7" + CUDA = "6.4.2" + KernelAbstractions = "0.9.45" + CTLie = "0.2.3" + Mooncake = "0.5.64" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + Makie = "0.24.16" + Test = "stdlib" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + CairoMakie = "0.15.16" + SparseArrays = "1.10.0" + Unicode = "stdlib" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.12.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.12.toml new file mode 100644 index 0000000..440566b --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.12.toml @@ -0,0 +1,699 @@ +julia = "1.12.6" + +[env."CTParser/docs"] +ok = true + + [env."CTParser/docs".versions] + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Printf = "1.11.0" + Test = "1.11.0" + Random = "1.11.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "1.11.0" + Documenter = "1.19.0" + Unicode = "1.11.0" + +[env."CTModels/docs"] +ok = true + + [env."CTModels/docs".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Random = "1.11.0" + Plots = "1.41.7" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + LiveServer = "1.6.0" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTLie/test"] +ok = true + + [env."CTLie/test".versions] + ForwardDiff = "1.4.6" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + Markdown = "1.11.0" + CTLie = "0.2.3" + LinearAlgebra = "1.12.0" + JET = "0.12.3" + Mooncake = "0.5.64" + Printf = "1.11.0" + Test = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Aqua = "0.8.18" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Logging = "1.11.0" + Reexport = "1.2.2" + CUDA = "6.4.2" + DifferentiationInterface = "0.7.21" + GPUArraysCore = "0.2.1" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + KernelAbstractions = "0.9.45" + +[env."CTDirect/test"] +ok = true + + [env."CTDirect/test".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + SparseConnectivityTracer = "1.2.4" + Logging = "1.11.0" + GPUArraysCore = "0.2.1" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + CUDSS = "0.8.1" + Random = "1.11.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.3.1" + MadNLP = "0.10.1" + SplitApplyCombine = "1.3.0" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + AMDGPU = "2.8.0" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTDirect/docs"] +ok = true + + [env."CTDirect/docs".versions] + ExaModels = "0.12.1" + CTDirect = "1.1.8-beta" + CTParser = "0.9.5-beta" + ForwardDiff = "1.4.6" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + Markdown = "1.11.0" + MLStyle = "0.4.17" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + Test = "1.11.0" + Random = "1.11.0" + SparseConnectivityTracer = "1.2.4" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocumenterMermaid = "0.2.1" + ADNLPModels = "0.8.15" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "1.11.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + DifferentiationInterface = "0.7.21" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.19.8" + KernelAbstractions = "0.9.45" + +[env."CTFlows/docs"] +ok = true + + [env."CTFlows/docs".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqRosenbrock = "2.7.5" + Random = "1.11.0" + Plots = "1.41.7" + KernelAbstractions = "0.9.45" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + Makie = "0.24.16" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + OrdinaryDiffEqTsit5 = "2.1.5" + CairoMakie = "0.15.16" + LiveServer = "1.6.0" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."OptimalControl/docs"] +ok = true + + [env."OptimalControl/docs".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + NLPModelsKnitro = "0.10.6" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + NonlinearSolve = "4.32.0" + SparseConnectivityTracer = "1.2.4" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqVerner = "2.4.2" + OrdinaryDiffEqRosenbrock = "2.7.5" + CUDSS = "0.7.0" + Random = "1.11.0" + NLPModelsIpopt = "0.11.3" + Plots = "1.41.7" + ADNLPModels = "0.8.15" + CUDA = "6.2.0" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + Literate = "2.21.0" + OrdinaryDiffEq = "7.8.1" + CTLie = "0.2.3" + UnoSolver = "0.3.8" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + MadNCL = "0.2.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + OrdinaryDiffEqTsit5 = "2.1.5" + CairoMakie = "0.15.16" + LiveServer = "1.6.0" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTModels/test"] +ok = true + + [env."CTModels/test".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + Aqua = "0.8.18" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Plots = "1.41.7" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + CairoMakie = "0.15.16" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTLie/docs"] +ok = true + + [env."CTLie/docs".versions] + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Markdown = "1.11.0" + CTLie = "0.2.3" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + Test = "1.11.0" + DocumenterInterLinks = "1.1.0" + Random = "1.11.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + DocumenterVitepress = "0.3.8" + ADTypes = "1.24.0" + Logging = "1.11.0" + Documenter = "1.19.0" + DifferentiationInterface = "0.7.21" + LiveServer = "1.6.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + +[env."CTSolvers/test"] +ok = true + + [env."CTSolvers/test".versions] + RecipesBase = "1.4.0" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + SparseConnectivityTracer = "1.2.4" + Aqua = "0.8.18" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + CUDSS = "0.8.1" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.4.1" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + UnoSolver = "0.3.8" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + Enzyme = "0.13.216" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + MadNCL = "0.2.3" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTParser/test"] +ok = true + + [env."CTParser/test".versions] + ExaModels = "0.12.1" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + CTBase = "0.29.3" + RecipesBase = "1.4.0" + MLStyle = "0.4.17" + Markdown = "1.11.0" + SolverCore = "0.3.10" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + CUDSS = "0.8.1" + Test = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Aqua = "0.8.18" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "1.11.0" + Reexport = "1.2.2" + CUDA = "6.4.2" + MadNLP = "0.10.1" + Interpolations = "0.16.3" + GPUArraysCore = "0.2.1" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.18.0" + KernelAbstractions = "0.9.45" + MadNLPGPU = "0.10.2" + +[env."OptimalControl/test"] +ok = true + + [env."OptimalControl/test".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + NonlinearSolve = "4.32.0" + SparseConnectivityTracer = "1.2.4" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqVerner = "2.4.2" + OrdinaryDiffEqRosenbrock = "2.7.5" + CUDSS = "0.7.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.2.0" + MadNLP = "0.10.1" + SplitApplyCombine = "1.3.0" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + OrdinaryDiffEq = "7.8.1" + CTLie = "0.2.3" + UnoSolver = "0.3.8" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + MadNCL = "0.2.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTBase/test"] +ok = true + + [env."CTBase/test".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + Aqua = "0.8.18" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + OrderedCollections = "2.0.3" + Markdown = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Plots = "1.41.7" + Coverage = "1.8.2" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env.joint] +ok = true + + [env.joint.versions] + ExaModels = "0.12.1" + CTDirect = "1.1.8-beta" + CTParser = "0.9.5-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + MLStyle = "0.4.17" + Markdown = "1.11.0" + CTLie = "0.2.3" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + Random = "1.11.0" + SparseConnectivityTracer = "1.2.4" + CTSolvers = "0.5.8" + ADNLPModels = "0.8.15" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + Reexport = "1.2.2" + DifferentiationInterface = "0.7.21" + GPUArraysCore = "0.2.1" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.19.8" + KernelAbstractions = "0.9.45" + +[env."CTBase/docs"] +ok = true + + [env."CTBase/docs".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + OrderedCollections = "2.0.3" + Markdown = "1.11.0" + Random = "1.11.0" + Plots = "1.41.7" + Coverage = "1.8.2" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + LiveServer = "1.5.0" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTSolvers/docs"] +ok = true + + [env."CTSolvers/docs".versions] + RecipesBase = "1.4.0" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + NLPModelsKnitro = "0.10.6" + LinearAlgebra = "1.12.0" + Printf = "1.11.0" + SparseConnectivityTracer = "1.2.4" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqVerner = "2.4.2" + CUDSS = "0.8.1" + Random = "1.11.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.4.1" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + UnoSolver = "0.3.8" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + Enzyme = "0.13.216" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + MadNCL = "0.2.3" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + OrdinaryDiffEqTsit5 = "2.1.5" + LiveServer = "1.6.0" + SparseArrays = "1.12.0" + Unicode = "1.11.0" + +[env."CTFlows/test"] +ok = true + + [env."CTFlows/test".versions] + RecipesBase = "1.4.0" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.12.0" + DiffEqGPU = "3.21.5" + Printf = "1.11.0" + NonlinearSolve = "4.32.0" + Aqua = "0.8.18" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Random = "1.11.0" + Plots = "1.41.7" + CUDA = "6.4.2" + KernelAbstractions = "0.9.45" + CTLie = "0.2.3" + Mooncake = "0.5.64" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + Makie = "0.24.16" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + CairoMakie = "0.15.16" + SparseArrays = "1.12.0" + Unicode = "1.11.0" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.13.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.13.toml new file mode 100644 index 0000000..2bb8d9e --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/baseline-1.13.toml @@ -0,0 +1,699 @@ +julia = "1.13.1" + +[env."CTParser/docs"] +ok = true + + [env."CTParser/docs".versions] + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Printf = "1.11.0" + Test = "1.11.0" + Random = "1.11.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "1.11.0" + Documenter = "1.19.0" + Unicode = "1.11.0" + +[env."CTModels/docs"] +ok = true + + [env."CTModels/docs".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Random = "1.11.0" + Plots = "1.41.7" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + SparseArrays = "1.13.0" + LiveServer = "1.6.0" + Unicode = "1.11.0" + +[env."CTLie/test"] +ok = true + + [env."CTLie/test".versions] + ForwardDiff = "1.4.6" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + Markdown = "1.11.0" + CTLie = "0.2.3" + LinearAlgebra = "1.13.0" + JET = "0.12.3" + Mooncake = "0.5.64" + Printf = "1.11.0" + Test = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Aqua = "0.8.18" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Logging = "1.11.0" + Reexport = "1.2.2" + CUDA = "6.4.2" + DifferentiationInterface = "0.7.21" + GPUArraysCore = "0.2.1" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + KernelAbstractions = "0.9.45" + +[env."CTDirect/test"] +ok = true + + [env."CTDirect/test".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + SparseConnectivityTracer = "1.2.4" + Logging = "1.11.0" + GPUArraysCore = "0.2.1" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + CUDSS = "0.8.1" + Random = "1.11.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.3.1" + MadNLP = "0.10.1" + SplitApplyCombine = "1.3.0" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + AMDGPU = "2.8.0" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + +[env."CTDirect/docs"] +ok = true + + [env."CTDirect/docs".versions] + ExaModels = "0.12.1" + CTDirect = "1.1.8-beta" + CTParser = "0.9.5-beta" + ForwardDiff = "1.4.6" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + MLStyle = "0.4.17" + CommonSolve = "0.2.14" + Markdown = "1.11.0" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + Test = "1.11.0" + Random = "1.11.0" + SparseConnectivityTracer = "1.2.4" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocumenterMermaid = "0.2.1" + ADNLPModels = "0.8.15" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "1.11.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + DifferentiationInterface = "0.7.21" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.19.8" + KernelAbstractions = "0.9.45" + +[env."CTFlows/docs"] +ok = true + + [env."CTFlows/docs".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqRosenbrock = "2.7.5" + Random = "1.11.0" + Plots = "1.41.7" + KernelAbstractions = "0.9.45" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + Makie = "0.24.16" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + SparseArrays = "1.13.0" + LiveServer = "1.6.0" + Unicode = "1.11.0" + +[env."OptimalControl/docs"] +ok = true + + [env."OptimalControl/docs".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + NLPModelsKnitro = "0.10.6" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + NonlinearSolve = "4.32.0" + SparseConnectivityTracer = "1.2.4" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqVerner = "2.4.2" + OrdinaryDiffEqRosenbrock = "2.7.5" + CUDSS = "0.7.0" + Random = "1.11.0" + NLPModelsIpopt = "0.11.3" + Plots = "1.41.7" + ADNLPModels = "0.8.15" + CUDA = "6.2.0" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + Literate = "2.21.0" + OrdinaryDiffEq = "7.8.1" + CTLie = "0.2.3" + UnoSolver = "0.3.8" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + MadNCL = "0.2.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + SparseArrays = "1.13.0" + LiveServer = "1.6.0" + Unicode = "1.11.0" + +[env."CTModels/test"] +ok = true + + [env."CTModels/test".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + Aqua = "0.8.18" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Plots = "1.41.7" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + CTBase = "0.30.10" + JLD2 = "0.6.7" + JSON3 = "1.14.3" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + CairoMakie = "0.15.16" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + +[env."CTSolvers/test"] +ok = true + + [env."CTSolvers/test".versions] + RecipesBase = "1.4.0" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + SparseConnectivityTracer = "1.2.4" + Aqua = "0.8.18" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + CUDSS = "0.8.1" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.4.1" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + UnoSolver = "0.3.8" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + Enzyme = "0.13.216" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + MadNCL = "0.2.3" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + +[env."CTLie/docs"] +ok = true + + [env."CTLie/docs".versions] + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Markdown = "1.11.0" + CTLie = "0.2.3" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + Test = "1.11.0" + DocumenterInterLinks = "1.1.0" + Random = "1.11.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + DocumenterVitepress = "0.3.8" + ADTypes = "1.24.0" + Logging = "1.11.0" + Documenter = "1.19.0" + DifferentiationInterface = "0.7.21" + LiveServer = "1.6.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + +[env."CTParser/test"] +ok = true + + [env."CTParser/test".versions] + ExaModels = "0.12.1" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + CTBase = "0.29.3" + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + MLStyle = "0.4.17" + Markdown = "1.11.0" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + CUDSS = "0.8.1" + Test = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Aqua = "0.8.18" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Logging = "1.11.0" + Reexport = "1.2.2" + CUDA = "6.4.2" + MadNLP = "0.10.1" + Interpolations = "0.16.3" + GPUArraysCore = "0.2.1" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.18.0" + KernelAbstractions = "0.9.45" + MadNLPGPU = "0.10.2" + +[env."OptimalControl/test"] +ok = true + + [env."OptimalControl/test".versions] + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + NonlinearSolve = "4.32.0" + SparseConnectivityTracer = "1.2.4" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + CTParser = "0.9.5-beta" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqVerner = "2.4.2" + OrdinaryDiffEqRosenbrock = "2.7.5" + CUDSS = "0.7.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.2.0" + MadNLP = "0.10.1" + SplitApplyCombine = "1.3.0" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + OrdinaryDiffEq = "7.8.1" + CTLie = "0.2.3" + UnoSolver = "0.3.8" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + OptimalControl = "2.2.1-beta" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + CTDirect = "1.1.8-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + MadNCL = "0.2.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + +[env."CTBase/test"] +ok = true + + [env."CTBase/test".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + Aqua = "0.8.18" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + OrderedCollections = "2.0.3" + Markdown = "1.11.0" + Random = "1.11.0" + BenchmarkTools = "1.8.0" + Plots = "1.41.7" + Coverage = "1.8.2" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + +[env.joint] +ok = true + + [env.joint.versions] + ExaModels = "0.12.1" + CTDirect = "1.1.8-beta" + CTParser = "0.9.5-beta" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + OrderedCollections = "2.0.3" + CTBase = "0.30.10" + RecipesBase = "1.4.0" + SolverCore = "0.3.10" + MLStyle = "0.4.17" + CommonSolve = "0.2.14" + Markdown = "1.11.0" + CTLie = "0.2.3" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + Random = "1.11.0" + SparseConnectivityTracer = "1.2.4" + CTSolvers = "0.5.8" + ADNLPModels = "0.8.15" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + ADTypes = "1.24.0" + Parameters = "0.13.1" + Reexport = "1.2.2" + OptimalControl = "2.2.1-beta" + DifferentiationInterface = "0.7.21" + GPUArraysCore = "0.2.1" + SparseArrays = "1.13.0" + Unicode = "1.11.0" + MacroTools = "0.5.16" + NLPModels = "0.21.12" + CTModels = "0.19.8" + KernelAbstractions = "0.9.45" + +[env."CTBase/docs"] +ok = true + + [env."CTBase/docs".versions] + RecipesBase = "1.4.0" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + OrderedCollections = "2.0.3" + Markdown = "1.11.0" + Random = "1.11.0" + Plots = "1.41.7" + Coverage = "1.8.2" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + Makie = "0.24.16" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + Documenter = "1.19.0" + CairoMakie = "0.15.16" + SparseArrays = "1.13.0" + LiveServer = "1.5.0" + Unicode = "1.11.0" + +[env."CTSolvers/docs"] +ok = true + + [env."CTSolvers/docs".versions] + RecipesBase = "1.4.0" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + NLPModelsKnitro = "0.10.6" + LinearAlgebra = "1.13.0" + Printf = "1.11.0" + SparseConnectivityTracer = "1.2.4" + DocumenterVitepress = "0.3.8" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + OrdinaryDiffEqVerner = "2.4.2" + CUDSS = "0.8.1" + Random = "1.11.0" + NLPModelsIpopt = "0.11.3" + ADNLPModels = "0.8.15" + CUDA = "6.4.1" + MadNLP = "0.10.1" + KernelAbstractions = "0.9.45" + ExaModels = "0.12.1" + UnoSolver = "0.3.8" + DocumenterInterLinks = "1.1.0" + MarkdownAST = "0.1.3" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + Enzyme = "0.13.216" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + DifferentiationInterface = "0.7.21" + NLPModels = "0.21.12" + MadNLPGPU = "0.10.2" + ForwardDiff = "1.4.6" + CTBase = "0.30.10" + MadNCL = "0.2.3" + JET = "0.12.3" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + Documenter = "1.19.0" + SparseArrays = "1.13.0" + LiveServer = "1.6.0" + Unicode = "1.11.0" + +[env."CTFlows/test"] +ok = true + + [env."CTFlows/test".versions] + RecipesBase = "1.4.0" + Zygote = "0.7.13" + SolverCore = "0.3.10" + CommonSolve = "0.2.14" + LinearAlgebra = "1.13.0" + DiffEqGPU = "3.21.5" + Printf = "1.11.0" + NonlinearSolve = "4.32.0" + Aqua = "0.8.18" + Logging = "1.11.0" + DiffEqBase = "7.21.4" + GPUArraysCore = "0.2.1" + SciMLBase = "3.58.0" + Colors = "0.13.3" + MacroTools = "0.5.16" + CTModels = "0.19.8" + OrderedCollections = "2.0.3" + MLStyle = "0.4.17" + Markdown = "1.11.0" + Random = "1.11.0" + Plots = "1.41.7" + CUDA = "6.4.2" + KernelAbstractions = "0.9.45" + CTLie = "0.2.3" + Mooncake = "0.5.64" + CTSolvers = "0.5.8" + DocStringExtensions = "0.9.5" + StaticArrays = "1.9.23" + Parameters = "0.13.1" + Interpolations = "0.16.3" + DifferentiationInterface = "0.7.21" + ForwardDiff = "1.4.6" + CTFlows = "0.18.1" + CTBase = "0.30.10" + Makie = "0.24.16" + Test = "1.11.0" + ADTypes = "1.24.0" + Reexport = "1.2.2" + OrdinaryDiffEqTsit5 = "2.1.5" + CairoMakie = "0.15.16" + SparseArrays = "1.13.0" + Unicode = "1.11.0" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTBase/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTBase/Project.toml new file mode 100644 index 0000000..aecdb99 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTBase/Project.toml @@ -0,0 +1,64 @@ +name = "CTBase" +uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" +version = "0.30.10" +authors = ["Olivier Cots ", "Jean-Baptiste Caillau "] + +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" + +[weakdeps] +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[extensions] +CTBaseDifferentiationInterface = ["DifferentiationInterface"] +CTBaseMakie = ["Makie"] +CTBasePlots = ["Plots"] +CoveragePostprocessing = ["Coverage"] +DocumenterReference = ["Documenter", "Markdown", "MarkdownAST"] +TestRunner = ["Test"] + +[compat] +ADTypes = "1" +Aqua = "0.8" +BenchmarkTools = "1" +CairoMakie = "0.15" +Coverage = "1" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9, 0.10" +Documenter = "1" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +Logging = "1" +Makie = "0.24" +Markdown = "1" +MarkdownAST = "0.1" +Plots = "1" +Test = "1" +julia = "1.10" + +[extras] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Aqua", "BenchmarkTools", "CairoMakie", "Coverage", "DifferentiationInterface", "Documenter", "ForwardDiff", "JET", "Logging", "Markdown", "MarkdownAST", "Plots", "Test"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTBase/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTBase/docs/Project.toml new file mode 100644 index 0000000..848cbe1 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTBase/docs/Project.toml @@ -0,0 +1,28 @@ +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[compat] +ADTypes = "1" +CairoMakie = "0.15" +Coverage = "1" +DifferentiationInterface = "0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3.5" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +LiveServer = "1" +MarkdownAST = "0.1" +Plots = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTDirect/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTDirect/Project.toml new file mode 100644 index 0000000..2749473 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTDirect/Project.toml @@ -0,0 +1,58 @@ +name = "CTDirect" +uuid = "790bbbee-bee9-49ee-8912-a9de031322d5" +version = "1.1.8-beta" +authors = ["Pierre Martinon "] + +[workspace] +projects = ["test", "docs"] + +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" + +[compat] +ADNLPModels = "0.8" +AMDGPU = "2" +CTBase = "0.29, 0.30" +CTModels = "0.18, 0.19" +CTParser = "0.9" +CTSolvers = "0.5" +CUDA = "6" +CUDSS = "0.8" +CommonSolve = "0.2" +DocStringExtensions = "0.9" +ExaModels = "0.12" +MadNLP = "0.10" +MadNLPGPU = "0.10" +NLPModels = "0.21" +NLPModelsIpopt = "0.11" +SolverCore = "0.3" +SparseArrays = "1" +SparseConnectivityTracer = "1.2.1" +SplitApplyCombine = "1" +Test = "1" +julia = "1.10" + +[extras] +AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["AMDGPU", "CTParser", "CUDA", "CUDSS", "CommonSolve", "MadNLP", "MadNLPGPU", "NLPModels", "NLPModelsIpopt", "SplitApplyCombine", "Test"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTDirect/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTDirect/docs/Project.toml new file mode 100644 index 0000000..50c9b08 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTDirect/docs/Project.toml @@ -0,0 +1,13 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterMermaid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" + +[compat] +CTBase = "0.29, 0.30" +Documenter = "1" +DocumenterMermaid = "0.2" +Markdown = "1" +MarkdownAST = "0.1" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTFlows/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTFlows/Project.toml new file mode 100644 index 0000000..bd35945 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTFlows/Project.toml @@ -0,0 +1,76 @@ +name = "CTFlows" +uuid = "1c39547c-7794-42f7-af83-d98194f657c2" +version = "0.18.1" +authors = ["Olivier Cots "] + +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + +[weakdeps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[extensions] +CTFlowsMakie = ["Makie"] +CTFlowsPlots = ["Plots"] +CTFlowsSciMLFlows = ["SciMLBase"] +CTFlowsSciMLIntegrator = ["SciMLBase"] +CTFlowsStaticArrays = ["StaticArrays"] + +[compat] +ADTypes = "1" +Aqua = "0.8" +CTBase = "0.30.8" +CTLie = "0.3" +CTModels = "0.19" +CTSolvers = "0.6" +CUDA = "5, 6" +CairoMakie = "0.15" +CommonSolve = "0.2" +DiffEqBase = "6, 7" +DiffEqGPU = "3" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9" +ForwardDiff = "0.10, 1" +GPUArraysCore = "0.1, 0.2" +Makie = "0.24" +Mooncake = "0.5" +NonlinearSolve = "4" +OrdinaryDiffEqTsit5 = "2" +Plots = "1" +RecipesBase = "1" +SciMLBase = "3" +StaticArrays = "1" +Test = "1" +Zygote = "0.7" +julia = "1.10" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +CTLie = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DiffEqGPU = "071ae1c0-96b5-11e9-1965-c90190d839ea" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[targets] +test = ["Aqua", "CTLie", "CUDA", "CairoMakie", "DiffEqBase", "DiffEqGPU", "DifferentiationInterface", "ForwardDiff", "Mooncake", "NonlinearSolve", "OrdinaryDiffEqTsit5", "Plots", "SciMLBase", "StaticArrays", "Test", "Zygote"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTFlows/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTFlows/docs/Project.toml new file mode 100644 index 0000000..bf59804 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTFlows/docs/Project.toml @@ -0,0 +1,38 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +OrdinaryDiffEqRosenbrock = "43230ef6-c299-4910-a778-202eb28ce4ce" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[compat] +CTBase = "0.30" +CTModels = "0.19" +CTSolvers = "0.6" +CairoMakie = "0.15" +DiffEqBase = "7" +DifferentiationInterface = "0.6, 0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3" +ForwardDiff = "0.10, 1" +LiveServer = "1" +MarkdownAST = "0.1" +OrdinaryDiffEqRosenbrock = "2" +OrdinaryDiffEqTsit5 = "2" +Plots = "1" +SciMLBase = "3" +StaticArrays = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTLie/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTLie/Project.toml new file mode 100644 index 0000000..c680bdc --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTLie/Project.toml @@ -0,0 +1,38 @@ +name = "CTLie" +uuid = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +authors = ["Olivier Cots"] +version = "0.3.0" + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" + +[compat] +ADTypes = "1" +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CUDA = "5, 6" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +MacroTools = "0.5" +Mooncake = "0.5" +Test = "1" +julia = "1.10" + +[extras] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["ADTypes", "Aqua", "BenchmarkTools", "CUDA", "DifferentiationInterface", "ForwardDiff", "JET", "Mooncake", "Test"] \ No newline at end of file diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTLie/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTLie/docs/Project.toml new file mode 100644 index 0000000..e1509c8 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTLie/docs/Project.toml @@ -0,0 +1,22 @@ +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" + +[compat] +ADTypes = "1" +CTBase = "0.30" +DifferentiationInterface = "0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3" +ForwardDiff = "0.10, 1" +LiveServer = "1" +MarkdownAST = "0.1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTModels/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTModels/Project.toml new file mode 100644 index 0000000..a26f0e9 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTModels/Project.toml @@ -0,0 +1,61 @@ +name = "CTModels" +uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" +version = "0.19.8" +authors = ["Olivier Cots "] + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" +MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + +[weakdeps] +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[extensions] +CTModelsJLD = "JLD2" +CTModelsJSON = "JSON3" +CTModelsMakie = "Makie" +CTModelsPlots = "Plots" + +[compat] +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CairoMakie = "0.15" +DocStringExtensions = "0.9" +JET = "0.9, 0.11, 0.12" +JLD2 = "0.6" +JSON3 = "1" +LinearAlgebra = "1" +MLStyle = "0.4" +Makie = "0.24" +MacroTools = "0.5" +OrderedCollections = "1, 2" +Parameters = "0.13" +Plots = "1" +Random = "1" +RecipesBase = "1" +Test = "1" +julia = "1.10" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Aqua", "BenchmarkTools", "CairoMakie", "JET", "JLD2", "JSON3", "Plots", "Random", "Test"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTModels/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTModels/docs/Project.toml new file mode 100644 index 0000000..6a4b442 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTModels/docs/Project.toml @@ -0,0 +1,26 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[compat] +CTBase = "0.30" +CairoMakie = "0.15" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3.5" +JET = "0.9, 0.11, 0.12" +JLD2 = "0.6" +JSON3 = "1" +LiveServer = "1" +MarkdownAST = "0.1" +Plots = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/Project.toml new file mode 100644 index 0000000..8e8b5cc --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/Project.toml @@ -0,0 +1,21 @@ +name = "CTParser" +uuid = "32681960-a1b1-40db-9bff-a1ca817385d1" +version = "0.8.17-beta" +authors = ["Jean-Baptiste Caillau "] + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" +Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[compat] +CTBase = "0.18, 0.27, 0.28" +DocStringExtensions = "0.9" +MLStyle = "0.4" +OrderedCollections = "1" +Parameters = "0.12, 0.13" +Unicode = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/docs/Project.toml new file mode 100644 index 0000000..631245d --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/docs/Project.toml @@ -0,0 +1,12 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" + +[compat] +CTBase = "0.18, 0.27, 0.28" +Documenter = "1" +Markdown = "1" +MarkdownAST = "0.1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/test/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/test/Project.toml new file mode 100644 index 0000000..6214d50 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTParser/test/Project.toml @@ -0,0 +1,32 @@ +[deps] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[compat] +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.18, 0.27, 0.28" +CTModels = "0.10, 0.14, 0.15" +CUDA = "5" +ExaModels = "0.9" +Interpolations = "0.16" +KernelAbstractions = "0.9" +LinearAlgebra = "1" +MadNLP = "0.9" +MadNLPGPU = "0.8" +NLPModels = "0.21" +OrderedCollections = "1.8" +Test = "1.10" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTSolvers/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTSolvers/Project.toml new file mode 100644 index 0000000..9065356 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTSolvers/Project.toml @@ -0,0 +1,107 @@ +name = "CTSolvers" +uuid = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +version = "0.6.0" +authors = ["Olivier Cots "] + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" + +[weakdeps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[extensions] +CTSolversADNLPModels = "ADNLPModels" +CTSolversCUDA = "CUDA" +CTSolversEnzyme = "Enzyme" +CTSolversExaModels = ["ExaModels"] +CTSolversForwardDiff = "ForwardDiff" +CTSolversIpopt = ["NLPModels", "NLPModelsIpopt"] +CTSolversKnitro = ["NLPModels", "NLPModelsKnitro"] +CTSolversMadNCL = ["MadNCL", "MadNLP", "NLPModels"] +CTSolversMadNLP = ["MadNLP", "NLPModels"] +CTSolversMadNLPGPU = ["MadNLPGPU", "CUDA", "CUDSS"] +CTSolversOrdinaryDiffEqTsit5 = "OrdinaryDiffEqTsit5" +CTSolversSciMLIntegrator = ["DiffEqBase", "SciMLBase"] +CTSolversUno = ["NLPModels", "UnoSolver"] +CTSolversZygote = "Zygote" + +[compat] +ADNLPModels = "0.8" +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CTModels = "0.19" +CUDA = "5, 6" +CUDSS = "0.6, 0.7, 0.8" +CommonSolve = "0.2" +DiffEqBase = "6, 7" +DocStringExtensions = "0.9, 0.10" +Enzyme = "0.13" +ExaModels = "0.12" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +KernelAbstractions = "0.9" +MadNCL = "0.2" +MadNLP = "0.9, 0.10" +MadNLPGPU = "0.8, 0.10" +NLPModels = "0.21" +NLPModelsIpopt = "0.11" +NLPModelsKnitro = "0.10" +OrderedCollections = "1, 2" +OrdinaryDiffEqTsit5 = "2" +Random = "1" +SciMLBase = "3" +SolverCore = "0.3" +Test = "1" +UnoSolver = "0.3" +Zygote = "0.7" +julia = "1.10" + +[extras] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[targets] +test = ["ADNLPModels", "Aqua", "BenchmarkTools", "CUDA", "CUDSS", "DiffEqBase", "Enzyme", "ExaModels", "ForwardDiff", "JET", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModels", "NLPModelsIpopt", "OrderedCollections", "OrdinaryDiffEqTsit5", "Random", "SciMLBase", "Test", "UnoSolver", "Zygote"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTSolvers/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTSolvers/docs/Project.toml new file mode 100644 index 0000000..48e650d --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/CTSolvers/docs/Project.toml @@ -0,0 +1,59 @@ +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[compat] +ADNLPModels = "0.8" +CTBase = "0.30.10" +CUDA = "5, 6" +CUDSS = "0.6, 0.7, 0.8" +CommonSolve = "0.2" +DiffEqBase = "6, 7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3.5" +Enzyme = "0.13" +ExaModels = "0.12" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +LiveServer = "1" +MadNCL = "0.2" +MadNLP = "0.9, 0.10" +MadNLPGPU = "0.8, 0.10" +MarkdownAST = "0.1" +NLPModels = "0.21" +NLPModelsIpopt = "0.11" +NLPModelsKnitro = "0.10" +OrdinaryDiffEqTsit5 = "2" +OrdinaryDiffEqVerner = "2" +SciMLBase = "3" +UnoSolver = "0.3" +Zygote = "0.7" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/OptimalControl/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/OptimalControl/Project.toml new file mode 100644 index 0000000..ad776d9 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/OptimalControl/Project.toml @@ -0,0 +1,80 @@ +name = "OptimalControl" +uuid = "5f98b655-cc9a-415a-b60e-744165666948" +version = "2.2.1-beta" +authors = ["Olivier Cots "] + +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTDirect = "790bbbee-bee9-49ee-8912-a9de031322d5" +CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" +CTLie = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" + +[compat] +ADNLPModels = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CTDirect = "1" +CTFlows = "0.18" +CTLie = "0.2" +CTModels = "0.19" +CTParser = "0.9" +CTSolvers = "0.5" +CUDA = "6" +CUDSS = "0.7" +CommonSolve = "0.2" +DiffEqBase = "7" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9" +ExaModels = "0.12" +ForwardDiff = "1" +LinearAlgebra = "1" +MadNCL = "0.2" +MadNLP = "0.10" +MadNLPGPU = "0.10" +NLPModelsIpopt = "0.11" +NonlinearSolve = "4" +OrdinaryDiffEq = "7" +OrdinaryDiffEqTsit5 = "2" +Printf = "1" +RecipesBase = "1" +Reexport = "1" +SciMLBase = "3" +SolverCore = "0.3" +SplitApplyCombine = "1" +Test = "1" +UnoSolver = "0.3" +julia = "1.10" + +[extras] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" +SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" + +[targets] +test = ["BenchmarkTools", "CUDA", "CUDSS", "DiffEqBase", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModelsIpopt", "NonlinearSolve", "OrdinaryDiffEq", "OrdinaryDiffEqTsit5", "Printf", "SciMLBase", "SolverCore", "SplitApplyCombine", "Test", "UnoSolver"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/OptimalControl/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/OptimalControl/docs/Project.toml new file mode 100644 index 0000000..30d7aef --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/default/OptimalControl/docs/Project.toml @@ -0,0 +1,73 @@ +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTDirect = "790bbbee-bee9-49ee-8912-a9de031322d5" +CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" +CTLie = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" + +[compat] +ADNLPModels = "0.8" +CTBase = "0.30" +CTDirect = "1" +CTFlows = "0.18" +CTLie = "0.2" +CTModels = "0.19" +CTParser = "0.9" +CTSolvers = "0.5" +CUDA = "6" +CUDSS = "0.7" +CairoMakie = "0.15" +Colors = "0.13" +CommonSolve = "0.2" +DifferentiationInterface = "0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3" +ExaModels = "0.12" +ForwardDiff = "1" +JLD2 = "0.6" +JSON3 = "1" +Literate = "2" +LiveServer = "1" +MadNCL = "0.2" +MadNLP = "0.10" +MadNLPGPU = "0.10" +MarkdownAST = "0.1" +NLPModelsIpopt = "0.11" +NLPModelsKnitro = "0.10" +NonlinearSolve = "4" +OrdinaryDiffEq = "7" +OrdinaryDiffEqTsit5 = "2" +Plots = "1" +UnoSolver = "0.3" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/index.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/index.toml new file mode 100644 index 0000000..fe2d3a8 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/index.toml @@ -0,0 +1,73 @@ +julia = "1.13.1" + +[packages.CTBase] +repo = "control-toolbox/CTBase.jl" +version = "0.30.10" +default_branch = "main" +files = ["Project.toml", "docs/Project.toml"] +tree = "cafe2bf5cbd697c5b239517aa8a699369ced443c" +default_files = ["Project.toml", "docs/Project.toml"] +uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" + +[packages.CTModels] +repo = "control-toolbox/CTModels.jl" +version = "0.19.8" +default_branch = "main" +files = ["Project.toml", "docs/Project.toml"] +tree = "25682b75dd2149f0abea4d4264a83e92c6171114" +default_files = ["Project.toml", "docs/Project.toml"] +uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" + +[packages.CTFlows] +repo = "control-toolbox/CTFlows.jl" +version = "0.18.1" +default_branch = "main" +files = ["Project.toml", "docs/Project.toml"] +tree = "d82116ac4932b8494e7f6f3deb312dce44753969" +default_files = ["Project.toml", "docs/Project.toml"] +uuid = "1c39547c-7794-42f7-af83-d98194f657c2" + +[packages.CTDirect] +repo = "control-toolbox/CTDirect.jl" +version = "1.1.8-beta" +default_branch = "main" +files = ["Project.toml", "docs/Project.toml"] +tree = "38f6fbb168b844b7ceac71678b42c96daa20bba5" +default_files = ["Project.toml", "docs/Project.toml"] +uuid = "790bbbee-bee9-49ee-8912-a9de031322d5" + +[packages.OptimalControl] +repo = "control-toolbox/OptimalControl.jl" +version = "2.2.1-beta" +default_branch = "main" +files = ["Project.toml", "docs/Project.toml"] +tree = "07c2d96cde77525ad4c354547a34a068132f314d" +default_files = ["Project.toml", "docs/Project.toml"] +uuid = "5f98b655-cc9a-415a-b60e-744165666948" + +[packages.CTParser] +repo = "control-toolbox/CTParser.jl" +version = "0.9.5-beta" +default_branch = "main" +files = ["Project.toml", "test/Project.toml", "docs/Project.toml"] +tree = "3f745c73919729ca654d02e1091fce5583facacc" +default_files = ["Project.toml", "test/Project.toml", "docs/Project.toml"] +uuid = "32681960-a1b1-40db-9bff-a1ca817385d1" + +[packages.CTSolvers] +repo = "control-toolbox/CTSolvers.jl" +version = "0.5.8" +default_branch = "main" +files = ["Project.toml", "docs/Project.toml"] +tree = "50e6edf7726eb7d620323fb4cc3c2e87392022c3" +default_files = ["Project.toml", "docs/Project.toml"] +uuid = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" + +[packages.CTLie] +repo = "control-toolbox/CTLie.jl" +version = "0.2.3" +default_branch = "main" +files = ["Project.toml", "docs/Project.toml"] +tree = "049bbb24dfa8b5d54cff2736352d76ce9e7782ae" +default_files = ["Project.toml", "docs/Project.toml"] +uuid = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTBase/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTBase/Project.toml new file mode 100644 index 0000000..aecdb99 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTBase/Project.toml @@ -0,0 +1,64 @@ +name = "CTBase" +uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" +version = "0.30.10" +authors = ["Olivier Cots ", "Jean-Baptiste Caillau "] + +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" + +[weakdeps] +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[extensions] +CTBaseDifferentiationInterface = ["DifferentiationInterface"] +CTBaseMakie = ["Makie"] +CTBasePlots = ["Plots"] +CoveragePostprocessing = ["Coverage"] +DocumenterReference = ["Documenter", "Markdown", "MarkdownAST"] +TestRunner = ["Test"] + +[compat] +ADTypes = "1" +Aqua = "0.8" +BenchmarkTools = "1" +CairoMakie = "0.15" +Coverage = "1" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9, 0.10" +Documenter = "1" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +Logging = "1" +Makie = "0.24" +Markdown = "1" +MarkdownAST = "0.1" +Plots = "1" +Test = "1" +julia = "1.10" + +[extras] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Aqua", "BenchmarkTools", "CairoMakie", "Coverage", "DifferentiationInterface", "Documenter", "ForwardDiff", "JET", "Logging", "Markdown", "MarkdownAST", "Plots", "Test"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTBase/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTBase/docs/Project.toml new file mode 100644 index 0000000..848cbe1 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTBase/docs/Project.toml @@ -0,0 +1,28 @@ +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[compat] +ADTypes = "1" +CairoMakie = "0.15" +Coverage = "1" +DifferentiationInterface = "0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3.5" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +LiveServer = "1" +MarkdownAST = "0.1" +Plots = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTDirect/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTDirect/Project.toml new file mode 100644 index 0000000..2749473 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTDirect/Project.toml @@ -0,0 +1,58 @@ +name = "CTDirect" +uuid = "790bbbee-bee9-49ee-8912-a9de031322d5" +version = "1.1.8-beta" +authors = ["Pierre Martinon "] + +[workspace] +projects = ["test", "docs"] + +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" + +[compat] +ADNLPModels = "0.8" +AMDGPU = "2" +CTBase = "0.29, 0.30" +CTModels = "0.18, 0.19" +CTParser = "0.9" +CTSolvers = "0.5" +CUDA = "6" +CUDSS = "0.8" +CommonSolve = "0.2" +DocStringExtensions = "0.9" +ExaModels = "0.12" +MadNLP = "0.10" +MadNLPGPU = "0.10" +NLPModels = "0.21" +NLPModelsIpopt = "0.11" +SolverCore = "0.3" +SparseArrays = "1" +SparseConnectivityTracer = "1.2.1" +SplitApplyCombine = "1" +Test = "1" +julia = "1.10" + +[extras] +AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["AMDGPU", "CTParser", "CUDA", "CUDSS", "CommonSolve", "MadNLP", "MadNLPGPU", "NLPModels", "NLPModelsIpopt", "SplitApplyCombine", "Test"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTDirect/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTDirect/docs/Project.toml new file mode 100644 index 0000000..50c9b08 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTDirect/docs/Project.toml @@ -0,0 +1,13 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterMermaid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" + +[compat] +CTBase = "0.29, 0.30" +Documenter = "1" +DocumenterMermaid = "0.2" +Markdown = "1" +MarkdownAST = "0.1" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTFlows/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTFlows/Project.toml new file mode 100644 index 0000000..b51e704 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTFlows/Project.toml @@ -0,0 +1,76 @@ +name = "CTFlows" +uuid = "1c39547c-7794-42f7-af83-d98194f657c2" +version = "0.18.1" +authors = ["Olivier Cots "] + +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + +[weakdeps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[extensions] +CTFlowsMakie = ["Makie"] +CTFlowsPlots = ["Plots"] +CTFlowsSciMLFlows = ["SciMLBase"] +CTFlowsSciMLIntegrator = ["SciMLBase"] +CTFlowsStaticArrays = ["StaticArrays"] + +[compat] +ADTypes = "1" +Aqua = "0.8" +CTBase = "0.30" +CTLie = "0.2" +CTModels = "0.19" +CTSolvers = "0.5" +CUDA = "5, 6" +CairoMakie = "0.15" +CommonSolve = "0.2" +DiffEqBase = "6, 7" +DiffEqGPU = "3" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9" +ForwardDiff = "0.10, 1" +GPUArraysCore = "0.1, 0.2" +Makie = "0.24" +Mooncake = "0.5" +NonlinearSolve = "4" +OrdinaryDiffEqTsit5 = "2" +Plots = "1" +RecipesBase = "1" +SciMLBase = "3" +StaticArrays = "1" +Test = "1" +Zygote = "0.7" +julia = "1.10" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +CTLie = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DiffEqGPU = "071ae1c0-96b5-11e9-1965-c90190d839ea" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[targets] +test = ["Aqua", "CTLie", "CUDA", "CairoMakie", "DiffEqBase", "DiffEqGPU", "DifferentiationInterface", "ForwardDiff", "Mooncake", "NonlinearSolve", "OrdinaryDiffEqTsit5", "Plots", "SciMLBase", "StaticArrays", "Test", "Zygote"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTFlows/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTFlows/docs/Project.toml new file mode 100644 index 0000000..f2767df --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTFlows/docs/Project.toml @@ -0,0 +1,38 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +OrdinaryDiffEqRosenbrock = "43230ef6-c299-4910-a778-202eb28ce4ce" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[compat] +CTBase = "0.30" +CTModels = "0.19" +CTSolvers = "0.5" +CairoMakie = "0.15" +DiffEqBase = "7" +DifferentiationInterface = "0.6, 0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3" +ForwardDiff = "0.10, 1" +LiveServer = "1" +MarkdownAST = "0.1" +OrdinaryDiffEqRosenbrock = "2" +OrdinaryDiffEqTsit5 = "2" +Plots = "1" +SciMLBase = "3" +StaticArrays = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTLie/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTLie/Project.toml new file mode 100644 index 0000000..d2860cd --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTLie/Project.toml @@ -0,0 +1,38 @@ +name = "CTLie" +uuid = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +authors = ["Olivier Cots"] +version = "0.2.3" + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" + +[compat] +ADTypes = "1" +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CUDA = "5, 6" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +MacroTools = "0.5" +Mooncake = "0.5" +Test = "1" +julia = "1.10" + +[extras] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["ADTypes", "Aqua", "BenchmarkTools", "CUDA", "DifferentiationInterface", "ForwardDiff", "JET", "Mooncake", "Test"] \ No newline at end of file diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTLie/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTLie/docs/Project.toml new file mode 100644 index 0000000..e1509c8 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTLie/docs/Project.toml @@ -0,0 +1,22 @@ +[deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" + +[compat] +ADTypes = "1" +CTBase = "0.30" +DifferentiationInterface = "0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3" +ForwardDiff = "0.10, 1" +LiveServer = "1" +MarkdownAST = "0.1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTModels/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTModels/Project.toml new file mode 100644 index 0000000..a26f0e9 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTModels/Project.toml @@ -0,0 +1,61 @@ +name = "CTModels" +uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" +version = "0.19.8" +authors = ["Olivier Cots "] + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" +MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + +[weakdeps] +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[extensions] +CTModelsJLD = "JLD2" +CTModelsJSON = "JSON3" +CTModelsMakie = "Makie" +CTModelsPlots = "Plots" + +[compat] +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CairoMakie = "0.15" +DocStringExtensions = "0.9" +JET = "0.9, 0.11, 0.12" +JLD2 = "0.6" +JSON3 = "1" +LinearAlgebra = "1" +MLStyle = "0.4" +Makie = "0.24" +MacroTools = "0.5" +OrderedCollections = "1, 2" +Parameters = "0.13" +Plots = "1" +Random = "1" +RecipesBase = "1" +Test = "1" +julia = "1.10" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Aqua", "BenchmarkTools", "CairoMakie", "JET", "JLD2", "JSON3", "Plots", "Random", "Test"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTModels/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTModels/docs/Project.toml new file mode 100644 index 0000000..6a4b442 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTModels/docs/Project.toml @@ -0,0 +1,26 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[compat] +CTBase = "0.30" +CairoMakie = "0.15" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3.5" +JET = "0.9, 0.11, 0.12" +JLD2 = "0.6" +JSON3 = "1" +LiveServer = "1" +MarkdownAST = "0.1" +Plots = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/Project.toml new file mode 100644 index 0000000..56fa680 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/Project.toml @@ -0,0 +1,30 @@ +name = "CTParser" +uuid = "32681960-a1b1-40db-9bff-a1ca817385d1" +version = "0.9.5-beta" +authors = ["Jean-Baptiste Caillau "] + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" +Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[weakdeps] +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[extensions] +CTParserExaModels = ["ExaModels", "LinearAlgebra"] + +[compat] +CTBase = "0.29, 0.30" +DocStringExtensions = "0.9" +ExaModels = "0.12" +LinearAlgebra = "1" +MLStyle = "0.4" +OrderedCollections = "1, 2" +Parameters = "0.13" +Unicode = "1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/docs/Project.toml new file mode 100644 index 0000000..6b1c7b4 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/docs/Project.toml @@ -0,0 +1,12 @@ +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" + +[compat] +CTBase = "0.29, 0.30" +Documenter = "1" +Markdown = "1" +MarkdownAST = "0.1" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/test/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/test/Project.toml new file mode 100644 index 0000000..8d16399 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTParser/test/Project.toml @@ -0,0 +1,34 @@ +[deps] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[compat] +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.29, 0.30" +CTModels = "0.18" +CUDA = "5, 6" +CUDSS = "0.6, 0.7, 0.8" +ExaModels = "0.12" +Interpolations = "0.16" +KernelAbstractions = "0.9" +LinearAlgebra = "1" +MadNLP = "0.9, 0.10" +MadNLPGPU = "0.8, 0.10" +NLPModels = "0.21" +OrderedCollections = "1.8, 2" +Test = "1.10" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTSolvers/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTSolvers/Project.toml new file mode 100644 index 0000000..e18b255 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTSolvers/Project.toml @@ -0,0 +1,107 @@ +name = "CTSolvers" +uuid = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +version = "0.5.8" +authors = ["Olivier Cots "] + +[deps] +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" + +[weakdeps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[extensions] +CTSolversADNLPModels = "ADNLPModels" +CTSolversCUDA = "CUDA" +CTSolversEnzyme = "Enzyme" +CTSolversExaModels = ["ExaModels"] +CTSolversForwardDiff = "ForwardDiff" +CTSolversIpopt = ["NLPModels", "NLPModelsIpopt"] +CTSolversKnitro = ["NLPModels", "NLPModelsKnitro"] +CTSolversMadNCL = ["MadNCL", "MadNLP", "NLPModels"] +CTSolversMadNLP = ["MadNLP", "NLPModels"] +CTSolversMadNLPGPU = ["MadNLPGPU", "CUDA", "CUDSS"] +CTSolversOrdinaryDiffEqTsit5 = "OrdinaryDiffEqTsit5" +CTSolversSciMLIntegrator = ["DiffEqBase", "SciMLBase"] +CTSolversUno = ["NLPModels", "UnoSolver"] +CTSolversZygote = "Zygote" + +[compat] +ADNLPModels = "0.8" +Aqua = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CTModels = "0.19" +CUDA = "5, 6" +CUDSS = "0.6, 0.7, 0.8" +CommonSolve = "0.2" +DiffEqBase = "6, 7" +DocStringExtensions = "0.9" +Enzyme = "0.13" +ExaModels = "0.12" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +KernelAbstractions = "0.9" +MadNCL = "0.2" +MadNLP = "0.9, 0.10" +MadNLPGPU = "0.8, 0.10" +NLPModels = "0.21" +NLPModelsIpopt = "0.11" +NLPModelsKnitro = "0.10" +OrderedCollections = "1, 2" +OrdinaryDiffEqTsit5 = "2" +Random = "1" +SciMLBase = "3" +SolverCore = "0.3" +Test = "1" +UnoSolver = "0.3" +Zygote = "0.7" +julia = "1.10" + +[extras] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[targets] +test = ["ADNLPModels", "Aqua", "BenchmarkTools", "CUDA", "CUDSS", "DiffEqBase", "Enzyme", "ExaModels", "ForwardDiff", "JET", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModels", "NLPModelsIpopt", "OrderedCollections", "OrdinaryDiffEqTsit5", "Random", "SciMLBase", "Test", "UnoSolver", "Zygote"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTSolvers/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTSolvers/docs/Project.toml new file mode 100644 index 0000000..48ed89a --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/CTSolvers/docs/Project.toml @@ -0,0 +1,56 @@ +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[compat] +ADNLPModels = "0.8" +CTBase = "0.30" +CUDA = "5, 6" +CUDSS = "0.6, 0.7, 0.8" +CommonSolve = "0.2" +DiffEqBase = "6, 7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3" +Enzyme = "0.13" +ExaModels = "0.12" +ForwardDiff = "0.10, 1" +JET = "0.9, 0.11, 0.12" +LiveServer = "1" +MadNCL = "0.2" +MadNLP = "0.9, 0.10" +MadNLPGPU = "0.8, 0.10" +MarkdownAST = "0.1" +NLPModels = "0.21" +NLPModelsIpopt = "0.11" +NLPModelsKnitro = "0.10" +OrdinaryDiffEqTsit5 = "2" +OrdinaryDiffEqVerner = "2" +SciMLBase = "3" +UnoSolver = "0.3" +Zygote = "0.7" +julia = "1.10" diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/OptimalControl/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/OptimalControl/Project.toml new file mode 100644 index 0000000..ad776d9 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/OptimalControl/Project.toml @@ -0,0 +1,80 @@ +name = "OptimalControl" +uuid = "5f98b655-cc9a-415a-b60e-744165666948" +version = "2.2.1-beta" +authors = ["Olivier Cots "] + +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTDirect = "790bbbee-bee9-49ee-8912-a9de031322d5" +CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" +CTLie = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" + +[compat] +ADNLPModels = "0.8" +BenchmarkTools = "1" +CTBase = "0.30" +CTDirect = "1" +CTFlows = "0.18" +CTLie = "0.2" +CTModels = "0.19" +CTParser = "0.9" +CTSolvers = "0.5" +CUDA = "6" +CUDSS = "0.7" +CommonSolve = "0.2" +DiffEqBase = "7" +DifferentiationInterface = "0.7" +DocStringExtensions = "0.9" +ExaModels = "0.12" +ForwardDiff = "1" +LinearAlgebra = "1" +MadNCL = "0.2" +MadNLP = "0.10" +MadNLPGPU = "0.10" +NLPModelsIpopt = "0.11" +NonlinearSolve = "4" +OrdinaryDiffEq = "7" +OrdinaryDiffEqTsit5 = "2" +Printf = "1" +RecipesBase = "1" +Reexport = "1" +SciMLBase = "3" +SolverCore = "0.3" +SplitApplyCombine = "1" +Test = "1" +UnoSolver = "0.3" +julia = "1.10" + +[extras] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" +SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" + +[targets] +test = ["BenchmarkTools", "CUDA", "CUDSS", "DiffEqBase", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModelsIpopt", "NonlinearSolve", "OrdinaryDiffEq", "OrdinaryDiffEqTsit5", "Printf", "SciMLBase", "SolverCore", "SplitApplyCombine", "Test", "UnoSolver"] diff --git a/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/OptimalControl/docs/Project.toml b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/OptimalControl/docs/Project.toml new file mode 100644 index 0000000..0a0a850 --- /dev/null +++ b/scripts/compat-prune/test/fixtures/snapshot-2026-10-15/registered/OptimalControl/docs/Project.toml @@ -0,0 +1,72 @@ +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +CTDirect = "790bbbee-bee9-49ee-8912-a9de031322d5" +CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" +CTLie = "6880e05b-3a7d-4cac-887c-30cb52c5fdde" +CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" +CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NLPModelsKnitro = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" + +[compat] +ADNLPModels = "0.8" +CTBase = "0.30" +CTDirect = "1" +CTFlows = "0.18" +CTLie = "0.2" +CTModels = "0.19" +CTParser = "0.9" +CTSolvers = "0.5" +CUDA = "6" +CUDSS = "0.7" +CairoMakie = "0.15" +Colors = "0.13" +CommonSolve = "0.2" +DifferentiationInterface = "0.7" +Documenter = "1" +DocumenterInterLinks = "1" +DocumenterVitepress = "0.3" +ExaModels = "0.12" +ForwardDiff = "1" +JLD2 = "0.6" +JSON3 = "1" +Literate = "2" +LiveServer = "1" +MadNCL = "0.2" +MadNLP = "0.10" +MadNLPGPU = "0.10" +MarkdownAST = "0.1" +NLPModelsIpopt = "0.11" +NLPModelsKnitro = "0.10" +NonlinearSolve = "4" +OrdinaryDiffEq = "7" +OrdinaryDiffEqTsit5 = "2" +Plots = "1" +UnoSolver = "0.3" +julia = "1.10" diff --git a/scripts/compat-prune/test/golden/classified-2026-10-15.txt b/scripts/compat-prune/test/golden/classified-2026-10-15.txt new file mode 100644 index 0000000..ea1d6f8 --- /dev/null +++ b/scripts/compat-prune/test/golden/classified-2026-10-15.txt @@ -0,0 +1,45 @@ +CTBase | Project.toml | DocStringExtensions | blocked | newest 0.10 +CTBase | Project.toml | ForwardDiff | prune | 1 +CTBase | Project.toml | JET | prune | 0.9, 0.12 +CTBase | docs/Project.toml | ForwardDiff | prune | 1 +CTBase | docs/Project.toml | JET | prune | 0.9, 0.12 +CTDirect | Project.toml | CTBase | prune | 0.30 +CTDirect | Project.toml | CTModels | prune | 0.19 +CTDirect | docs/Project.toml | CTBase | prune | 0.30 +CTFlows | Project.toml | CUDA | prune | 6 +CTFlows | Project.toml | DiffEqBase | prune | 7 +CTFlows | Project.toml | ForwardDiff | prune | 1 +CTFlows | Project.toml | GPUArraysCore | prune | 0.2 +CTFlows | docs/Project.toml | DifferentiationInterface | prune | 0.7 +CTFlows | docs/Project.toml | ForwardDiff | prune | 1 +CTLie | Project.toml | CUDA | prune | 6 +CTLie | Project.toml | ForwardDiff | prune | 1 +CTLie | Project.toml | JET | prune | 0.9, 0.12 +CTLie | docs/Project.toml | ForwardDiff | prune | 1 +CTModels | Project.toml | JET | prune | 0.9, 0.12 +CTModels | Project.toml | OrderedCollections | prune | 2 +CTModels | docs/Project.toml | JET | prune | 0.9, 0.12 +CTParser | Project.toml | CTBase | keep | +CTParser | Project.toml | OrderedCollections | prune | 2 +CTParser | docs/Project.toml | CTBase | prune | 0.30 +CTParser | test/Project.toml | CTBase | blocked | newest 0.30 +CTParser | test/Project.toml | CUDA | prune | 6 +CTParser | test/Project.toml | CUDSS | prune | 0.8 +CTParser | test/Project.toml | MadNLP | prune | 0.10 +CTParser | test/Project.toml | MadNLPGPU | prune | 0.10 +CTParser | test/Project.toml | OrderedCollections | prune | 2 +CTSolvers | Project.toml | CUDA | prune | 6 +CTSolvers | Project.toml | CUDSS | prune | 0.7, 0.8 +CTSolvers | Project.toml | DiffEqBase | prune | 7 +CTSolvers | Project.toml | ForwardDiff | prune | 1 +CTSolvers | Project.toml | JET | prune | 0.9, 0.12 +CTSolvers | Project.toml | MadNLP | prune | 0.10 +CTSolvers | Project.toml | MadNLPGPU | prune | 0.10 +CTSolvers | Project.toml | OrderedCollections | prune | 2 +CTSolvers | docs/Project.toml | CUDA | prune | 6 +CTSolvers | docs/Project.toml | CUDSS | prune | 0.8 +CTSolvers | docs/Project.toml | DiffEqBase | prune | 7 +CTSolvers | docs/Project.toml | ForwardDiff | prune | 1 +CTSolvers | docs/Project.toml | JET | prune | 0.9, 0.12 +CTSolvers | docs/Project.toml | MadNLP | prune | 0.10 +CTSolvers | docs/Project.toml | MadNLPGPU | prune | 0.10 diff --git a/scripts/compat-prune/test/golden/edits-2026-10-15.txt b/scripts/compat-prune/test/golden/edits-2026-10-15.txt new file mode 100644 index 0000000..570e322 --- /dev/null +++ b/scripts/compat-prune/test/golden/edits-2026-10-15.txt @@ -0,0 +1,42 @@ +CTBase | Project.toml | ForwardDiff | edited | +CTBase | Project.toml | JET | edited | +CTBase | docs/Project.toml | ForwardDiff | edited | +CTBase | docs/Project.toml | JET | edited | +CTDirect | Project.toml | CTBase | edited | +CTDirect | Project.toml | CTModels | edited | +CTDirect | docs/Project.toml | CTBase | edited | +CTFlows | Project.toml | CUDA | edited | +CTFlows | Project.toml | DiffEqBase | edited | +CTFlows | Project.toml | ForwardDiff | edited | +CTFlows | Project.toml | GPUArraysCore | edited | +CTFlows | docs/Project.toml | DifferentiationInterface | edited | +CTFlows | docs/Project.toml | ForwardDiff | edited | +CTLie | Project.toml | CUDA | edited | +CTLie | Project.toml | ForwardDiff | edited | +CTLie | Project.toml | JET | edited | +CTLie | docs/Project.toml | ForwardDiff | edited | +CTModels | Project.toml | JET | edited | +CTModels | Project.toml | OrderedCollections | edited | +CTModels | docs/Project.toml | JET | edited | +CTParser | Project.toml | OrderedCollections | drift | `1` on the default branch, `1, 2` audited +CTParser | docs/Project.toml | CTBase | drift | `0.18, 0.27, 0.28` on the default branch, `0.29, 0.30` audited +CTParser | test/Project.toml | CUDA | drift | `5` on the default branch, `5, 6` audited +CTParser | test/Project.toml | CUDSS | drift | no `CUDSS` entry in `[compat]` on the default branch +CTParser | test/Project.toml | MadNLP | drift | `0.9` on the default branch, `0.9, 0.10` audited +CTParser | test/Project.toml | MadNLPGPU | drift | `0.8` on the default branch, `0.8, 0.10` audited +CTParser | test/Project.toml | OrderedCollections | drift | `1.8` on the default branch, `1.8, 2` audited +CTSolvers | Project.toml | CUDA | edited | +CTSolvers | Project.toml | CUDSS | edited | +CTSolvers | Project.toml | DiffEqBase | edited | +CTSolvers | Project.toml | ForwardDiff | edited | +CTSolvers | Project.toml | JET | edited | +CTSolvers | Project.toml | MadNLP | edited | +CTSolvers | Project.toml | MadNLPGPU | edited | +CTSolvers | Project.toml | OrderedCollections | edited | +CTSolvers | docs/Project.toml | CUDA | edited | +CTSolvers | docs/Project.toml | CUDSS | edited | +CTSolvers | docs/Project.toml | DiffEqBase | edited | +CTSolvers | docs/Project.toml | ForwardDiff | edited | +CTSolvers | docs/Project.toml | JET | edited | +CTSolvers | docs/Project.toml | MadNLP | edited | +CTSolvers | docs/Project.toml | MadNLPGPU | edited | diff --git a/scripts/compat-prune/test/golden/envs-2026-10-15.txt b/scripts/compat-prune/test/golden/envs-2026-10-15.txt new file mode 100644 index 0000000..e6dc0ca --- /dev/null +++ b/scripts/compat-prune/test/golden/envs-2026-10-15.txt @@ -0,0 +1,17 @@ +CTBase/docs: deps=ADTypes,CTBase,CairoMakie,Coverage,DifferentiationInterface,Documenter,DocumenterInterLinks,DocumenterVitepress,ForwardDiff,JET,LiveServer,MarkdownAST,Plots pins=CTBase=0.30.10 +CTBase/test: deps=Aqua,BenchmarkTools,CTBase,CairoMakie,Coverage,DifferentiationInterface,Documenter,ForwardDiff,JET,Logging,Markdown,MarkdownAST,Plots,Test pins=CTBase=0.30.10 +CTDirect/docs: deps=CTBase,CTDirect,Documenter,DocumenterMermaid,Markdown,MarkdownAST pins=CTDirect=1.1.8-beta +CTDirect/test: deps=AMDGPU,CTDirect,CTParser,CUDA,CUDSS,CommonSolve,MadNLP,MadNLPGPU,NLPModels,NLPModelsIpopt,SplitApplyCombine,Test pins=CTDirect=1.1.8-beta +CTFlows/docs: deps=CTBase,CTFlows,CTModels,CTSolvers,CairoMakie,DiffEqBase,DifferentiationInterface,Documenter,DocumenterInterLinks,DocumenterVitepress,ForwardDiff,LiveServer,MarkdownAST,OrdinaryDiffEqRosenbrock,OrdinaryDiffEqTsit5,Plots,SciMLBase,StaticArrays pins=CTFlows=0.18.1 +CTFlows/test: deps=Aqua,CTFlows,CTLie,CUDA,CairoMakie,DiffEqBase,DiffEqGPU,DifferentiationInterface,ForwardDiff,Mooncake,NonlinearSolve,OrdinaryDiffEqTsit5,Plots,SciMLBase,StaticArrays,Test,Zygote pins=CTFlows=0.18.1 +CTLie/docs: deps=ADTypes,CTBase,CTLie,DifferentiationInterface,Documenter,DocumenterInterLinks,DocumenterVitepress,ForwardDiff,LiveServer,MarkdownAST pins=CTLie=0.2.3 +CTLie/test: deps=ADTypes,Aqua,BenchmarkTools,CTLie,CUDA,DifferentiationInterface,ForwardDiff,JET,Mooncake,Test pins=CTLie=0.2.3 +CTModels/docs: deps=CTBase,CTModels,CairoMakie,Documenter,DocumenterInterLinks,DocumenterVitepress,JET,JLD2,JSON3,LiveServer,MarkdownAST,Plots pins=CTModels=0.19.8 +CTModels/test: deps=Aqua,BenchmarkTools,CTModels,CairoMakie,JET,JLD2,JSON3,Plots,Random,Test pins=CTModels=0.19.8 +CTParser/docs: deps=CTBase,CTParser,Documenter,Markdown,MarkdownAST pins=CTParser=0.9.5-beta +CTParser/test: deps=Aqua,BenchmarkTools,CTBase,CTModels,CTParser,CUDA,CUDSS,ExaModels,Interpolations,KernelAbstractions,LinearAlgebra,MadNLP,MadNLPGPU,NLPModels,OrderedCollections,Test pins=CTParser=0.9.5-beta +CTSolvers/docs: deps=ADNLPModels,CTBase,CTSolvers,CUDA,CUDSS,CommonSolve,DiffEqBase,Documenter,DocumenterInterLinks,DocumenterVitepress,Enzyme,ExaModels,ForwardDiff,JET,LiveServer,MadNCL,MadNLP,MadNLPGPU,MarkdownAST,NLPModels,NLPModelsIpopt,NLPModelsKnitro,OrdinaryDiffEqTsit5,OrdinaryDiffEqVerner,SciMLBase,UnoSolver,Zygote pins=CTSolvers=0.5.8 +CTSolvers/test: deps=ADNLPModels,Aqua,BenchmarkTools,CTSolvers,CUDA,CUDSS,DiffEqBase,Enzyme,ExaModels,ForwardDiff,JET,MadNCL,MadNLP,MadNLPGPU,NLPModels,NLPModelsIpopt,OrderedCollections,OrdinaryDiffEqTsit5,Random,SciMLBase,Test,UnoSolver,Zygote pins=CTSolvers=0.5.8 +OptimalControl/docs: deps=ADNLPModels,CTBase,CTDirect,CTFlows,CTLie,CTModels,CTParser,CTSolvers,CUDA,CUDSS,CairoMakie,Colors,CommonSolve,DifferentiationInterface,Documenter,DocumenterInterLinks,DocumenterVitepress,ExaModels,ForwardDiff,JLD2,JSON3,Literate,LiveServer,MadNCL,MadNLP,MadNLPGPU,MarkdownAST,NLPModelsIpopt,NLPModelsKnitro,NonlinearSolve,OptimalControl,OrdinaryDiffEq,OrdinaryDiffEqTsit5,Plots,UnoSolver pins=OptimalControl=2.2.1-beta +OptimalControl/test: deps=BenchmarkTools,CUDA,CUDSS,DiffEqBase,MadNCL,MadNLP,MadNLPGPU,NLPModelsIpopt,NonlinearSolve,OptimalControl,OrdinaryDiffEq,OrdinaryDiffEqTsit5,Printf,SciMLBase,SolverCore,SplitApplyCombine,Test,UnoSolver pins=OptimalControl=2.2.1-beta +joint: deps=CTBase,CTDirect,CTFlows,CTLie,CTModels,CTParser,CTSolvers,OptimalControl pins=CTBase=0.30.10,CTDirect=1.1.8-beta,CTFlows=0.18.1,CTLie=0.2.3,CTModels=0.19.8,CTParser=0.9.5-beta,CTSolvers=0.5.8,OptimalControl=2.2.1-beta diff --git a/scripts/compat-prune/test/golden/pr-body.md b/scripts/compat-prune/test/golden/pr-body.md new file mode 100644 index 0000000..919b37d --- /dev/null +++ b/scripts/compat-prune/test/golden/pr-body.md @@ -0,0 +1,14 @@ + + +Drops the `[compat]` specifiers that no supported Julia version selects, in any +environment of the control-toolbox packages (joint, tests and docs of every package). +Without them every audited manifest is unchanged: this PR changes no version that +these environments resolve, needs no release of its own, and can be merged in any +order with the PRs of the other packages. + +| file | dependency | before | after | selected | +| --- | --- | --- | --- | --- | +| `Project.toml` | `Dep1` | `0.5, 0.6, 0.7` | `0.6, 0.7` | 0.7.2 on 1.12; 0.6.4 on 1.10 | +| `docs/Project.toml` | `Doc` | `0.9, 1` | `1` | | + +Opened by the [`compat-prune`](https://github.com/control-toolbox/CTActions/blob/main/.github/workflows/compat-prune.yml) workflow (tracking issue: https://github.com/example-org/Root.jl/issues/100, [audit run](https://github.com/example-org/Root.jl/actions/runs/1)). The branch is rewritten by every run: push nothing to it. diff --git a/scripts/compat-prune/test/golden/report.md b/scripts/compat-prune/test/golden/report.md new file mode 100644 index 0000000..a767a9f --- /dev/null +++ b/scripts/compat-prune/test/golden/report.md @@ -0,0 +1,98 @@ + + +Audit of the `[compat]` entries of the control-toolbox packages, by the +[`compat-prune`](https://github.com/control-toolbox/CTActions/blob/main/.github/workflows/compat-prune.yml) workflow on 2026-10-15 19:48 UTC ([run](https://github.com/example-org/Root.jl/actions/runs/1)), for Julia 1.10, 1.12, 1.13. This body is rewritten by every run: do not edit it. + +A prune drops `[compat]` specifiers that no environment selects, on no Julia version, and +leaves every resolved manifest (joint, tests and docs of every package) unchanged. PRs can +therefore be merged in any order, and need no release of their own. + +## Audit + +| Julia | environments resolved | +| --- | --- | +| 1.10 | 3 / 4 (failed: joint) | +| 1.12 | 4 / 4 | + +9 multi-specifier `[compat]` entries examined: 4 with lanes to prune, 1 rejected, 2 blocked, 1 kept (every lane in use), 1 idle (resolved nowhere they apply), 1 withheld. + +Registries: General (tree aff17d8a1e3e), ct-registry (commit 0123456789ab). + +
Entries left as they are (2) + +| entry | compat | status | why | +| --- | --- | --- | --- | +| Alpha `test/Project.toml` `Gpu` | `5, 6` | idle | not resolved where the entry applies | +| Beta `Project.toml` `Dep3` | `0.9, 1` | keep | every lane in use: 1.2.0 on 1.12; 0.9.4 on 1.10 | + +
+ +## Summary + +| | entries | +| --- | --- | +| to prune (open PRs) | 3 | +| merged, awaiting release | 1 | +| drift (not edited) | 1 | +| rejected by validation | 1 | +| withheld | 1 | +| blocked (information) | 2 | + +## To prune + +### Alpha — https://github.com/example-org/Alpha.jl/pull/12 + +| file | dependency | before | after | selected | +| --- | --- | --- | --- | --- | +| `Project.toml` | `Dep1` | `0.5, 0.6, 0.7` | `0.6, 0.7` | 0.7.2 on 1.12; 0.6.4 on 1.10 | +| `test/Project.toml` | `Tool` | `1, 2` | `2` | 2.0.0 on 1.10, 1.12 | + +### Beta + +| file | dependency | before | after | selected | +| --- | --- | --- | --- | --- | +| `Project.toml` | `Dep1` | `0.6, 0.7` | `0.7` | 0.7.2 on 1.12 | + +## Merged, awaiting release + +| entry | after | +| --- | --- | +| Beta `Project.toml` `Tool` | `2` | + +## Drift + +The default branch differs from the registered version: not edited. + +| entry | audited | detail | +| --- | --- | --- | +| Alpha `docs/Project.toml` `Doc` | `0.8, 0.9` | `0.9, 1` on the default branch, `0.8, 0.9` audited | + +## Rejected + +| entry | where | reason | +| --- | --- | --- | +| Beta `Project.toml` `Weak` | Beta/test@1.10 | manifest differs: A 1.0.0 → 1.1.0 \| odd second line | + +## Withheld + +Baseline failed in joint@1.10: lanes used there are unknown. + +| entry | would drop | +| --- | --- | +| Alpha `Project.toml` `Dep2` | `1` | + +## Blocked + +The newest specifier is never selected: information only, nothing is edited. + +| entry | newest | capped by | +| --- | --- | --- | +| Alpha `docs/Project.toml` `Doc` | `1` | Beta, Documenter | +| Beta `Project.toml` `Weak` | `0.2` | resolver preference | + +## Audited versions + +| package | version | repository | +| --- | --- | --- | +| Alpha | 0.3.1 | [example-org/Alpha.jl](https://github.com/example-org/Alpha.jl) | +| Beta | 1.2.0-beta | [example-org/Beta.jl](https://github.com/example-org/Beta.jl) | diff --git a/scripts/compat-prune/test/helpers.jl b/scripts/compat-prune/test/helpers.jl new file mode 100644 index 0000000..cb19db7 --- /dev/null +++ b/scripts/compat-prune/test/helpers.jl @@ -0,0 +1,88 @@ +# Shared by every test file: the scripts (their `main` blocks do not run when included), +# fixture paths, and small builders. + +const SCRIPTS = joinpath(@__DIR__, "..") +for f in ("collect.jl", "envs.jl", "resolve.jl", "classify.jl", "validate.jl", "edit.jl", "report.jl", "audit.jl") + include(joinpath(SCRIPTS, f)) +end + +const FIXTURES = joinpath(@__DIR__, "fixtures") +const COLLECTED = joinpath(FIXTURES, "collected") +const GOLDEN = joinpath(@__DIR__, "golden") +const UPDATE_GOLDEN = get(ENV, "COMPAT_PRUNE_UPDATE_GOLDEN", "") == "1" + +"A resolved environment result with the given name => version pairs." +ok(versions...) = Dict("ok" => true, "versions" => Dict{String,Any}(versions...)) + +"A failed environment result." +failed(msg="unsatisfiable") = Dict("ok" => false, "error" => msg) + +"A prune record." +prune(owner, file, dep, before, after) = + Dict{String,Any}("owner" => owner, "file" => file, "dep" => dep, "before" => before, "after" => after, + "removed" => setdiff(specifiers(before), specifiers(after)), "status" => "prune", + "evidence" => Dict{String,Any}()) + +"Compare `text` with the golden file `name`, or rewrite it under COMPAT_PRUNE_UPDATE_GOLDEN=1." +function golden(name, text) + path = joinpath(GOLDEN, name) + if UPDATE_GOLDEN || !isfile(path) + mkpath(dirname(path)) + write(path, text) + @info "golden file written: $name" + end + return text == read(path, String) +end + +"Run `f` with `GH` replaced by `fake`, restoring the real runner afterwards." +function with_gh(f, fake) + real = GH[] + GH[] = fake + try + f() + finally + GH[] = real + end +end + +# Captured from real runs (2026-10-15): DocStringExtensions restricted to its newest 0.10. +const CONFLICT_ON_DEP = """ +Unsatisfiable requirements detected for package DocStringExtensions [ffbed154]: + DocStringExtensions [ffbed154] log: + ├─possible versions are: 0.9.3 - 0.10.0 or uninstalled + ├─restricted to versions 0.10 by project [4e53ef5c], leaving only versions: 0.10.0 + │ └─project [4e53ef5c] log: + │ ├─possible versions are: 0.0.0 or uninstalled + │ └─project [4e53ef5c] is fixed to version 0.0.0 + └─restricted by compatibility requirements with SciMLBase [0bca4576] to versions: 0.9.5 — no versions left + └─SciMLBase [0bca4576] log: + ├─possible versions are: 2.77.2 - 3.58.0 or uninstalled + └─restricted by compatibility requirements with Deeper [00000000] to versions: 3 +""" +const CONFLICT_ON_OTHER = """ +Unsatisfiable requirements detected for package DocumenterVitepress [4710194d]: + DocumenterVitepress [4710194d] log: + ├─possible versions are: 0.3.4 - 0.3.8 or uninstalled + └─restricted by compatibility requirements with DocStringExtensions [ffbed154] to versions: uninstalled — no versions left + └─DocStringExtensions [ffbed154] log: + └─restricted to versions 0.10 by project [064cce67], leaving only versions: 0.10.0 +""" + +"Run `f(root)` on a temporary copy of the collected fixture, with its `envs.toml` built." +function with_collected(f) + mktempdir() do dir + root = joinpath(dir, "collected") + cp(COLLECTED, root) + open(io -> TOML.print(io, Dict("env" => build_envs(root))), joinpath(root, "envs.toml"), "w") + f(root) + end +end + +"Baseline results of the collected fixture, keyed `@`." +fixture_results() = Dict{String,Any}( + "joint@1.10" => ok("Alpha" => "0.3.1", "Beta" => "1.2.0-beta", "Dep1" => "0.6.4"), + "joint@1.12" => ok("Alpha" => "0.3.1", "Beta" => "1.2.0-beta", "Dep1" => "0.7.2"), + "Alpha/test@1.12" => ok("Alpha" => "0.3.1", "Beta" => "1.2.0-beta", "Dep1" => "0.7.2", "Tool" => "2.0.0"), + "Alpha/docs@1.12" => ok("Alpha" => "0.3.1", "Beta" => "1.2.0-beta", "Dep1" => "0.7.2", "Doc" => "0.9.3"), + "Beta/test@1.12" => ok("Beta" => "1.2.0-beta", "Dep1" => "0.7.2", "Tool" => "2.1.0"), +) diff --git a/scripts/compat-prune/test/runtests.jl b/scripts/compat-prune/test/runtests.jl new file mode 100644 index 0000000..efe63d3 --- /dev/null +++ b/scripts/compat-prune/test/runtests.jl @@ -0,0 +1,32 @@ +# Tests of compat-prune. +# +# usage: julia scripts/compat-prune/test/runtests.jl +# +# COMPAT_PRUNE_INTEGRATION=1 also run the integration tests (real Pkg resolutions against +# the local General registry, nothing downloaded; seconds) +# COMPAT_PRUNE_UPDATE_GOLDEN=1 rewrite the golden files instead of comparing to them +# +# Unit tests run on fixtures and fakes (no network, no resolution): the `gh` runner (`GH`) +# and the resolver of `simulate` are replaced by functions recording their calls. + +using Test + +include("helpers.jl") + +@testset "compat-prune" begin + include("test_common.jl") + include("test_collect.jl") + include("test_envs.jl") + include("test_classify.jl") + include("test_validate.jl") + include("test_audit.jl") + include("test_edit.jl") + include("test_report.jl") + include("test_publish.jl") + include("test_regression.jl") + if get(ENV, "COMPAT_PRUNE_INTEGRATION", "") == "1" + include("test_integration.jl") + else + @info "Integration tests skipped (set COMPAT_PRUNE_INTEGRATION=1)" + end +end diff --git a/scripts/compat-prune/test/test_audit.jl b/scripts/compat-prune/test/test_audit.jl new file mode 100644 index 0000000..ab52963 --- /dev/null +++ b/scripts/compat-prune/test/test_audit.jl @@ -0,0 +1,48 @@ +# audit.jl: coverage of the baselines, and the record summarizing an audit. + +@testset "audit" begin + @testset "coverage" begin + baselines = Dict( + "1.12" => Dict("env" => Dict("joint" => ok(), "A/test" => ok(), "A/docs" => ok())), + "1.10" => Dict("env" => Dict("joint" => ok(), "A/test" => failed(), "A/docs" => failed())), + ) + c = coverage(baselines) + @test [x["julia"] for x in c] == ["1.10", "1.12"] + @test c[1]["resolved"] == 1 && c[1]["total"] == 3 && c[1]["failed"] == ["A/docs", "A/test"] + @test c[2]["resolved"] == 3 && isempty(c[2]["failed"]) + end + + @testset "summarize" begin + p(owner, dep, status="prune") = merge(prune(owner, "Project.toml", dep, "1, 2", "2"), Dict("status" => status)) + classified = Dict( + "prune" => [p("A", "D1"), p("A", "D2"), p("B", "D3"), + p("B", "D5")], # obsolete lane of a blocked entry + "other" => [merge(p("B", "D4"), Dict("status" => "keep")), + merge(p("B", "D5"), Dict("status" => "blocked", "newest" => "3")), + merge(p("B", "D6"), Dict("status" => "idle")), + merge(p("A", "D7"), Dict("status" => "withheld"))], + ) + rejected = [Dict("owner" => "A", "file" => "Project.toml", "dep" => "D2", "where" => "joint@1.12", "reason" => "x")] + blocked = [Dict("owner" => "B", "file" => "Project.toml", "dep" => "D5", "newest" => "3", "where" => "joint@1.12", + "blockers" => ["Z"], "detail" => "")] + a = summarize(; date="2026-11-01 04:00 UTC", julia=["1.10", "1.12"], index=Dict("packages" => Dict()), + classified, rejected, blocked, registries=[Dict("name" => "General", "state" => "tree abc")], + coverage=[Dict("julia" => "1.12", "resolved" => 2, "total" => 2, "failed" => String[])]) + # accepted prunes: classified minus rejected, the blocked entry's obsolete lane included + @test sort(entry_key.(a["prune"])) == [("A", "Project.toml", "D1"), ("B", "Project.toml", "D3"), ("B", "Project.toml", "D5")] + @test a["rejected"] == rejected && a["blocked"] == blocked + # blocked records live in `blocked` (probes), not in `other` + @test sort([r["status"] for r in a["other"]]) == ["idle", "keep", "withheld"] + # one status per entry + @test a["record"]["entries"] == Dict("examined" => 7, "prune" => 2, "rejected" => 1, "keep" => 1, + "blocked" => 1, "idle" => 1, "withheld" => 1) + @test a["record"]["registries"] == [Dict("name" => "General", "state" => "tree abc")] + @test only(a["record"]["coverage"])["resolved"] == 2 + # the record survives a TOML round trip + mktempdir() do dir + path = joinpath(dir, "audit.toml") + open(io -> TOML.print(io, a), path, "w") + @test TOML.parsefile(path)["record"] == a["record"] + end + end +end diff --git a/scripts/compat-prune/test/test_classify.jl b/scripts/compat-prune/test/test_classify.jl new file mode 100644 index 0000000..03ba483 --- /dev/null +++ b/scripts/compat-prune/test/test_classify.jl @@ -0,0 +1,141 @@ +# classify.jl: which specifiers are used, and what becomes of each entry. + +const AUDITED_C = Dict("CTSolvers" => "0.5.8", "CTParser" => "0.9.5-beta", "CTBase" => "0.30.9") + +@testset "classify" begin + @testset "specifiers and lower bounds" begin + @test specifiers("0.9, 0.11,0.12") == ["0.9", "0.11", "0.12"] + @test specifiers("1") == ["1"] + @test lower("0.7 - 0.8") == v"0.7" + @test lower("^1.2") == v"1.2" + @test lower("~0.3.1") == v"0.3.1" + @test lower("=1.1.8") == v"1.1.8" + @test lower("1") == v"1" + end + + @testset "owner scoping keeps CTSolvers' CUDSS 0.7" begin + # OptimalControl's tests need CUDSS 0.7, CTDirect's take 0.8: both are CTSolvers users. + results = Dict( + "OptimalControl/test@1.12" => ok("CTSolvers" => "0.5.8", "CUDSS" => "0.7.0"), + "CTDirect/test@1.12" => ok("CTSolvers" => "0.5.8", "CUDSS" => "0.8.1"), + # an older CTSolvers selecting CUDSS 0.6 is no evidence for the audited file + "Old/test@1.12" => ok("CTSolvers" => "0.4.0", "CUDSS" => "0.6.3"), + ) + r = classify_entry("CTSolvers", "Project.toml", "CUDSS", "0.6, 0.7, 0.8", results, AUDITED_C) + @test r["status"] == "prune" + @test r["removed"] == ["0.6"] + @test r["after"] == "0.7, 0.8" + @test r["evidence"] == Dict("0.7" => ["OptimalControl/test@1.12=0.7.0"], "0.8" => ["CTDirect/test@1.12=0.8.1"]) + end + + @testset "a lane used on Julia 1.10 only is kept" begin + results = Dict("CTBase/test@1.10" => ok("CTBase" => "0.30.9", "JET" => "0.9.18"), + "CTBase/test@1.12" => ok("CTBase" => "0.30.9", "JET" => "0.12.3")) + r = classify_entry("CTBase", "Project.toml", "JET", "0.9, 0.11, 0.12", results, AUDITED_C) + @test r["status"] == "prune" + @test r["removed"] == ["0.11"] + @test r["after"] == "0.9, 0.12" + end + + @testset "declaration order is kept in `after`" begin + results = Dict("X@1.12" => ok("CTBase" => "0.30.9", "D" => "2.0.0"), + "Y@1.12" => ok("CTBase" => "0.30.9", "D" => "1.5.0")) + r = classify_entry("CTBase", "Project.toml", "D", "1.5, 0.9, 2", results, AUDITED_C) + @test r["after"] == "1.5, 2" + end + + @testset "blocked, idle, keep" begin + results = Dict("CTParser/test@1.12" => ok("CTParser" => "0.9.5-beta", "CTBase" => "0.29.3", "CUDSS" => "0.7.0")) + r = classify_entry("CTParser", "test/Project.toml", "CTBase", "0.29, 0.30", results, AUDITED_C) + @test r["status"] == "blocked" + @test r["newest"] == "0.30" + @test r["applies_in"] == ["CTParser/test"] + @test !haskey(r, "prunable") + # blocked, with an obsolete lane below the one in use + r = classify_entry("CTParser", "test/Project.toml", "CUDSS", "0.6, 0.7, 0.8", results, AUDITED_C) + @test r["status"] == "blocked" + @test r["prunable"] == ["0.6"] + # an unused lane between the used one and the newest is blocked, never pruned + r = classify_entry("CTParser", "test/Project.toml", "CUDSS", "0.7, 0.8, 0.9", results, AUDITED_C) + @test r["status"] == "blocked" && r["newest"] == "0.9" && !haskey(r, "prunable") + # resolved nowhere the entry applies + r = classify_entry("CTParser", "test/Project.toml", "CUDA", "5, 6", results, AUDITED_C) + @test r["status"] == "idle" + # every lane in use + results = Dict("CTParser/test@1.10" => ok("CTParser" => "0.9.5-beta", "JET" => "0.9.18"), + "CTParser/test@1.12" => ok("CTParser" => "0.9.5-beta", "JET" => "0.12.3")) + r = classify_entry("CTParser", "test/Project.toml", "JET", "0.9, 0.12", results, AUDITED_C) + @test r["status"] == "keep" + end + + @testset "a failed environment is no evidence" begin + results = Dict("CTBase/test@1.10" => failed(), + "CTBase/test@1.12" => ok("CTBase" => "0.30.9", "JET" => "0.12.3")) + r = classify_entry("CTBase", "Project.toml", "JET", "0.9, 0.12", results, AUDITED_C) + @test r["status"] == "prune" && r["after"] == "0.12" + end + + @testset "withhold" begin + r = prune("CTBase", "Project.toml", "JET", "0.9, 0.12", "0.12") + results = Dict("CTBase/test@1.10" => failed(), "CTBase/test@1.12" => ok()) + kept, withheld = withhold([r], results) + @test isempty(kept) + @test only(withheld)["status"] == "withheld" + @test occursin("CTBase/test@1.10", only(withheld)["reason"]) + kept, withheld = withhold([r], Dict("CTBase/test@1.12" => ok())) + @test kept == [r] && isempty(withheld) + end + + @testset "classify_all on the fixture" begin + with_collected() do root + prunes, others = classify_all(root, fixture_results()) + got = Dict(entry_key(r) => r for r in vcat(prunes, others)) + # Alpha's Dep1: 0.6 on 1.10 (joint), 0.7 on 1.12 → 0.5 is obsolete + p = got[("Alpha", "Project.toml", "Dep1")] + @test p["status"] == "prune" && p["after"] == "0.6, 0.7" + # test/ entry: Tool 2 only + p = got[("Alpha", "test/Project.toml", "Tool")] + @test p["status"] == "prune" && p["after"] == "2" + # docs/ entry: Doc 1 never selected + b = got[("Alpha", "docs/Project.toml", "Doc")] + @test b["status"] == "blocked" && b["newest"] == "1" && b["applies_in"] == ["Alpha/docs"] + # Beta's Dep1: both lanes in use + @test got[("Beta", "Project.toml", "Dep1")]["status"] == "keep" + # Beta's weak dep: in no resolved environment + @test got[("Beta", "Project.toml", "Weak")]["status"] == "idle" + # single-specifier entries are not classified + @test !haskey(got, ("Beta", "Project.toml", "Tool")) + @test !haskey(got, ("Alpha", "Project.toml", "Beta")) + @test length(prunes) == 2 && length(others) == 3 + end + end + + @testset "classify_all: obsolete lane of a blocked entry, failed baseline" begin + with_collected() do root + results = fixture_results() + # Doc: 0.8 lane obsolete, 1 blocked + path = joinpath(root, "registered", "Alpha", "docs", "Project.toml") + write(path, replace(read(path, String), "Doc = \"0.9, 1\"" => "Doc = \"0.8, 0.9, 1\"")) + prunes, others = classify_all(root, results) + p = only(filter(r -> r["dep"] == "Doc", prunes)) + @test p["after"] == "0.9, 1" && p["removed"] == ["0.8"] + @test any(r -> r["dep"] == "Doc" && r["status"] == "blocked", others) + # a failed baseline withholds every prune + results["Beta/test@1.10"] = failed() + prunes, others = classify_all(root, results) + @test isempty(prunes) + @test count(r -> r["status"] == "withheld", others) == 3 + end + end + + @testset "load_results" begin + mktempdir() do dir + for (j, v) in (("1.10.11", "0.6.4"), ("1.12.6", "0.7.2")) + open(io -> TOML.print(io, Dict("julia" => j, "env" => Dict("joint" => ok("Dep1" => v)))), + joinpath(dir, "b-$j.toml"), "w") + end + r = load_results(joinpath.(dir, ["b-1.10.11.toml", "b-1.12.6.toml"])) + @test r == Dict("joint@1.10" => ok("Dep1" => "0.6.4"), "joint@1.12" => ok("Dep1" => "0.7.2")) + end + end +end diff --git a/scripts/compat-prune/test/test_collect.jl b/scripts/compat-prune/test/test_collect.jl new file mode 100644 index 0000000..32fec21 --- /dev/null +++ b/scripts/compat-prune/test/test_collect.jl @@ -0,0 +1,91 @@ +# collect.jl: files fetched from the registered tree and the default branch (fake `gh`). + +""" +A fake GitHub: `repos` maps "owner/repo" to (default branch, Dict(treeish => Dict(path => text))). +Returns the runner and the vector of the calls it received. +""" +function fake_github(repos) + calls = Vector{Vector{String}}() + blobs = Dict{String,String}() + function runner(args) + push!(calls, args) + @assert args[1] == "api" + path = args[2] + m = match(r"^repos/([^/]+/[^/]+)$", path) + m === nothing || return repos[m[1]][1] * "\n" # --jq .default_branch + m = match(r"^repos/([^/]+/[^/]+)/git/trees/([^?]+)\?recursive=1$", path) + if m !== nothing + files = repos[m[1]][2][m[2]] + lines = String[] + for (p, text) in sort(collect(files)) + sha = string(hash((m[1], m[2], p)); base=16) + blobs[sha] = text + push!(lines, "$p $sha") + end + return join(lines, "\n") * "\n" + end + m = match(r"^repos/[^/]+/[^/]+/git/blobs/(\w+)$", path) + m === nothing || return blobs[m[1]] + error("unexpected gh call: $args") + end + return runner, calls +end + +@testset "collect" begin + repos = Dict( + "org/Alpha.jl" => ("main", Dict( + "t-alpha" => Dict("Project.toml" => "name = \"Alpha\"\n", "test/Project.toml" => "[deps]\n", + "src/Alpha.jl" => "module Alpha end\n", "docs/make.jl" => ""), + "main" => Dict("Project.toml" => "name = \"Alpha\"\nversion = \"0.3.2\"\n"), + )), + "org/Beta.jl" => ("dev", Dict( + "t-beta" => Dict("Project.toml" => "name = \"Beta\"\n", "docs/Project.toml" => "[deps]\n"), + "dev" => Dict("Project.toml" => "name = \"Beta\"\n", "docs/Project.toml" => "[deps]\n"), + )), + ) + runner, calls = fake_github(repos) + mktempdir() do out + index = with_gh(runner) do + collect_files(out, Dict("Alpha" => ("u-a", "org/Alpha.jl"), "Beta" => ("u-b", "org/Beta.jl")), + Dict("Alpha" => "0.3.1", "Beta" => "1.2.0-beta"), + Dict("Alpha" => "t-alpha", "Beta" => "t-beta")) + end + @testset "index" begin + @test index["Alpha"]["version"] == "0.3.1" + @test index["Alpha"]["tree"] == "t-alpha" + @test index["Alpha"]["default_branch"] == "main" + @test index["Alpha"]["files"] == ["Project.toml", "test/Project.toml"] + @test index["Alpha"]["default_files"] == ["Project.toml"] + @test index["Beta"]["files"] == ["Project.toml", "docs/Project.toml"] + @test index["Beta"]["default_branch"] == "dev" + @test TOML.parsefile(joinpath(out, "index.toml"))["packages"] == index + end + @testset "files" begin + # the registered tree and the default branch are kept apart + @test read(joinpath(out, "registered", "Alpha", "Project.toml"), String) == "name = \"Alpha\"\n" + @test read(joinpath(out, "default", "Alpha", "Project.toml"), String) == + "name = \"Alpha\"\nversion = \"0.3.2\"\n" + @test isfile(joinpath(out, "registered", "Alpha", "test", "Project.toml")) + @test !isfile(joinpath(out, "default", "Alpha", "test", "Project.toml")) + @test isfile(joinpath(out, "default", "Beta", "docs", "Project.toml")) + end + @testset "calls" begin + # only the audited files are downloaded; the tree is read by its registered sha + fetched = [c[2] for c in calls if occursin("/git/blobs/", c[2])] + @test length(fetched) == 2 + 1 + 2 + 2 + @test any(c -> c[2] == "repos/org/Alpha.jl/git/trees/t-alpha?recursive=1", calls) + @test any(c -> c[2] == "repos/org/Beta.jl/git/trees/dev?recursive=1", calls) + # blobs are fetched raw + @test all(c -> c[3:4] == ["-H", "Accept: application/vnd.github.raw"], + [c for c in calls if occursin("/git/blobs/", c[2])]) + end + end + + @testset "fetch_files with no audited file" begin + runner, _ = fake_github(Dict("org/Empty.jl" => ("main", Dict("t" => Dict("README.md" => ""))))) + mktempdir() do out + @test with_gh(() -> fetch_files("org/Empty.jl", "t", out), runner) == String[] + @test isempty(readdir(out)) + end + end +end diff --git a/scripts/compat-prune/test/test_common.jl b/scripts/compat-prune/test/test_common.jl new file mode 100644 index 0000000..e5ec7b0 --- /dev/null +++ b/scripts/compat-prune/test/test_common.jl @@ -0,0 +1,104 @@ +# common.jl: specifier arithmetic, where an entry applies, simulation (fake resolver). + +const AUDITED = Dict("CTSolvers" => "0.5.8", "CTParser" => "0.9.5-beta", "CTBase" => "0.30.9") + +@testset "common" begin + @testset "parse_list" begin + @test parse_list("[\"CTBase\",\"CTParser\"]") == ["CTBase", "CTParser"] + @test parse_list("[ \"1.10\" , \"1.12\" ]") == ["1.10", "1.12"] + @test isempty(parse_list("[]")) + end + + @testset "render" begin + @test render(spec("0.6, 0.7, 0.8") ∩ spec("0.7, 0.8")) == "0.7 - 0.8" + @test render(spec("0.9, 0.11, 0.12") ∩ spec("0.9, 0.12")) == "0.9, 0.12" + @test render(spec(">= 0") ∩ spec("1")) == "1" + @test isempty(render(spec("0.7") ∩ spec("0.8"))) + # a rendered spec parses back to the same set + for s in ("0.6, 0.7, 0.8", "1.8, 2", "0.30.8", "5 - 6") + @test spec(render(spec(s))) == spec(s) + end + end + + @testset "entry_key" begin + p = prune("CTSolvers", "Project.toml", "CUDSS", "0.6, 0.7, 0.8", "0.7, 0.8") + @test entry_key(p) == ("CTSolvers", "Project.toml", "CUDSS") + end + + @testset "applies" begin + v = Dict("CTSolvers" => "0.5.8") + @test applies("CTSolvers", "Project.toml", "OptimalControl/test", v, AUDITED) + @test applies("CTSolvers", "Project.toml", "joint", v, AUDITED) + # another version of the owner carries other compat entries + @test !applies("CTSolvers", "Project.toml", "X/test", Dict("CTSolvers" => "0.4.0"), AUDITED) + @test !applies("CTSolvers", "Project.toml", "X/test", Dict{String,String}(), AUDITED) + # test/ and docs/ entries apply in their own environment only + @test applies("CTParser", "test/Project.toml", "CTParser/test", v, AUDITED) + @test applies("CTParser", "docs/Project.toml", "CTParser/docs", v, AUDITED) + @test !applies("CTParser", "test/Project.toml", "CTParser/docs", v, AUDITED) + @test !applies("CTParser", "docs/Project.toml", "OptimalControl/docs", v, AUDITED) + @test !applies("CTParser", "test/Project.toml", "joint", v, AUDITED) + end + + @testset "narrowings" begin + prunes = [prune("CTSolvers", "Project.toml", "CUDSS", "0.6, 0.7, 0.8", "0.7, 0.8"), + prune("CTSolvers", "docs/Project.toml", "CUDSS", "0.6, 0.7, 0.8", "0.8"), + prune("CTBase", "Project.toml", "JET", "0.9, 0.11, 0.12", "0.9, 0.12")] + v = Dict("CTSolvers" => "0.5.8", "CUDSS" => "0.8.1", "CTBase" => "0.30.9") + # JET absent: nothing to bind; the docs entry intersects with the root one + @test narrowings(prunes, "CTSolvers/docs", v, AUDITED) == Dict("CUDSS" => "0.8") + @test narrowings(prunes, "CTDirect/test", v, AUDITED) == Dict("CUDSS" => "0.7, 0.8") + # the owner is absent: the entry does not apply + @test isempty(narrowings(prunes, "CTDirect/test", Dict("CUDSS" => "0.8.1"), AUDITED)) + @test narrowings(prunes, "CTBase/test", Dict("CTBase" => "0.30.9", "JET" => "0.12.3"), AUDITED) == + Dict("JET" => "0.9, 0.12") + @test isempty(narrowings([], "joint", v, AUDITED)) + end + + @testset "simulate (fake resolver)" begin + env = Dict("deps" => Dict("Own" => "u-own", "A" => "u-a"), + "compat" => Dict("Own" => "=1.0.0", "A" => "0.6, 0.7, 0.8"), + "pins" => Dict("Own" => "1.0.0")) + uuids = Dict("A" => "u-a", "T" => "u-t") + calls = [] + resolver(versions) = (deps, compat) -> (push!(calls, (copy(deps), copy(compat))); + Dict("ok" => true, "versions" => Dict{String,Any}(versions), "trees" => Dict())) + # the narrowed compat is intersected with the environment's own + r = simulate("Own/test", env, Dict("A" => "0.7, 0.8"), uuids; resolver=resolver(Dict("Own" => "1.0.0", "A" => "0.8.1"))) + @test r == Dict("ok" => true, "versions" => Dict("Own" => "1.0.0", "A" => "0.8.1")) + @test last(calls)[2]["A"] == "0.7 - 0.8" + @test last(calls)[2]["Own"] == "=1.0.0" + # a transitive dependency is listed to carry its compat entry + simulate("Own/test", env, Dict("T" => "2"), uuids; resolver=resolver(Dict("Own" => "1.0.0", "T" => "2.1.0"))) + @test last(calls)[1]["T"] == "u-t" + @test last(calls)[2]["T"] == "2" + # the environment itself is not modified + @test !haskey(env["deps"], "T") + # an empty intersection fails without calling the resolver + n = length(calls) + r = simulate("Own/test", env, Dict("A" => "0.5"), uuids; resolver=resolver(Dict())) + @test !r["ok"] && occursin("empty compat intersection for A", r["error"]) + @test length(calls) == n + # a pinned package resolved at another version (e.g. a stable release next to the + # audited prerelease) is a failure + r = simulate("Own/test", env, Dict(), uuids; resolver=resolver(Dict("Own" => "1.0.1"))) + @test !r["ok"] && occursin("pinned versions not selected: Own 1.0.1 (audited 1.0.0)", r["error"]) + # a resolver failure is passed through + r = simulate("Own/test", env, Dict(), uuids; resolver=(d, c) -> failed("boom")) + @test r == failed("boom") + end + + @testset "load_collected" begin + mktempdir() do dir + cp(COLLECTED, joinpath(dir, "c")) + root = joinpath(dir, "c") + write(joinpath(root, "envs.toml"), "[env]\n") + envs, audited, uuids = load_collected(root) + @test audited == Dict("Alpha" => "0.3.1", "Beta" => "1.2.0-beta") + # every package named in an audited file, from [deps], [weakdeps] and [extras] + for n in ("Beta", "Dep1", "Tool", "Doc", "Weak", "Unused", "Test") + @test haskey(uuids, n) + end + end + end +end diff --git a/scripts/compat-prune/test/test_edit.jl b/scripts/compat-prune/test/test_edit.jl new file mode 100644 index 0000000..12dff79 --- /dev/null +++ b/scripts/compat-prune/test/test_edit.jl @@ -0,0 +1,153 @@ +# edit.jl: textual edit of one [compat] line; outcome of each prune against a checkout. + +const PROJECT = """ +name = "Pkg" +uuid = "aaaaaaaa-0000-4000-8000-000000000001" +version = "0.6.1" + +[deps] +CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +JETLS = "ffffffff-0000-4000-8000-00000000000f" + +[compat] +CUDSS = "0.6, 0.7, 0.8" +JET = "0.9, 0.11, 0.12" +JETLS = "0.9, 0.11" +julia = "1.10" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +""" + +"`text` with line `old` replaced by `new`, asserting it was there." +function swap(text, old, new) + @assert occursin(old, text) old + replace(text, old => new) +end + +@testset "edit" begin + @testset "edits the value and nothing else" begin + out, status, detail = edit_compat(PROJECT, "JET", "0.9, 0.11, 0.12", "0.9, 0.12") + @test status == "edited" && detail == "" + @test out == swap(PROJECT, "JET = \"0.9, 0.11, 0.12\"", "JET = \"0.9, 0.12\"") + # a key that only starts with the same name is not touched + @test occursin("JETLS = \"0.9, 0.11\"", out) + end + + @testset "layout is preserved" begin + cases = [ + "JET = \"0.9, 0.11, 0.12\" # kept for Julia 1.10" => "JET = \"0.9, 0.12\" # kept for Julia 1.10", + "JET = \"0.9, 0.11, 0.12\"" => "JET = \"0.9, 0.12\"", + " JET = \"0.9, 0.11, 0.12\"" => " JET = \"0.9, 0.12\"", + "JET = '0.9, 0.11, 0.12'" => "JET = '0.9, 0.12'", + "\"JET\" = \"0.9, 0.11, 0.12\"" => "\"JET\" = \"0.9, 0.12\"", + ] + for (old, new) in cases + text = swap(PROJECT, "JET = \"0.9, 0.11, 0.12\"", old) + out, status, _ = edit_compat(text, "JET", "0.9, 0.11, 0.12", "0.9, 0.12") + @test status == "edited" + @test out == swap(text, old, new) + end + end + + @testset "CRLF line endings are kept" begin + text = replace(PROJECT, "\n" => "\r\n") + out, status, _ = edit_compat(text, "CUDSS", "0.6, 0.7, 0.8", "0.7, 0.8") + @test status == "edited" + @test out == replace(swap(PROJECT, "CUDSS = \"0.6, 0.7, 0.8\"", "CUDSS = \"0.7, 0.8\""), "\n" => "\r\n") + end + + @testset "spacing inside the value does not matter" begin + text = swap(PROJECT, "CUDSS = \"0.6, 0.7, 0.8\"", "CUDSS = \"0.6,0.7, 0.8\"") + out, status, _ = edit_compat(text, "CUDSS", "0.6, 0.7, 0.8", "0.7, 0.8") + @test status == "edited" && occursin("CUDSS = \"0.7, 0.8\"", out) + end + + @testset "only the [compat] section is read" begin + # same key in [deps] (a uuid) and in an [extras]-like section after [compat] + text = PROJECT * "\n[sources]\nJET = {path = \"../JET\"}\n" + out, status, _ = edit_compat(text, "JET", "0.9, 0.11, 0.12", "0.9, 0.12") + @test status == "edited" + @test occursin("JET = \"c3a54625-cd67-489e-a8e7-0a5a0ff4e31b\"", out) + @test occursin("JET = {path = \"../JET\"}", out) + # a [compat] entry declared only after a nested table header is not mistaken + text = swap(PROJECT, "[compat]", "[compat.extra]") + @test edit_compat(text, "JET", "0.9, 0.11, 0.12", "0.9, 0.12")[2] == "drift" + end + + @testset "already, drift" begin + text = swap(PROJECT, "JET = \"0.9, 0.11, 0.12\"", "JET = \"0.9, 0.12\"") + out, status, detail = edit_compat(text, "JET", "0.9, 0.11, 0.12", "0.9, 0.12") + @test status == "already" && out == text && occursin("0.9, 0.12", detail) + # the branch moved the entry elsewhere + text = swap(PROJECT, "JET = \"0.9, 0.11, 0.12\"", "JET = \"0.12\"") + out, status, detail = edit_compat(text, "JET", "0.9, 0.11, 0.12", "0.9, 0.12") + @test status == "drift" && out == text + @test detail == "`0.12` on the default branch, `0.9, 0.11, 0.12` audited" + # the entry is gone + text = swap(PROJECT, "JET = \"0.9, 0.11, 0.12\"\n", "") + out, status, detail = edit_compat(text, "JET", "0.9, 0.11, 0.12", "0.9, 0.12") + @test status == "drift" && out == text && occursin("no `JET` entry", detail) + # no [compat] at all + @test edit_compat("name = \"X\"\n", "JET", "0.9", "0.12")[2] == "drift" + end + + @testset "check_edit" begin + edited = swap(PROJECT, "JET = \"0.9, 0.11, 0.12\"", "JET = \"0.9, 0.12\"") + @test check_edit(PROJECT, edited, "JET", "0.9, 0.12") === nothing + @test_throws ErrorException check_edit(PROJECT, swap(edited, "0.6.1", "0.6.0"), "JET", "0.9, 0.12") + @test_throws ErrorException check_edit(PROJECT, edited, "JET", "0.12") + end + + @testset "plan_edits" begin + files = Dict(("Pkg", "Project.toml") => PROJECT, + ("Pkg", "docs/Project.toml") => "[compat]\nJET = \"0.12\"\n") + read_file(owner, file) = get(files, (owner, file), nothing) + audit = Dict("prune" => [ + prune("Pkg", "Project.toml", "JET", "0.9, 0.11, 0.12", "0.9, 0.12"), + prune("Pkg", "Project.toml", "CUDSS", "0.6, 0.7, 0.8", "0.7, 0.8"), # same file, accumulated + prune("Pkg", "docs/Project.toml", "JET", "0.11, 0.12", "0.12"), # already + prune("Pkg", "test/Project.toml", "JET", "0.11, 0.12", "0.12"), # no such file + prune("Pkg", "Project.toml", "JETLS", "0.8, 0.9, 0.11", "0.11"), # drift: the branch has "0.9, 0.11" + ]) + records, texts = plan_edits(audit, read_file) + status = Dict(entry_key(r) => r["status"] for r in records) + @test status[("Pkg", "Project.toml", "JET")] == "edited" + @test status[("Pkg", "Project.toml", "CUDSS")] == "edited" + @test status[("Pkg", "docs/Project.toml", "JET")] == "already" + @test status[("Pkg", "test/Project.toml", "JET")] == "drift" + @test status[("Pkg", "Project.toml", "JETLS")] == "drift" + @test collect(keys(texts)) == [("Pkg", "Project.toml")] + out = texts[("Pkg", "Project.toml")] + @test out == swap(swap(PROJECT, "JET = \"0.9, 0.11, 0.12\"", "JET = \"0.9, 0.12\""), + "CUDSS = \"0.6, 0.7, 0.8\"", "CUDSS = \"0.7, 0.8\"") + # the version line is never touched (regression: copying a collected file reverted it) + @test occursin("version = \"0.6.1\"", out) + end + + @testset "against the fixture's default branches" begin + audit = Dict("prune" => [ + prune("Alpha", "Project.toml", "Dep1", "0.5, 0.6, 0.7", "0.6, 0.7"), + prune("Beta", "Project.toml", "Dep1", "0.6, 0.7", "0.7"), + prune("Alpha", "docs/Project.toml", "Doc", "0.9, 1", "1"), + ]) + records, texts = plan_edits(audit, read_checkout(joinpath(COLLECTED, "default"))) + status = Dict(entry_key(r) => r["status"] for r in records) + # the default branch bumped the version and commented the line: still edited, both kept + @test status[("Alpha", "Project.toml", "Dep1")] == "edited" + out = texts[("Alpha", "Project.toml")] + @test occursin("version = \"0.3.2\"", out) + @test occursin("Dep1 = \"0.6, 0.7\" # lanes kept by CompatHelper", out) + # Beta's default branch already dropped 0.6 + @test status[("Beta", "Project.toml", "Dep1")] == "already" + @test status[("Alpha", "docs/Project.toml", "Doc")] == "edited" + end + + @testset "read_checkout" begin + r = read_checkout(joinpath(COLLECTED, "default")) + @test startswith(r("Alpha", "Project.toml"), "name = \"Alpha\"") + @test r("Alpha", "test/Project.toml") === nothing + @test r("Nope", "Project.toml") === nothing + end +end diff --git a/scripts/compat-prune/test/test_envs.jl b/scripts/compat-prune/test/test_envs.jl new file mode 100644 index 0000000..4e5e65c --- /dev/null +++ b/scripts/compat-prune/test/test_envs.jl @@ -0,0 +1,69 @@ +# envs.jl: environments built from the synthetic collected fixture. + +@testset "envs" begin + envs = build_envs(COLLECTED) + + @testset "names" begin + # Beta has no docs/Project.toml + @test sort(collect(keys(envs))) == ["Alpha/docs", "Alpha/test", "Beta/test", "joint"] + @test envs["Alpha/test"]["owner"] == "Alpha" && envs["Alpha/test"]["kind"] == "test" + @test envs["joint"]["owner"] == "" && envs["joint"]["kind"] == "joint" + end + + @testset "joint" begin + e = envs["joint"] + @test sort(collect(keys(e["deps"]))) == ["Alpha", "Beta"] + # every package is pinned; a prerelease is pinned without its suffix + @test e["compat"] == Dict("Alpha" => "=0.3.1", "Beta" => "=1.2.0") + @test e["pins"] == Dict("Alpha" => "0.3.1", "Beta" => "1.2.0-beta") + end + + @testset "test env from test/Project.toml" begin + e = envs["Alpha/test"] + @test sort(collect(keys(e["deps"]))) == ["Alpha", "Test", "Tool"] + # compat of a dep the file does not list is dropped; only the owner is pinned + @test e["compat"] == Dict("Tool" => "1, 2", "Alpha" => "=0.3.1") + @test e["pins"] == Dict("Alpha" => "0.3.1") + end + + @testset "test env from [targets]" begin + e = envs["Beta/test"] + # [targets] test, not every [extras]: Unused is left out, the weak dep is in + @test sort(collect(keys(e["deps"]))) == ["Beta", "Test", "Tool", "Weak"] + @test e["deps"]["Weak"] == "44444444-0000-4000-8000-000000000004" + # the package's own compat, restricted to those deps, plus julia + @test e["compat"] == Dict("Tool" => "2", "Weak" => "0.1, 0.2", "julia" => "1.10", "Beta" => "=1.2.0") + @test e["pins"] == Dict("Beta" => "1.2.0-beta") + end + + @testset "docs env" begin + e = envs["Alpha/docs"] + # the package itself is added: docs/make.jl develops it + @test sort(collect(keys(e["deps"]))) == ["Alpha", "Doc"] + @test e["compat"] == Dict("Doc" => "0.9, 1", "Alpha" => "=0.3.1") + end + + @testset "test env without [targets]" begin + mktempdir() do dir + write(joinpath(dir, "Project.toml"), """ + name = "Gamma" + [extras] + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" + [compat] + Aqua = "0.8" + """) + deps, compat = test_env("Gamma", "u-g", dir) + @test sort(collect(keys(deps))) == ["Aqua", "Gamma", "Test"] + @test compat == Dict("Aqua" => "0.8") + end + end + + @testset "envs.toml round trip" begin + mktempdir() do dir + path = joinpath(dir, "envs.toml") + open(io -> TOML.print(io, Dict("env" => envs)), path, "w") + @test TOML.parsefile(path)["env"] == envs + end + end +end diff --git a/scripts/compat-prune/test/test_integration.jl b/scripts/compat-prune/test/test_integration.jl new file mode 100644 index 0000000..afa470d --- /dev/null +++ b/scripts/compat-prune/test/test_integration.jl @@ -0,0 +1,241 @@ +# Integration: real Pkg resolutions against the local General registry, then the +# scripts run as commands on a one-package ecosystem: Example 0.5.5, whose docs declare +# `OrderedCollections = "1, 2"` (only 2 is selectable with nothing else in the way). +# +# Run with COMPAT_PRUNE_INTEGRATION=1. Needs General installed (any recent copy). + +const EXAMPLE = "7876af07-990d-54b4-ab0e-23690620f79a" +const ORDERED = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +const JULIA = Base.julia_cmd() + +"Write a collected directory for Example 0.5.5 into `root`; `docs_compat` is the docs compat of OrderedCollections." +function example_collected(root; docs_compat="1, 2") + reg = joinpath(root, "registered", "Example") + mkpath(joinpath(reg, "docs")) + write(joinpath(reg, "Project.toml"), """ + name = "Example" + uuid = "$EXAMPLE" + version = "0.5.5" + + [compat] + julia = "1" + + [extras] + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + + [targets] + test = ["Test"] + """) + write(joinpath(reg, "docs", "Project.toml"), """ + [deps] + OrderedCollections = "$ORDERED" + + [compat] + OrderedCollections = "$docs_compat" + """) + cp(joinpath(root, "registered"), joinpath(root, "default")) + open(joinpath(root, "index.toml"), "w") do io + TOML.print(io, Dict("julia" => string(VERSION), "packages" => Dict("Example" => Dict( + "uuid" => EXAMPLE, "version" => "0.5.5", "repo" => "JuliaLang/Example.jl", "tree" => "0", + "default_branch" => "master", "files" => ["Project.toml", "docs/Project.toml"], + "default_files" => ["Project.toml", "docs/Project.toml"])))) + end + return root +end + +"Run a script of compat-prune with `args` on the running Julia; return its output." +run_script(script, args...) = read(`$JULIA --startup-file=no $(joinpath(SCRIPTS, script)) $args`, String) + +@testset "integration" begin + @testset "resolve_env" begin + r = resolve_env(Dict("Example" => EXAMPLE), Dict("Example" => "0.5")) + @test r["ok"] + @test startswith(r["versions"]["Example"], "0.5.") + # the registered tree is known, stdlibs have none + @test length(r["trees"]["Example"]) == 40 + r = resolve_env(Dict("Example" => EXAMPLE, "LinearAlgebra" => "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"), Dict()) + @test haskey(r["versions"], "LinearAlgebra") && !haskey(r["trees"], "LinearAlgebra") + # unsatisfiable: a result, not an exception + r = resolve_env(Dict("Example" => EXAMPLE), Dict("Example" => "99")) + @test !r["ok"] && occursin("Example", r["error"]) + # anything else is a bug of the caller: thrown + @test_throws Exception resolve_env(Dict("Example" => EXAMPLE), Dict("Example" => "not a version")) + end + + @testset "registry_snapshot" begin + snap = registry_snapshot() + @test any(r -> r["name"] == "General", snap) + @test issorted([r["name"] for r in snap]) + @test all(r -> occursin(r"^(tree|commit) [0-9a-f]{40}$|^unknown$", r["state"]), snap) + end + + @testset "registry_lookup" begin + @test registry_lookup("Example") == (EXAMPLE, "JuliaLang/Example.jl") + @test_throws ErrorException registry_lookup("NoSuchPackageForCompatPrune") + end + + @testset "collect.jl as a command (fake gh on PATH)" begin + mktempdir() do dir + # canned answers of `gh api …`, one file per path + answers = joinpath(dir, "answers") + mkpath(answers) + put(path, text) = write(joinpath(answers, replace(path, r"[/?=]" => "_")), text) + project = "name = \"Example\"\nuuid = \"$EXAMPLE\"\nversion = \"0.5.5\"\n" + tree = resolve_env(Dict("Example" => EXAMPLE), Dict("Example" => "=0.5.5"))["trees"]["Example"] + put("repos/JuliaLang/Example.jl", "master\n") + put("repos/JuliaLang/Example.jl/git/trees/$tree?recursive=1", "Project.toml b1\nsrc/Example.jl b2\n") + put("repos/JuliaLang/Example.jl/git/trees/master?recursive=1", "Project.toml b3\n") + put("repos/JuliaLang/Example.jl/git/blobs/b1", project) + put("repos/JuliaLang/Example.jl/git/blobs/b3", project * "# moved on\n") + bin = joinpath(dir, "bin") + mkpath(bin) + write(joinpath(bin, "gh"), """ + #!/bin/sh + exec cat "$answers/\$(printf '%s' "\$2" | tr '/?=' '___')" + """) + chmod(joinpath(bin, "gh"), 0o755) + out = joinpath(dir, "audit") + withenv("PATH" => bin * ":" * ENV["PATH"]) do + run_script("collect.jl", "[\"Example\"]", out) + end + index = TOML.parsefile(joinpath(out, "index.toml"))["packages"]["Example"] + @test index["version"] == "0.5.5" && index["tree"] == tree + @test index["repo"] == "JuliaLang/Example.jl" && index["default_branch"] == "master" + @test read(joinpath(out, "registered", "Example", "Project.toml"), String) == project + @test endswith(read(joinpath(out, "default", "Example", "Project.toml"), String), "# moved on\n") + end + end + + @testset "checkout.sh (local origins)" begin + mktempdir() do dir + # two packages, one with a dict-valued index entry each (sorting must not compare them) + index = Dict("Beta" => Dict("repo" => "org/Beta.jl", "default_branch" => "dev"), + "Alpha" => Dict("repo" => "org/Alpha.jl", "default_branch" => "main")) + for (name, p) in index + bare = joinpath(dir, "org", "$name.jl.git") + work = joinpath(dir, "seed-$name") + run(`git -c init.defaultBranch=$(p["default_branch"]) init --quiet $work`) + write(joinpath(work, "Project.toml"), "name = \"$name\"\n") + run(`git -C $work add --all`) + run(`git -C $work -c user.name=t -c user.email=t@example.org commit --quiet -m init`) + run(`git clone --quiet --bare $work $bare`) + end + audit = joinpath(dir, "audit") + mkpath(audit) + open(io -> TOML.print(io, Dict("packages" => index)), joinpath(audit, "index.toml"), "w") + withenv("COMPAT_PRUNE_GIT_BASE" => "file://$dir") do + run(`$(joinpath(SCRIPTS, "checkout.sh")) $audit $(joinpath(dir, "checkouts"))`) + end + for (name, p) in index + co = joinpath(dir, "checkouts", name) + @test read(joinpath(co, "Project.toml"), String) == "name = \"$name\"\n" + @test readchomp(`git -C $co rev-parse --abbrev-ref HEAD`) == p["default_branch"] + end + end + end + + @testset "simulate" begin + env = Dict("deps" => Dict("Example" => EXAMPLE), "compat" => Dict("Example" => "=0.5.5"), + "pins" => Dict("Example" => "0.5.5")) + base = simulate("x", env, Dict(), Dict()) + @test base["ok"] && base["versions"]["Example"] == "0.5.5" + # the pin is checked: =0.5.4 admitted, but the audited version says 0.5.5 + env4 = Dict("deps" => env["deps"], "compat" => Dict("Example" => "=0.5.4"), "pins" => env["pins"]) + r = simulate("x", env4, Dict(), Dict()) + @test !r["ok"] && occursin("pinned versions not selected: Example 0.5.4 (audited 0.5.5)", r["error"]) + # a narrowing that moves a transitive dependency departs from the baseline + free = Dict("deps" => Dict("Example" => EXAMPLE, "OrderedCollections" => ORDERED), "compat" => Dict(), "pins" => Dict()) + b = simulate("x", free, Dict(), Dict()) + @test b["versions"]["OrderedCollections"] == "2.0.3" || VersionNumber(b["versions"]["OrderedCollections"]) ≥ v"2" + r = simulate("x", free, Dict("OrderedCollections" => "1"), Dict("OrderedCollections" => ORDERED)) + @test startswith(departure(r, b["versions"]), "manifest differs: OrderedCollections") + @test departure(simulate("x", free, Dict("OrderedCollections" => "2"), Dict("OrderedCollections" => ORDERED)), b["versions"]) === nothing + end + + @testset "scripts end to end" begin + mktempdir() do dir + root = example_collected(joinpath(dir, "audit")) + out = run_script("envs.jl", root) + @test occursin("3 environments: Example/docs, Example/test, joint", out) + j = join(split(string(VERSION), ".")[1:2], ".") + base = joinpath(root, "baseline-$j.toml") + run_script("resolve.jl", root, base) + b = TOML.parsefile(base) + @test all(r -> r["ok"], values(b["env"])) + @test VersionNumber(b["env"]["Example/docs"]["versions"]["OrderedCollections"]) ≥ v"2" + + classified = joinpath(root, "classified.toml") + out = run_script("classify.jl", root, classified, base) + @test occursin("prune → 2", out) + c = TOML.parsefile(classified) + p = only(c["prune"]) + @test entry_key(p) == ("Example", "docs/Project.toml", "OrderedCollections") && p["after"] == "2" + + # validation accepts it + empty_rejected = joinpath(root, "rejected.toml") + write(empty_rejected, "") + validated = joinpath(root, "validated.toml") + run_script("validate.jl", root, classified, base, validated, empty_rejected, "--blockers") + @test isempty(TOML.parsefile(validated)["rejected"]) + + # resolve.jl with the prunes: same manifests + pruned = joinpath(root, "pruned.toml") + run_script("resolve.jl", root, pruned, classified, base) + @test TOML.parsefile(pruned)["env"] == b["env"] + + # a prune that moves the manifest is rejected + c["prune"][1]["after"] = "1" + bad = joinpath(root, "classified-bad.toml") + open(io -> TOML.print(io, c), bad, "w") + run_script("validate.jl", root, bad, base, validated, empty_rejected) + r = only(TOML.parsefile(validated)["rejected"]) + @test r["where"] == "Example/docs@$j" + @test occursin("OrderedCollections", r["reason"]) + + # edit a checkout, then report + open(joinpath(root, "audit.toml"), "w") do io + TOML.print(io, Dict("date" => "2026-10-15 00:00 UTC", "julia" => [j], "prune" => TOML.parsefile(classified)["prune"], + "rejected" => [], "blocked" => [], "other" => [], + "packages" => TOML.parsefile(joinpath(root, "index.toml"))["packages"])) + end + checkouts = joinpath(dir, "checkouts") + cp(joinpath(root, "default"), checkouts) + out = run_script("edit.jl", root, checkouts) + @test occursin(r"edited\s+1", out) + @test occursin("OrderedCollections = \"2\"", read(joinpath(checkouts, "Example", "docs", "Project.toml"), String)) + @test read(joinpath(checkouts, "Example", "Project.toml"), String) == + read(joinpath(root, "default", "Example", "Project.toml"), String) + # a second run finds it done + out = run_script("edit.jl", root, checkouts) + @test occursin(r"already\s+1", out) + run_script("report.jl", root) + report = read(joinpath(root, "report.md"), String) + @test startswith(report, MARKER) + @test occursin("| merged, awaiting release | 1 |", report) + end + end + + @testset "a blocked entry is probed" begin + mktempdir() do dir + # newest lane 3 does not exist: never selected, the probe cannot resolve it + root = example_collected(joinpath(dir, "audit"); docs_compat="1, 2, 3") + run_script("envs.jl", root) + j = join(split(string(VERSION), ".")[1:2], ".") + base = joinpath(root, "baseline-$j.toml") + run_script("resolve.jl", root, base) + classified = joinpath(root, "classified.toml") + run_script("classify.jl", root, classified, base) + c = TOML.parsefile(classified) + b = only(filter(r -> r["status"] == "blocked", c["other"])) + @test b["newest"] == "3" && b["prunable"] == ["1"] + # its obsolete lane is still a prune + @test only(c["prune"])["after"] == "2, 3" + rejected = joinpath(root, "rejected.toml"); write(rejected, "") + validated = joinpath(root, "validated.toml") + run_script("validate.jl", root, classified, base, validated, rejected, "--blockers") + probe = only(TOML.parsefile(validated)["blocked"]) + @test probe["where"] == "Example/docs@$j" + @test occursin("OrderedCollections", probe["detail"]) + end + end +end diff --git a/scripts/compat-prune/test/test_publish.jl b/scripts/compat-prune/test/test_publish.jl new file mode 100644 index 0000000..f1eceef --- /dev/null +++ b/scripts/compat-prune/test/test_publish.jl @@ -0,0 +1,228 @@ +# publish.jl: idempotent PRs and tracking issue, against a stateful fake GitHub and real git +# repositories (a local bare repository plays `origin` for each package). + +include(joinpath(SCRIPTS, "publish.jl")) + +""" +A fake GitHub keeping issues and PRs. `origins` maps a repo ("owner/name") to its bare +repository, where `pr close --delete-branch` deletes the branch. +""" +mutable struct FakeGitHub + origins::Dict{String,String} + issues::Vector{Dict{String,Any}} + prs::Vector{Dict{String,Any}} + calls::Vector{Vector{String}} +end +FakeGitHub(origins) = FakeGitHub(origins, Dict{String,Any}[], Dict{String,Any}[], Vector{String}[]) + +opt(args, name) = args[findfirst(==(name), args) + 1] + +function (f::FakeGitHub)(args) + push!(f.calls, args) + cmd = args[1:2] + if cmd == ["api", "--paginate"] + repo = match(r"^repos/([^/]+/[^/]+)/issues", args[3])[1] + hits = [i for i in f.issues if i["repo"] == repo && occursin(MARKER, i["body"])] + return join(["$(i["number"]) $(i["state"])" for i in hits], "\n") * (isempty(hits) ? "" : "\n") + elseif cmd == ["issue", "create"] + repo = opt(args, "--repo") + n = length(f.issues) + 100 + push!(f.issues, Dict("repo" => repo, "number" => n, "state" => "open", "title" => opt(args, "--title"), + "body" => opt(args, "--body"), "comments" => String[])) + return "https://github.com/$repo/issues/$n\n" + elseif cmd[1] == "issue" + i = only(filter(i -> string(i["number"]) == args[3] && i["repo"] == opt(args, "--repo"), f.issues)) + cmd[2] == "edit" && (i["body"] = opt(args, "--body")) + cmd[2] == "close" && (i["state"] = "closed"; push!(i["comments"], opt(args, "--comment"))) + cmd[2] == "reopen" && (i["state"] = "open") + return "" + elseif cmd == ["pr", "list"] + repo, head = opt(args, "--repo"), opt(args, "--head") + hits = [p for p in f.prs if p["repo"] == repo && p["head"] == head && p["state"] == "open"] + return join(["$(p["number"]) $(p["url"])" for p in hits], "\n") + elseif cmd == ["pr", "create"] + repo = opt(args, "--repo") + n = length(f.prs) + 1 + url = "https://github.com/$repo/pull/$n" + push!(f.prs, Dict("repo" => repo, "number" => n, "url" => url, "state" => "open", "head" => opt(args, "--head"), + "base" => opt(args, "--base"), "title" => opt(args, "--title"), "body" => opt(args, "--body"))) + return url * "\n" + elseif cmd[1] == "pr" + p = only(filter(p -> string(p["number"]) == args[3] && p["repo"] == opt(args, "--repo"), f.prs)) + cmd[2] == "edit" && (p["body"] = opt(args, "--body")) + if cmd[2] == "close" + p["state"] = "closed" + "--delete-branch" in args && run(`git -C $(f.origins[p["repo"]]) branch --quiet -D $(p["head"])`) + end + return "" + end + error("unexpected gh call: $args") +end + +const GITC = `git -c user.name=test -c user.email=test@example.org -c init.defaultBranch=main` + +"A bare origin for each fixture package, holding its default-branch files; returns repo => path." +function make_origins(dir) + index = TOML.parsefile(joinpath(COLLECTED, "index.toml"))["packages"] + origins = Dict{String,String}() + for (name, p) in index + bare = joinpath(dir, "origins", name * ".git") + run(`$GITC init --quiet --bare -b $(p["default_branch"]) $bare`) + work = joinpath(dir, "seed", name) + run(`$GITC clone --quiet $bare $work`) + src = joinpath(COLLECTED, "default", name) + foreach(f -> cp(joinpath(src, f), joinpath(work, f)), readdir(src)) + run(`$GITC -C $work checkout --quiet -B $(p["default_branch"])`) + run(`$GITC -C $work add --all`) + run(`$GITC -C $work commit --quiet -m init`) + run(`$GITC -C $work push --quiet origin $(p["default_branch"])`) + origins[p["repo"]] = bare + end + return origins +end + +"Fresh clones of every origin's default branch, then edit.jl's work: writes audit.toml, edits.toml." +function prepare_run(dir, origins, prunes; tag) + index = TOML.parsefile(joinpath(COLLECTED, "index.toml"))["packages"] + root = joinpath(dir, "run-$tag") + checkouts = joinpath(root, "checkouts") + for (name, p) in index + run(`$GITC clone --quiet --branch $(p["default_branch"]) $(origins[p["repo"]]) $(joinpath(checkouts, name))`) + end + audit = Dict{String,Any}("date" => "2026-11-01 04:00 UTC", "julia" => ["1.10", "1.12"], "packages" => index, + "prune" => prunes, "rejected" => [], "blocked" => [], "other" => []) + open(io -> TOML.print(io, audit), joinpath(root, "audit.toml"), "w") + records, texts = plan_edits(audit, read_checkout(checkouts)) + for ((owner, file), text) in texts + write(joinpath(checkouts, owner, file), text) + end + open(io -> TOML.print(io, Dict("edit" => records)), joinpath(root, "edits.toml"), "w") + return root, checkouts +end + +"Commit `text` as `file` on the default branch of `origin` (a maintainer merging the PR)." +function merge_on_origin(dir, origin, branch, file, text) + work = joinpath(dir, "merge-" * string(hash((origin, file, text)); base=16)) + run(`$GITC clone --quiet --branch $branch $origin $work`) + write(joinpath(work, file), text) + run(`$GITC -C $work commit --quiet -am merged`) + run(`$GITC -C $work push --quiet origin $branch`) +end + +branch_exists(origin) = success(`git -C $origin rev-parse --verify --quiet refs/heads/$BRANCH`) + +@testset "publish" begin + @testset "helpers" begin + evidence = Dict{String,Any}("0.6" => ["joint@1.10=0.6.4"], "0.7" => ["joint@1.12=0.7.2"]) + edits = [merge(prune("Alpha", "Project.toml", "Dep1", "0.5, 0.6, 0.7", "0.6, 0.7"), Dict("status" => "edited", "evidence" => evidence)), + merge(prune("Alpha", "docs/Project.toml", "Doc", "0.9, 1", "1"), Dict("status" => "edited")), + merge(prune("Beta", "Project.toml", "Dep1", "0.6, 0.7", "0.7"), Dict("status" => "already"))] + @test commit_message("Alpha", edits) == """ + chore(compat): drop [compat] specifiers no supported Julia version selects + + Project.toml: Dep1 = "0.5, 0.6, 0.7" → "0.6, 0.7" + docs/Project.toml: Doc = "0.9, 1" → "1\"""" + body = render_pr_body("Alpha", edits; issue_url="https://github.com/example-org/Root.jl/issues/100", + run_url="https://github.com/example-org/Root.jl/actions/runs/1") + @test golden("pr-body.md", body) + @test startswith(body, MARKER) + @test !occursin("Beta", body) + end + + mktempdir() do dir + origins = make_origins(dir) + alpha, beta = origins["example-org/Alpha.jl"], origins["example-org/Beta.jl"] + fake = FakeGitHub(origins) + prunes = [prune("Alpha", "Project.toml", "Dep1", "0.5, 0.6, 0.7", "0.6, 0.7"), + prune("Alpha", "docs/Project.toml", "Doc", "0.9, 1", "1"), + prune("Beta", "Project.toml", "Dep1", "0.6, 0.7", "0.7")] # Beta's branch already has it + + @testset "first run: issue, branch, PR" begin + root, checkouts = prepare_run(dir, origins, prunes; tag="1") + done = with_gh(() -> publish(root, checkouts, "example-org/Root.jl"; run_url="https://run/1"), fake) + @test length(fake.issues) == 1 + issue = only(fake.issues) + @test issue["title"] == ISSUE_TITLE && issue["state"] == "open" + @test done["issue"] == "https://github.com/example-org/Root.jl/issues/100" + pr = only(fake.prs) + @test pr["repo"] == "example-org/Alpha.jl" && pr["head"] == BRANCH && pr["base"] == "main" + @test done["prs"] == Dict("Alpha" => pr["url"]) + @test done["pushed"] == ["Alpha"] + # the PR links the issue, the issue links the PR + @test occursin(done["issue"], pr["body"]) + @test occursin("### Alpha — $(pr["url"])", issue["body"]) + # the branch holds the edits on top of the default branch, nothing else + @test branch_exists(alpha) && !branch_exists(beta) + diff = read(`git -C $alpha diff main $BRANCH`, String) + @test occursin("-Dep1 = \"0.5, 0.6, 0.7\" # lanes kept by CompatHelper", diff) + @test occursin("+Dep1 = \"0.6, 0.7\" # lanes kept by CompatHelper", diff) + @test occursin("+Doc = \"1\"", diff) + @test count(l -> startswith(l, "+") && !startswith(l, "+++"), split(diff, '\n')) == 2 + @test strip(read(`git -C $alpha log -1 --format=%an $BRANCH`, String)) == BOT[1] + end + + @testset "second run: nothing changes" begin + before = read(`git -C $alpha rev-parse $BRANCH`, String) + n_calls = length(fake.calls) + root, checkouts = prepare_run(dir, origins, prunes; tag="2") + done = with_gh(() -> publish(root, checkouts, "example-org/Root.jl"; run_url="https://run/2"), fake) + @test length(fake.issues) == 1 && length(fake.prs) == 1 + @test isempty(done["pushed"]) + @test read(`git -C $alpha rev-parse $BRANCH`, String) == before + calls = fake.calls[n_calls+1:end] + @test !any(c -> c[1:2] in (["issue", "create"], ["pr", "create"]), calls) + @test any(c -> c[1:2] == ["pr", "edit"], calls) + # bodies refreshed with the new run + @test occursin("https://run/2", only(fake.issues)["body"]) + end + + @testset "after the merge: PR closed, branch deleted, issue closed" begin + # the maintainer merges: the default branch now carries the pruned values + alpha_now = read(`git -C $alpha show main:Project.toml`, String) + merge_on_origin(dir, alpha, "main", "Project.toml", replace(alpha_now, "Dep1 = \"0.5, 0.6, 0.7\"" => "Dep1 = \"0.6, 0.7\"")) + docs_now = read(`git -C $alpha show main:docs/Project.toml`, String) + merge_on_origin(dir, alpha, "main", "docs/Project.toml", replace(docs_now, "Doc = \"0.9, 1\"" => "Doc = \"1\"")) + root, checkouts = prepare_run(dir, origins, prunes; tag="3") + @test all(e -> e["status"] == "already", TOML.parsefile(joinpath(root, "edits.toml"))["edit"]) + done = with_gh(() -> publish(root, checkouts, "example-org/Root.jl"), fake) + @test done["closed"] == ["Alpha"] + @test only(fake.prs)["state"] == "closed" + @test !branch_exists(alpha) + issue = only(fake.issues) + @test issue["state"] == "closed" && issue["comments"] == ["Nothing left to prune."] + @test occursin("| merged, awaiting release | 3 |", issue["body"]) + end + + @testset "nothing to prune and no issue: nothing is created" begin + f = FakeGitHub(origins) + root, checkouts = prepare_run(dir, origins, prunes; tag="3b") + done = with_gh(() -> publish(root, checkouts, "example-org/Other.jl"), f) + @test isempty(f.issues) && isempty(f.prs) && done["issue"] == "" + end + + @testset "a new prune: the same issue is reopened, a new PR opened" begin + again = [prune("Beta", "Project.toml", "Tool", "1, 2", "2")] + beta_now = read(`git -C $beta show dev:Project.toml`, String) + merge_on_origin(dir, beta, "dev", "Project.toml", replace(beta_now, "Tool = \"2\"" => "Tool = \"1, 2\"")) + root, checkouts = prepare_run(dir, origins, again; tag="4") + done = with_gh(() -> publish(root, checkouts, "example-org/Root.jl"), fake) + @test length(fake.issues) == 1 && only(fake.issues)["state"] == "open" + @test length(fake.prs) == 2 + pr = fake.prs[2] + @test pr["repo"] == "example-org/Beta.jl" && pr["base"] == "dev" && pr["state"] == "open" + @test done["prs"] == Dict("Beta" => pr["url"]) + @test branch_exists(beta) + end + + @testset "find_issue ignores PRs and issues without the marker" begin + f = FakeGitHub(origins) + push!(f.issues, Dict("repo" => "a/b", "number" => 7, "state" => "open", "body" => "no marker", "comments" => String[])) + @test with_gh(() -> find_issue("a/b"), f) === nothing + push!(f.issues, Dict("repo" => "a/b", "number" => 8, "state" => "closed", "body" => MARKER * "\nold", "comments" => String[])) + @test with_gh(() -> find_issue("a/b"), f) == ("8", "closed") + # the jq filter excludes pull requests and empty bodies + jq = f.calls[end][end] + @test occursin("select(.pull_request == null)", jq) && occursin("(.body // \"\")", jq) + end + end +end diff --git a/scripts/compat-prune/test/test_regression.jl b/scripts/compat-prune/test/test_regression.jl new file mode 100644 index 0000000..6547026 --- /dev/null +++ b/scripts/compat-prune/test/test_regression.jl @@ -0,0 +1,66 @@ +# Non-regression on a real audit (2026-10-15): the collected files of the eight CT packages, +# their default branches, and the baselines of Julia 1.10, 1.12, 1.13 (trimmed to the audited +# packages and the dependencies named in a compat entry, which is all classification reads). +# Environments, classification and edits must stay identical; the golden files are the +# reviewed outputs of that audit. + +const SNAPSHOT = joinpath(FIXTURES, "snapshot-2026-10-15") + +"Lines `owner | file | dep | status | detail`, sorted, for a golden file." +summary_lines(records, detail) = + join(sort(["$(r["owner"]) | $(r["file"]) | $(r["dep"]) | $(r["status"]) | $(detail(r))" for r in records]), "\n") * "\n" + +@testset "regression 2026-10-15" begin + mktempdir() do dir + root = joinpath(dir, "snapshot") + cp(SNAPSHOT, root) + envs = build_envs(root) + open(io -> TOML.print(io, Dict("env" => envs)), joinpath(root, "envs.toml"), "w") + + @testset "environments" begin + @test length(envs) == 17 + text = join(sort(["$name: deps=$(join(sort(collect(keys(e["deps"]))), ",")) " * + "pins=$(join(sort(["$k=$v" for (k, v) in e["pins"]]), ","))" for (name, e) in envs]), "\n") * "\n" + @test golden("envs-2026-10-15.txt", text) + # the docs env of every package contains the package itself + @test all(e -> haskey(e["deps"], e["owner"]), [e for e in values(envs) if e["kind"] == "docs"]) + end + + results = load_results([joinpath(root, "baseline-$j.toml") for j in ("1.10", "1.12", "1.13")]) + prunes, others = classify_all(root, results) + + @testset "classification" begin + detail(r) = r["status"] == "prune" ? r["after"] : r["status"] == "blocked" ? "newest " * r["newest"] : "" + @test golden("classified-2026-10-15.txt", summary_lines(vcat(prunes, others), detail)) + got = Dict(entry_key(r) => r for r in vcat(prunes, others)) + @test length(prunes) == 42 + # CTSolvers keeps CUDSS 0.7: OptimalControl's tests and docs select it + @test got[("CTSolvers", "Project.toml", "CUDSS")]["after"] == "0.7, 0.8" + @test got[("CTSolvers", "docs/Project.toml", "CUDSS")]["after"] == "0.8" + # JET 0.9 is what Julia 1.10 selects + @test got[("CTBase", "Project.toml", "JET")]["after"] == "0.9, 0.12" + # CTParser's own CTBase 0.29 lane is used by its test env (CTModels 0.18) + @test got[("CTParser", "Project.toml", "CTBase")]["status"] == "keep" + @test got[("CTParser", "test/Project.toml", "CTBase")]["status"] == "blocked" + @test got[("CTBase", "Project.toml", "DocStringExtensions")]["status"] == "blocked" + end + + @testset "edits against the default branches" begin + records, texts = plan_edits(Dict("prune" => prunes), read_checkout(joinpath(root, "default"))) + @test golden("edits-2026-10-15.txt", summary_lines(records, r -> r["detail"])) + @test count(r -> r["status"] == "edited", records) == 35 + drift = [r for r in records if r["status"] == "drift"] + # CTParser's default branch is behind its registered version (CTParser#326) + @test length(drift) == 7 && all(r -> r["owner"] == "CTParser", drift) + # every edited file differs from its default branch by compat lines only + for ((owner, file), text) in texts + original = read(joinpath(root, "default", owner, file), String) + a, b = TOML.parse(original), TOML.parse(text) + delete!(a, "compat"); delete!(b, "compat") + @test a == b + changed = [l for l in split(text, '\n') if !(l in split(original, '\n'))] + @test all(l -> occursin(r"^\w+ = \"[^\"]+\"$", l), changed) + end + end + end +end diff --git a/scripts/compat-prune/test/test_report.jl b/scripts/compat-prune/test/test_report.jl new file mode 100644 index 0000000..0b439ba --- /dev/null +++ b/scripts/compat-prune/test/test_report.jl @@ -0,0 +1,121 @@ +# report.jl: Markdown body of the tracking issue (golden file + properties). + +"An audit and its edits covering every section of the report." +function report_fixture() + index = TOML.parsefile(joinpath(COLLECTED, "index.toml"))["packages"] + ev(pairs...) = Dict{String,Any}(spec => list for (spec, list) in pairs) + edited = [ + merge(prune("Alpha", "Project.toml", "Dep1", "0.5, 0.6, 0.7", "0.6, 0.7"), Dict( + "evidence" => ev("0.6" => ["joint@1.10=0.6.4"], "0.7" => ["joint@1.12=0.7.2", "Alpha/test@1.12=0.7.2"]), + "status" => "edited", "detail" => "")), + merge(prune("Alpha", "test/Project.toml", "Tool", "1, 2", "2"), Dict( + "evidence" => ev("2" => ["Alpha/test@1.10=2.0.0", "Alpha/test@1.12=2.0.0"]), + "status" => "edited", "detail" => "")), + merge(prune("Beta", "Project.toml", "Dep1", "0.6, 0.7", "0.7"), Dict( + "evidence" => ev("0.7" => ["Beta/test@1.12=0.7.2"]), "status" => "edited", "detail" => "")), + ] + already = [merge(prune("Beta", "Project.toml", "Tool", "1, 2", "2"), Dict("status" => "already", "detail" => "already `2`"))] + drift = [merge(prune("Alpha", "docs/Project.toml", "Doc", "0.8, 0.9", "0.9"), + Dict("status" => "drift", "detail" => "`0.9, 1` on the default branch, `0.8, 0.9` audited"))] + audit = Dict{String,Any}( + "date" => "2026-10-15 19:48 UTC", + "julia" => ["1.10", "1.12", "1.13"], + "packages" => index, + "prune" => vcat(edited, already, drift), + "rejected" => [Dict("owner" => "Beta", "file" => "Project.toml", "dep" => "Weak", "where" => "Beta/test@1.10", + "reason" => "manifest differs: A 1.0.0 → 1.1.0 | odd\nsecond line")], + "blocked" => [ + Dict("owner" => "Alpha", "file" => "docs/Project.toml", "dep" => "Doc", "newest" => "1", + "where" => "Alpha/docs@1.13", "blockers" => ["Documenter"], "detail" => ""), + Dict("owner" => "Alpha", "file" => "docs/Project.toml", "dep" => "Doc", "newest" => "1", + "where" => "joint@1.13", "blockers" => ["Beta", "Documenter"], "detail" => ""), + Dict("owner" => "Beta", "file" => "Project.toml", "dep" => "Weak", "newest" => "0.2", + "where" => "Beta/test@1.13", "blockers" => String[], "detail" => "resolves, but…"), + ], + "other" => [Dict("owner" => "Alpha", "file" => "Project.toml", "dep" => "Dep2", "before" => "1, 2", + "removed" => ["1"], "status" => "withheld", + "reason" => "baseline failed in joint@1.10: lanes used there are unknown"), + Dict("owner" => "Beta", "file" => "Project.toml", "dep" => "Dep3", "before" => "0.9, 1", "status" => "keep", + "evidence" => ev("0.9" => ["joint@1.10=0.9.4"], "1" => ["joint@1.12=1.2.0"])), + Dict("owner" => "Alpha", "file" => "test/Project.toml", "dep" => "Gpu", "before" => "5, 6", "status" => "idle", + "evidence" => ev())], + "record" => Dict( + "registries" => [Dict("name" => "General", "state" => "tree aff17d8a1e3ecb7cf9a37f2f40543c57577276fd"), + Dict("name" => "ct-registry", "state" => "commit 0123456789abcdef0123456789abcdef01234567")], + "coverage" => [Dict("julia" => "1.10", "resolved" => 3, "total" => 4, "failed" => ["joint"]), + Dict("julia" => "1.12", "resolved" => 4, "total" => 4, "failed" => String[])], + "entries" => Dict("examined" => 9, "prune" => 4, "rejected" => 1, "keep" => 1, "blocked" => 2, "idle" => 1, "withheld" => 1)), + ) + return audit, vcat(edited, already, drift) +end + +@testset "report" begin + audit, edits = report_fixture() + prs = Dict("Alpha" => "https://github.com/example-org/Alpha.jl/pull/12") + body = render_report(audit, edits; prs, run_url="https://github.com/example-org/Root.jl/actions/runs/1") + + @testset "golden" begin + @test golden("report.md", body) + end + + @testset "marker and sections" begin + @test startswith(body, MARKER * "\n") + for h in ("## Audit", "## Summary", "## To prune", "### Alpha — https://github.com/example-org/Alpha.jl/pull/12", + "### Beta", "## Merged, awaiting release", "## Drift", "## Rejected", "## Withheld", + "## Blocked", "## Audited versions") + @test occursin("\n" * h * "\n", body) + end + # the reason of a rejection stays on one table row + @test occursin("A 1.0.0 → 1.1.0 \\| odd second line", body) + # blockers aggregated over environments; none found → resolver preference + @test occursin("| Alpha `docs/Project.toml` `Doc` | `1` | Beta, Documenter |", body) + @test occursin("| Beta `Project.toml` `Weak` | `0.2` | resolver preference |", body) + @test occursin("| to prune (open PRs) | 3 |", body) + @test occursin("| blocked (information) | 2 |", body) + end + + @testset "audit record" begin + @test occursin("| 1.10 | 3 / 4 (failed: joint) |", body) + @test occursin("| 1.12 | 4 / 4 |", body) + @test occursin("9 multi-specifier `[compat]` entries examined: 4 with lanes to prune, 1 rejected, 2 blocked, " * + "1 kept (every lane in use), 1 idle (resolved nowhere they apply), 1 withheld.", body) + @test occursin("Registries: General (tree aff17d8a1e3e), ct-registry (commit 0123456789ab).", body) + @test occursin("Entries left as they are (2)", body) + @test occursin("| Beta `Project.toml` `Dep3` | `0.9, 1` | keep | every lane in use: 1.2.0 on 1.12; 0.9.4 on 1.10 |", body) + @test occursin("| Alpha `test/Project.toml` `Gpu` | `5, 6` | idle | not resolved where the entry applies |", body) + # the section comes before the summary + @test findfirst("## Audit\n", body).start < findfirst("## Summary", body).start + @test short_state("tree aff17d8a1e3ecb7cf9a37f2f40543c57577276fd") == "tree aff17d8a1e3e" + @test short_state("unknown") == "unknown" + end + + @testset "deterministic" begin + shuffled = deepcopy(audit) + reverse!(shuffled["blocked"]) + @test render_report(shuffled, reverse(edits); prs, run_url="https://github.com/example-org/Root.jl/actions/runs/1") == body + end + + @testset "empty sections are left out" begin + quiet = Dict{String,Any}("date" => "2026-11-01 03:00 UTC", "julia" => ["1.10"], "packages" => audit["packages"], + "prune" => [], "rejected" => [], "blocked" => [], "other" => []) + # without a record (older audit.toml): no Audit section + @test !occursin("## Audit\n", render_report(quiet, [])) + quiet["record"] = Dict("registries" => [], "coverage" => [], "entries" => Dict( + "examined" => 0, "prune" => 0, "rejected" => 0, "keep" => 0, "blocked" => 0, "idle" => 0, "withheld" => 0)) + b = render_report(quiet, []) + @test occursin("## Audit\n", b) && !occursin("
", b) + @test occursin("| to prune (open PRs) | 0 |", b) + for h in ("## To prune", "## Merged", "## Drift", "## Rejected", "## Withheld", "## Blocked") + @test !occursin(h, b) + end + @test !occursin("[run]", b) + @test occursin("## Audited versions", b) + end + + @testset "helpers" begin + @test table(["a", "b"], [["1", "2"]]) == "| a | b |\n| --- | --- |\n| 1 | 2 |" + @test selected(Dict("0.9" => ["CTBase/test@1.10=0.9.18"], "0.12" => ["CTBase/test@1.12=0.12.3", "joint@1.13=0.12.3"])) == + "0.12.3 on 1.12, 1.13; 0.9.18 on 1.10" + @test selected(Dict{String,Any}()) == "" + end +end diff --git a/scripts/compat-prune/test/test_validate.jl b/scripts/compat-prune/test/test_validate.jl new file mode 100644 index 0000000..819fd24 --- /dev/null +++ b/scripts/compat-prune/test/test_validate.jl @@ -0,0 +1,107 @@ +# validate.jl: departures, rejection of failing prunes (fake simulator), blockers. + +""" +A fake simulator: the baseline `versions` of each environment, departed from (`Tool` moves to +9.9.9) when `breaks(name, narrowings)` holds. Records every call. +""" +function fake_sim(baseline; breaks=(name, n) -> false, error_for=Dict()) + calls = [] + sim = function (name, env, n, uuids) + push!(calls, (name, copy(n))) + haskey(error_for, name) && return failed(error_for[name]) + versions = copy(baseline[name]["versions"]) + breaks(name, n) && (versions["Tool"] = "9.9.9") + return Dict("ok" => true, "versions" => versions) + end + return sim, calls +end + +@testset "validate" begin + @testset "departure" begin + base = Dict("A" => "1.0.0", "B" => "2.0.0") + @test departure(ok("A" => "1.0.0", "B" => "2.0.0"), base) === nothing + @test departure(ok("A" => "1.0.0", "B" => "2.1.0"), base) == "manifest differs: B 2.0.0 → 2.1.0" + @test departure(ok("A" => "1.0.0"), base) == "manifest differs: B 2.0.0 → —" + @test departure(ok("A" => "1.0.0", "B" => "2.0.0", "C" => "3.0.0"), base) == "manifest differs: C — → 3.0.0" + @test departure(failed("empty compat intersection for CUDSS"), base) == "empty compat intersection for CUDSS" + many = Dict("P$i" => "1.0.$i" for i in 1:9) + @test endswith(departure(ok(), many), ", …") + end + + @testset "blockers" begin + @test blockers(CONFLICT_ON_DEP, "DocStringExtensions") == ["SciMLBase"] + @test blockers(CONFLICT_ON_OTHER, "DocStringExtensions") == ["DocumenterVitepress"] + @test isempty(blockers("empty compat intersection for CUDSS", "CUDSS")) + end + + with_collected() do root + envs, audited, uuids = load_collected(root) + baseline = Dict(name => r for (k, r) in fixture_results() for name in (first(split(k, "@")),) if endswith(k, "@1.12")) + dep1 = prune("Alpha", "Project.toml", "Dep1", "0.5, 0.6, 0.7", "0.6, 0.7") + tool = prune("Alpha", "test/Project.toml", "Tool", "1, 2", "2") + + @testset "nothing departs" begin + sim, calls = fake_sim(baseline) + @test isempty(validate_prunes(envs, audited, uuids, [dep1, tool], baseline, "1.12"; sim)) + # one combined simulation per environment where a prune applies: Dep1 wherever + # Alpha is (not Beta/test), Tool in Alpha/test only + @test calls == [("Alpha/docs", Dict("Dep1" => "0.6, 0.7")), + ("Alpha/test", Dict("Dep1" => "0.6, 0.7", "Tool" => "2")), + ("joint", Dict("Dep1" => "0.6, 0.7"))] + end + + @testset "a culprit fails alone" begin + sim, calls = fake_sim(baseline; breaks=(name, n) -> get(n, "Tool", "") == "2") + rejected = validate_prunes(envs, audited, uuids, [dep1, tool], baseline, "1.12"; sim) + @test length(rejected) == 1 + r = only(rejected) + @test entry_key(r) == entry_key(tool) + @test r["where"] == "Alpha/test@1.12" + @test r["reason"] == "manifest differs: Tool 2.0.0 → 9.9.9" + end + + @testset "prunes that fail only together are all rejected" begin + sim, _ = fake_sim(baseline; breaks=(name, n) -> haskey(n, "Tool") && haskey(n, "Dep1")) + rejected = validate_prunes(envs, audited, uuids, [dep1, tool], baseline, "1.12"; sim) + @test sort(entry_key.(rejected)) == sort(entry_key.([dep1, tool])) + @test all(r -> startswith(r["reason"], "fails only combined with other prunes"), rejected) + end + + @testset "a rejected prune is not simulated again in later environments" begin + # Dep1 breaks everywhere: rejected in the first environment (sorted: Alpha/docs), + # then absent from the simulations of the next ones. + sim, calls = fake_sim(baseline; breaks=(name, n) -> haskey(n, "Dep1")) + rejected = validate_prunes(envs, audited, uuids, [dep1], baseline, "1.12"; sim) + @test only(rejected)["where"] == "Alpha/docs@1.12" + # combined + alone in Alpha/docs, then nothing left to simulate + @test length(calls) == 2 + end + + @testset "environments that failed at baseline are skipped" begin + b = copy(baseline) + b["joint"] = failed() + sim, calls = fake_sim(baseline) + validate_prunes(envs, audited, uuids, [dep1], b, "1.12"; sim) + @test !("joint" in first.(calls)) + end + + @testset "probe_blockers" begin + blocked = [Dict("owner" => "Alpha", "file" => "docs/Project.toml", "dep" => "Doc", + "newest" => "1", "applies_in" => ["Alpha/docs", "Alpha/test"])] + msg = replace(CONFLICT_ON_OTHER, "DocStringExtensions" => "Doc") + sim, calls = fake_sim(baseline; error_for=Dict("Alpha/docs" => msg)) + found = probe_blockers(envs, uuids, blocked, baseline, "1.12"; sim) + # Alpha/test does not have Doc: not probed + @test calls == [("Alpha/docs", Dict("Doc" => "1"))] + f = only(found) + @test f["where"] == "Alpha/docs@1.12" + @test f["blockers"] == ["DocumenterVitepress"] + @test f["newest"] == "1" + # restricted to the newest, it resolves: the resolver merely prefers other versions + sim, _ = fake_sim(baseline) + f = only(probe_blockers(envs, uuids, blocked, baseline, "1.12"; sim)) + @test isempty(f["blockers"]) + @test f["detail"] == "resolves, but the resolver prefers other versions" + end + end +end diff --git a/scripts/compat-prune/validate.jl b/scripts/compat-prune/validate.jl new file mode 100644 index 0000000..9bc678d --- /dev/null +++ b/scripts/compat-prune/validate.jl @@ -0,0 +1,119 @@ +# Validate the prunes and identify the blockers, on the running Julia. +# +# usage: julia validate.jl [--blockers] +# +# Prunes: all of them (minus those already rejected) are simulated together; an environment +# whose manifest differs from the baseline, or that no longer resolves, is a failure. The +# prunes that apply in a failing environment are then simulated one by one there: those that +# fail alone are rejected; if none does, the failure comes from their combination and all of +# them are rejected. `audit.jl` repeats until no Julia version rejects anything. +# +# Blockers (`--blockers`, given by `audit.jl` on the newest Julia version only, as they are +# information and cost one resolution per environment): for each blocked entry, each +# environment where it applies is resolved with the dependency restricted to the newest +# specifier; the resolver's explanation names the packages that cap it. + +include("common.jl") + +"Short description of how `r` departs from the baseline versions `base`." +function departure(r, base) + r["ok"] || return first(r["error"], 600) + d = sort(["$k $(get(base, k, "—")) → $(get(r["versions"], k, "—"))" + for k in union(keys(base), keys(r["versions"])) if get(base, k, "") != get(r["versions"], k, "")]) + isempty(d) ? nothing : "manifest differs: " * join(first(d, 6), ", ") * (length(d) > 6 ? ", …" : "") +end + +""" + blockers(msg, dep) -> Vector{String} + +Packages that cap `dep`, read from the resolver's error when `dep` is restricted to its newest +specifier. The resolver reports the first conflict it meets, not every one: when that conflict +is on another package, that package is the blocker (its compat excludes the newest `dep`); +when it is on `dep` itself, the blockers are the first-level `restricted by compatibility +requirements with …` lines of its log (deeper lines explain those blockers, they are not +blockers of `dep`). +""" +function blockers(msg, dep) + head = match(r"Unsatisfiable requirements detected for package (\S+) \[", msg) + head === nothing && return String[] + head[1] == dep || return [String(head[1])] + first_level = r"^ [├└]─restricted by compatibility requirements with (\S+) \["m + return sort(unique(String[m[1] for m in eachmatch(first_level, msg)])) +end + + +""" + validate_prunes(envs, audited, uuids, prunes, baseline, julia; sim=simulate) -> rejected + +Simulate `prunes` together in every environment of `envs` that resolved at `baseline`; in an +environment that departs from its baseline, simulate the prunes that apply there one by one +and reject those that fail alone, or all of them when none does. Returns the rejected +records (`owner`, `file`, `dep`, `where`, `reason`). +""" +function validate_prunes(envs, audited, uuids, prunes, baseline, julia; sim=simulate) + prunes = copy(prunes) + rejected = Dict{String,Any}[] + for name in sort(collect(keys(envs))) + base = baseline[name] + base["ok"] || continue + n = narrowings(prunes, name, base["versions"], audited) + isempty(n) && continue + why = departure(sim(name, envs[name], n, uuids), base["versions"]) + why === nothing && continue + println(julia, " ", rpad(name, 24), "fails together: ", why) + suspects = [p for p in prunes if !isempty(narrowings([p], name, base["versions"], audited))] + alone = Dict{Any,String}() + for p in suspects + w = departure(sim(name, envs[name], narrowings([p], name, base["versions"], audited), uuids), base["versions"]) + w === nothing || (alone[entry_key(p)] = w) + end + culprits = isempty(alone) ? suspects : [p for p in suspects if haskey(alone, entry_key(p))] + for p in culprits + reason = get(alone, entry_key(p), "fails only combined with other prunes: $why") + push!(rejected, Dict("owner" => p["owner"], "file" => p["file"], "dep" => p["dep"], + "where" => "$name@$julia", "reason" => reason)) + println(" rejected: $(p["owner"]) $(p["file"]) $(p["dep"]) → $(p["after"])") + end + gone = Set(entry_key.(culprits)) + filter!(p -> !(entry_key(p) in gone), prunes) + end + return rejected +end + +""" + probe_blockers(envs, uuids, blocked, baseline, julia; sim=simulate) -> probes + +For each blocked entry, resolve every environment where it applies with the dependency +restricted to its newest specifier, and read the blockers from the resolver's error. +""" +function probe_blockers(envs, uuids, blocked, baseline, julia; sim=simulate) + found = Dict{String,Any}[] + for b in blocked, name in b["applies_in"] + base = baseline[name] + base["ok"] && haskey(base["versions"], b["dep"]) || continue + r = sim(name, envs[name], Dict(b["dep"] => b["newest"]), uuids) + push!(found, Dict("owner" => b["owner"], "file" => b["file"], "dep" => b["dep"], + "newest" => b["newest"], "where" => "$name@$julia", + "blockers" => r["ok"] ? String[] : blockers(r["error"], b["dep"]), + "detail" => r["ok"] ? "resolves, but the resolver prefers other versions" : first(r["error"], 2000))) + println(julia, " blocked: $(b["owner"]) $(b["file"]) $(b["dep"]) $(b["newest"]) in $name, by ", + r["ok"] ? "nothing (resolver preference)" : join(found[end]["blockers"], ", ")) + end + return found +end + +if abspath(PROGRAM_FILE) == @__FILE__ + # usage: … [--blockers] + root, classified, basefile, outfile, rejectedfile = ARGS[1:5] + rejected_before = Set(entry_key.(get(TOML.parsefile(rejectedfile), "rejected", []))) + envs, audited, uuids = load_collected(root) + c = TOML.parsefile(classified) + prunes = [p for p in c["prune"] if !(entry_key(p) in rejected_before)] + baseline = TOML.parsefile(basefile)["env"] + julia = join(split(string(VERSION), ".")[1:2], ".") + rejected = validate_prunes(envs, audited, uuids, prunes, baseline, julia) + blocked = [b for b in get(c, "other", []) if b["status"] == "blocked"] + found = "--blockers" in ARGS ? probe_blockers(envs, uuids, blocked, baseline, julia) : Dict{String,Any}[] + open(io -> TOML.print(io, Dict("julia" => string(VERSION), "rejected" => rejected, "blocked" => found)), outfile, "w") + println(julia, " ", length(rejected), " prune(s) rejected, ", length(found), " blocker probe(s)") +end