Skip to content

chore: check for circular dependencies with cargo publish - #26582

Merged
alamb merged 2 commits into
apache:mainfrom
neilconway:neilc/ci-publish-dry-run
Oct 22, 2026
Merged

alamb merged 2 commits into
apache:mainfrom
neilconway:neilc/ci-publish-dry-run

Conversation

@neilconway

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Normal cargo commands reject circular dependencies between packages, but they allow cycles through dev-dependencies. This has bitten us in the past, which is why the dev/depcheck tool and its associated CI job were added to detect dev-dependency cycles.

Running dev/depcheck in CI is relatively expensive, in part because it depends on the cargo crate, which has a large dependency tree. Building and running depcheck takes ~130 seconds on a 4 vCPU container, and also requires an ubuntu-latest runner.

Cargo 1.90 added support for multiple packages in a single cargo publish, which does check for dev-dependency cycles. It follows that we can use a wrapper script over cargo publish --workspace --dry-run to replace depcheck. This is more efficient and can run on ubuntu-slim. Moreover, it avoids the need to maintain a separate tool and ensures that the CI check is consistent with how packages are actually published. (We should probably also switch to using multi-package cargo publish to make releases, but I'll defer that for now.)

One downside is that error reporting when a cycle is found is less helpful, but I don't think that is a major concern.

What changes are included in this PR?

  • Delete dev/depcheck
  • Change ci/scripts/check_circular_dependencies.sh to call cargo publish --dry-run --workspace
  • The "Circular Dependency Check" now runs on ubuntu-slim

What is the testing strategy for this PR?

CI / tooling only. Manual local testing:

  • Small test workspace with a dev-dependency cycle: cargo test passes, and cargo package / cargo publish --workspace --dry-run fail.
  • Same workspace without the cycle: packaging passes.
  • Same workspace with a dev-dependency that has a path but no version: packaging passes, because Cargo drops that dependency.
  • DataFusion main: the dry run passes in 15 seconds and skips the 11 publish = false crates.
  • DataFusion with a dev-dependency cycle added: cargo check --tests passes, and the dry run fails.
  • DataFusion with a normal-dependency cycle added: cargo check fails by itself.
  • DataFusion bumped to unpublished versions: the dry run passes.
  • Bumped versions plus the dev-dependency cycle: the dry run fails.

Are there any user-facing changes?

No.

The Circular Dependency Check built dev/depcheck, which links the cargo
crate (396 packages, including libgit2, curl, and sqlite) only to load
the workspace and look for a cycle in the dependencies between
DataFusion crates. The build and run took 130 seconds in a 4-vCPU
container, and the tool's cargo dependency and toolchain pin needed
their own updates (for example apache#24649 and apache#25935).

Such a cycle blocks publishing to crates.io. `cargo publish` resolves
all of a crate's dependencies, dev-dependencies included, so a crate can
be published only after every DataFusion crate it depends on, and no
crate in a cycle can go first (apache#9277). Cargo refuses to build a cycle of
normal dependencies, but it allows one that passes through a
dev-dependency, so that kind needs a check of its own.

Since Rust 1.90, `cargo publish --workspace` publishes all of a
workspace's crates in dependency order. check_circular_dependencies.sh
now runs it with `--dry-run --no-verify`, which packages every published
crate without building or uploading anything. Because it runs the
publish step itself, it also catches other problems that would block a
release, such as a dependency without a version.

On main the dry run takes 13 to 30 seconds locally, including from a
fresh checkout with an empty Cargo cache and no submodules, and it skips
the 11 crates with `publish = false`. With a temporary dev-dependency of
datafusion-common on datafusion-expr, `cargo check --tests` passes and
the dry run fails. It fails whether or not the workspace version is
already on crates.io, with different errors: "circular dependency
detected while publishing" when it is, and "failed to select a version",
as in apache#9277, when it is not. Neither error names the cycle itself.

The job keeps its name, since catching cycles is its main purpose and
renaming a required check would leave every open PR without it until
the PR runs CI again. It now runs on ubuntu-slim with setup-rustup,
without the amd64/rust container or submodules. Remove dev/depcheck, its
Dependabot entry, and its workspace exclusion.
@neilconway neilconway changed the title chore: check for circular dependencies with a publish dry run chore: check for circular dependencies with cargo publish Oct 22, 2026

@kumarUjjawal kumarUjjawal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @neilconway

LGTM!

@github-actions github-actions Bot added documentation Improvements or additions to documentation development-process Related to development process of DataFusion labels Oct 22, 2026
@kumarUjjawal

Copy link
Copy Markdown
Contributor

Merged main to PR branch to trigger CI.

@alamb
alamb added this pull request to the merge queue Oct 22, 2026
@alamb

alamb commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

CI looks good so merging

@AdamGS

AdamGS commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

awesome!

Merged via the queue into apache:main with commit f43f67b Oct 22, 2026
46 checks passed
lyne7-sc pushed a commit to lyne7-sc/datafusion that referenced this pull request Oct 23, 2026
## Which issue does this PR close?

- N/A

## Rationale for this change

The semver check previously compared the PR against the current state of
`apache/main`, and did so by doing a full `git` checkout, including the
entire repository history. This approach has two downsides:

1. It is expensive: cloning the entire repo history takes ~9 sec
locally, versus ~0.7 for a shallow checkout.
2. There's a race condition which results in comparing the PR against
the wrong base when assessing whether the PR breaks semantic versioning.

On (2): when a PR is opened or updated, GitHub creates a test merge
commit that merges the PR into main as of that moment (T0). Now suppose
that there is a delay between the CI jobs being queued and the semver
job actually running, so that when the semver job runs, `HEAD` is now at
T1. The previous version of this check would compare the PR with T1, not
T0. For example, if a commit between T0 and T1 added a new API, the
semver check would claim that the PR _removes_ that API.

Fix this by comparing the merge commit with its first parent instead,
which is T0 in the notation above.

## What changes are included in this PR?

See above.

## What is the testing strategy for this PR?

CI / dev tooling change only. Tested locally on six open PRs (apache#26565,
apache#26000, apache#26582, apache#25874, apache#26534, apache#26576): fetched each PR's merge ref at
depth 2 and ran both steps against `HEAD^1`. The crate lists and semver
results match CI, including two PRs that branched 66 and 154 commits
behind main.

## Are there any user-facing changes?

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

Labels

development-process Related to development process of DataFusion documentation Improvements or additions to documentation v56.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants