Repository: rust-lang/cargo Branch: master Commit: 6e62fbb83f78 Files: 2481 Total size: 12.7 MB Directory structure: gitextract_myxolkbh/ ├── .cargo/ │ └── config.toml ├── .git-blame-ignore-revs ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ ├── new_lint.yml │ │ └── tracking_issue.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── renovate.json5 │ └── workflows/ │ ├── audit.yml │ ├── contrib.yml │ ├── main.yml │ └── release.yml ├── .gitignore ├── .ignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── LICENSE-THIRD-PARTY ├── README.md ├── benches/ │ ├── README.md │ ├── benchsuite/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches/ │ │ │ ├── global_cache_tracker.rs │ │ │ ├── resolve.rs │ │ │ └── workspace_initialization.rs │ │ ├── global-cache-tracker/ │ │ │ ├── global-cache-sample │ │ │ └── random-sample │ │ └── src/ │ │ ├── bin/ │ │ │ └── capture-last-use.rs │ │ └── lib.rs │ ├── capture/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ └── workspaces/ │ ├── cargo.tgz │ ├── diem.tgz │ ├── empty.tgz │ ├── gecko-dev.tgz │ ├── rust-ws-inherit.tgz │ ├── rust.tgz │ ├── servo.tgz │ ├── substrate.tgz │ ├── tikv.tgz │ └── toml-rs.tgz ├── build.rs ├── ci/ │ ├── clean-test-output.sh │ ├── dump-environment.sh │ ├── fetch-smoke-test.sh │ ├── generate.py │ ├── validate-man.sh │ └── validate-version-bump.sh ├── clippy.toml ├── crates/ │ ├── build-rs/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── ident.rs │ │ ├── input.rs │ │ ├── lib.rs │ │ └── output.rs │ ├── build-rs-test-lib/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── lib.rs │ ├── cargo-platform/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── examples/ │ │ │ └── matches.rs │ │ ├── src/ │ │ │ ├── cfg.rs │ │ │ ├── error.rs │ │ │ └── lib.rs │ │ └── tests/ │ │ └── test_cfg.rs │ ├── cargo-test-macro/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── cargo-test-support/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── containers/ │ │ │ ├── apache/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── bar/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── httpd-cargo.conf │ │ │ └── sshd/ │ │ │ ├── Dockerfile │ │ │ └── bar/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── src/ │ │ ├── compare.rs │ │ ├── containers.rs │ │ ├── cross_compile.rs │ │ ├── git.rs │ │ ├── install.rs │ │ ├── lib.rs │ │ ├── paths.rs │ │ ├── publish.rs │ │ └── registry.rs │ ├── cargo-util/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── du.rs │ │ ├── lib.rs │ │ ├── paths.rs │ │ ├── process_builder.rs │ │ ├── process_error.rs │ │ ├── read2.rs │ │ ├── registry.rs │ │ └── sha256.rs │ ├── cargo-util-schemas/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── index.schema.json │ │ ├── lockfile.schema.json │ │ ├── manifest.schema.json │ │ └── src/ │ │ ├── core/ │ │ │ ├── mod.rs │ │ │ ├── package_id_spec.rs │ │ │ ├── partial_version.rs │ │ │ └── source_kind.rs │ │ ├── index.rs │ │ ├── lib.rs │ │ ├── lockfile.rs │ │ ├── manifest/ │ │ │ ├── mod.rs │ │ │ └── rust_version.rs │ │ ├── messages.rs │ │ ├── restricted_names.rs │ │ └── schema.rs │ ├── crates-io/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── lib.rs │ ├── home/ │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── env.rs │ │ ├── lib.rs │ │ └── windows.rs │ ├── mdman/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── doc/ │ │ │ ├── mdman.md │ │ │ └── out/ │ │ │ ├── mdman.1 │ │ │ ├── mdman.md │ │ │ └── mdman.txt │ │ ├── src/ │ │ │ ├── format/ │ │ │ │ ├── man.rs │ │ │ │ ├── md.rs │ │ │ │ ├── mod.rs │ │ │ │ └── text.rs │ │ │ ├── hbs.rs │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ └── util.rs │ │ └── tests/ │ │ ├── compare/ │ │ │ ├── expected/ │ │ │ │ ├── formatting.1 │ │ │ │ ├── formatting.md │ │ │ │ ├── formatting.txt │ │ │ │ ├── links.1 │ │ │ │ ├── links.md │ │ │ │ ├── links.txt │ │ │ │ ├── options.1 │ │ │ │ ├── options.md │ │ │ │ ├── options.txt │ │ │ │ ├── tables.1 │ │ │ │ ├── tables.md │ │ │ │ ├── tables.txt │ │ │ │ ├── vars.7 │ │ │ │ ├── vars.md │ │ │ │ └── vars.txt │ │ │ ├── formatting.md │ │ │ ├── includes/ │ │ │ │ ├── links-include.md │ │ │ │ └── options-common.md │ │ │ ├── links.md │ │ │ ├── options.md │ │ │ ├── tables.md │ │ │ └── vars.md │ │ ├── compare.rs │ │ ├── invalid/ │ │ │ ├── nested.md │ │ │ └── not-inside-options.md │ │ └── invalid.rs │ ├── resolver-tests/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── helpers.rs │ │ │ ├── lib.rs │ │ │ └── sat.rs │ │ └── tests/ │ │ ├── proptests.rs │ │ ├── pubgrub.rs │ │ ├── resolve.rs │ │ └── validated.rs │ ├── rustfix/ │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── examples/ │ │ │ └── fix-json.rs │ │ ├── proptest-regressions/ │ │ │ └── replace.txt │ │ ├── src/ │ │ │ ├── diagnostics.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ └── replace.rs │ │ └── tests/ │ │ ├── edge-cases/ │ │ │ ├── empty.json │ │ │ ├── empty.rs │ │ │ ├── indented_whitespace.json │ │ │ ├── no_main.json │ │ │ ├── no_main.rs │ │ │ ├── out_of_bounds.recorded.json │ │ │ └── utf8_idents.recorded.json │ │ ├── edge_cases.rs │ │ ├── everything/ │ │ │ ├── .gitignore │ │ │ ├── E0178.fixed.rs │ │ │ ├── E0178.json │ │ │ ├── E0178.rs │ │ │ ├── closure-immutable-outer-variable.fixed.rs │ │ │ ├── closure-immutable-outer-variable.json │ │ │ ├── closure-immutable-outer-variable.rs │ │ │ ├── dedup-suggestions.fixed.rs │ │ │ ├── dedup-suggestions.json │ │ │ ├── dedup-suggestions.rs │ │ │ ├── handle-insert-only.fixed.rs │ │ │ ├── handle-insert-only.json │ │ │ ├── handle-insert-only.rs │ │ │ ├── lt-generic-comp.fixed.rs │ │ │ ├── lt-generic-comp.json │ │ │ ├── lt-generic-comp.rs │ │ │ ├── multiple-solutions.fixed.rs │ │ │ ├── multiple-solutions.json │ │ │ ├── multiple-solutions.rs │ │ │ ├── replace-only-one-char.fixed.rs │ │ │ ├── replace-only-one-char.json │ │ │ ├── replace-only-one-char.rs │ │ │ ├── str-lit-type-mismatch.fixed.rs │ │ │ ├── str-lit-type-mismatch.json │ │ │ ├── str-lit-type-mismatch.rs │ │ │ ├── use-insert.fixed.rs │ │ │ ├── use-insert.json │ │ │ └── use-insert.rs │ │ └── parse_and_replace.rs │ ├── semver-check/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── xtask-build-man/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── xtask-bump-check/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── xtask.rs │ ├── xtask-lint-docs/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── xtask-spellcheck/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ └── xtask-stale-label/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── credential/ │ ├── README.md │ ├── cargo-credential/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── examples/ │ │ │ ├── file-provider.rs │ │ │ └── stdout-redirected.rs │ │ ├── src/ │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── secret.rs │ │ │ └── stdio.rs │ │ └── tests/ │ │ └── examples.rs │ ├── cargo-credential-1password/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── cargo-credential-libsecret/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── cargo-credential-macos-keychain/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ └── cargo-credential-wincred/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ └── lib.rs ├── deny.toml ├── publish.py ├── rustfmt.toml ├── src/ │ ├── bin/ │ │ └── cargo/ │ │ ├── cli.rs │ │ ├── commands/ │ │ │ ├── add.rs │ │ │ ├── bench.rs │ │ │ ├── build.rs │ │ │ ├── check.rs │ │ │ ├── clean.rs │ │ │ ├── config.rs │ │ │ ├── doc.rs │ │ │ ├── fetch.rs │ │ │ ├── fix.rs │ │ │ ├── generate_lockfile.rs │ │ │ ├── git_checkout.rs │ │ │ ├── help.rs │ │ │ ├── info.rs │ │ │ ├── init.rs │ │ │ ├── install.rs │ │ │ ├── locate_project.rs │ │ │ ├── login.rs │ │ │ ├── logout.rs │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ ├── new.rs │ │ │ ├── owner.rs │ │ │ ├── package.rs │ │ │ ├── pkgid.rs │ │ │ ├── publish.rs │ │ │ ├── read_manifest.rs │ │ │ ├── remove.rs │ │ │ ├── report.rs │ │ │ ├── run.rs │ │ │ ├── rustc.rs │ │ │ ├── rustdoc.rs │ │ │ ├── search.rs │ │ │ ├── test.rs │ │ │ ├── tree.rs │ │ │ ├── uninstall.rs │ │ │ ├── update.rs │ │ │ ├── vendor.rs │ │ │ ├── verify_project.rs │ │ │ ├── version.rs │ │ │ └── yank.rs │ │ └── main.rs │ ├── cargo/ │ │ ├── core/ │ │ │ ├── compiler/ │ │ │ │ ├── artifact.rs │ │ │ │ ├── build_config.rs │ │ │ │ ├── build_context/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── target_info.rs │ │ │ │ ├── build_runner/ │ │ │ │ │ ├── compilation_files.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── compilation.rs │ │ │ │ ├── compile_kind.rs │ │ │ │ ├── crate_type.rs │ │ │ │ ├── custom_build.rs │ │ │ │ ├── fingerprint/ │ │ │ │ │ ├── dep_info.rs │ │ │ │ │ ├── dirty_reason.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── rustdoc.rs │ │ │ │ ├── future_incompat.rs │ │ │ │ ├── job_queue/ │ │ │ │ │ ├── job.rs │ │ │ │ │ ├── job_state.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── layout.rs │ │ │ │ ├── links.rs │ │ │ │ ├── locking.rs │ │ │ │ ├── lto.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── output_depinfo.rs │ │ │ │ ├── output_sbom.rs │ │ │ │ ├── rustdoc.rs │ │ │ │ ├── standard_lib.rs │ │ │ │ ├── timings/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── report.rs │ │ │ │ │ └── timings.js │ │ │ │ ├── unit.rs │ │ │ │ ├── unit_dependencies.rs │ │ │ │ └── unit_graph.rs │ │ │ ├── dependency.rs │ │ │ ├── features.rs │ │ │ ├── gc.rs │ │ │ ├── global_cache_tracker.rs │ │ │ ├── manifest.rs │ │ │ ├── mod.rs │ │ │ ├── package.rs │ │ │ ├── package_id.rs │ │ │ ├── package_id_spec.rs │ │ │ ├── profiles.rs │ │ │ ├── registry.rs │ │ │ ├── resolver/ │ │ │ │ ├── conflict_cache.rs │ │ │ │ ├── context.rs │ │ │ │ ├── dep_cache.rs │ │ │ │ ├── encode.rs │ │ │ │ ├── errors.rs │ │ │ │ ├── features.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── resolve.rs │ │ │ │ ├── types.rs │ │ │ │ └── version_prefs.rs │ │ │ ├── shell.rs │ │ │ ├── source_id.rs │ │ │ ├── summary.rs │ │ │ └── workspace.rs │ │ ├── lib.rs │ │ ├── lints/ │ │ │ ├── mod.rs │ │ │ └── rules/ │ │ │ ├── blanket_hint_mostly_unused.rs │ │ │ ├── im_a_teapot.rs │ │ │ ├── implicit_minimum_version_req.rs │ │ │ ├── missing_lints_inheritance.rs │ │ │ ├── mod.rs │ │ │ ├── non_kebab_case_bins.rs │ │ │ ├── non_kebab_case_features.rs │ │ │ ├── non_kebab_case_packages.rs │ │ │ ├── non_snake_case_features.rs │ │ │ ├── non_snake_case_packages.rs │ │ │ ├── redundant_homepage.rs │ │ │ ├── redundant_readme.rs │ │ │ ├── unknown_lints.rs │ │ │ ├── unused_workspace_dependencies.rs │ │ │ └── unused_workspace_package_fields.rs │ │ ├── macros.rs │ │ ├── ops/ │ │ │ ├── cargo_add/ │ │ │ │ ├── crate_spec.rs │ │ │ │ └── mod.rs │ │ │ ├── cargo_clean.rs │ │ │ ├── cargo_compile/ │ │ │ │ ├── compile_filter.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── packages.rs │ │ │ │ └── unit_generator.rs │ │ │ ├── cargo_config.rs │ │ │ ├── cargo_doc.rs │ │ │ ├── cargo_fetch.rs │ │ │ ├── cargo_install.rs │ │ │ ├── cargo_new.rs │ │ │ ├── cargo_output_metadata.rs │ │ │ ├── cargo_package/ │ │ │ │ ├── mod.rs │ │ │ │ ├── vcs.rs │ │ │ │ └── verify.rs │ │ │ ├── cargo_pkgid.rs │ │ │ ├── cargo_read_manifest.rs │ │ │ ├── cargo_remove.rs │ │ │ ├── cargo_report/ │ │ │ │ ├── mod.rs │ │ │ │ ├── rebuilds.rs │ │ │ │ ├── sessions.rs │ │ │ │ ├── timings.rs │ │ │ │ └── util.rs │ │ │ ├── cargo_run.rs │ │ │ ├── cargo_test.rs │ │ │ ├── cargo_uninstall.rs │ │ │ ├── cargo_update.rs │ │ │ ├── common_for_install_and_uninstall.rs │ │ │ ├── fix/ │ │ │ │ ├── fix_edition.rs │ │ │ │ └── mod.rs │ │ │ ├── lockfile.rs │ │ │ ├── mod.rs │ │ │ ├── registry/ │ │ │ │ ├── info/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── view.rs │ │ │ │ ├── login.rs │ │ │ │ ├── logout.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── owner.rs │ │ │ │ ├── publish.rs │ │ │ │ ├── search.rs │ │ │ │ └── yank.rs │ │ │ ├── resolve.rs │ │ │ ├── tree/ │ │ │ │ ├── format/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── parse.rs │ │ │ │ ├── graph.rs │ │ │ │ └── mod.rs │ │ │ └── vendor.rs │ │ ├── sources/ │ │ │ ├── config.rs │ │ │ ├── directory.rs │ │ │ ├── git/ │ │ │ │ ├── known_hosts.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── oxide.rs │ │ │ │ ├── source.rs │ │ │ │ └── utils.rs │ │ │ ├── mod.rs │ │ │ ├── overlay.rs │ │ │ ├── path.rs │ │ │ ├── registry/ │ │ │ │ ├── download.rs │ │ │ │ ├── http_remote.rs │ │ │ │ ├── index/ │ │ │ │ │ ├── cache.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── local.rs │ │ │ │ ├── mod.rs │ │ │ │ └── remote.rs │ │ │ ├── replaced.rs │ │ │ └── source.rs │ │ ├── util/ │ │ │ ├── auth/ │ │ │ │ └── mod.rs │ │ │ ├── cache_lock.rs │ │ │ ├── canonical_url.rs │ │ │ ├── command_prelude.rs │ │ │ ├── context/ │ │ │ │ ├── config_value.rs │ │ │ │ ├── de.rs │ │ │ │ ├── environment.rs │ │ │ │ ├── error.rs │ │ │ │ ├── key.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── path.rs │ │ │ │ ├── schema.rs │ │ │ │ ├── target.rs │ │ │ │ └── value.rs │ │ │ ├── counter.rs │ │ │ ├── cpu.rs │ │ │ ├── credential/ │ │ │ │ ├── adaptor.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── paseto.rs │ │ │ │ ├── process.rs │ │ │ │ └── token.rs │ │ │ ├── dependency_queue.rs │ │ │ ├── diagnostic_server.rs │ │ │ ├── edit_distance.rs │ │ │ ├── errors.rs │ │ │ ├── flock.rs │ │ │ ├── frontmatter.rs │ │ │ ├── graph.rs │ │ │ ├── hasher.rs │ │ │ ├── hex.rs │ │ │ ├── important_paths.rs │ │ │ ├── interning.rs │ │ │ ├── into_url.rs │ │ │ ├── into_url_with_base.rs │ │ │ ├── io.rs │ │ │ ├── job.rs │ │ │ ├── lockserver.rs │ │ │ ├── log_message.rs │ │ │ ├── logger.rs │ │ │ ├── machine_message.rs │ │ │ ├── mod.rs │ │ │ ├── network/ │ │ │ │ ├── http.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── proxy.rs │ │ │ │ ├── retry.rs │ │ │ │ └── sleep.rs │ │ │ ├── once.rs │ │ │ ├── open.rs │ │ │ ├── progress.rs │ │ │ ├── queue.rs │ │ │ ├── restricted_names.rs │ │ │ ├── rustc.rs │ │ │ ├── semver_eval_ext.rs │ │ │ ├── semver_ext.rs │ │ │ ├── sqlite.rs │ │ │ ├── style.rs │ │ │ ├── toml/ │ │ │ │ ├── embedded.rs │ │ │ │ ├── mod.rs │ │ │ │ └── targets.rs │ │ │ ├── toml_mut/ │ │ │ │ ├── dependency.rs │ │ │ │ ├── manifest.rs │ │ │ │ ├── mod.rs │ │ │ │ └── upgrade.rs │ │ │ ├── vcs.rs │ │ │ └── workspace.rs │ │ └── version.rs │ ├── doc/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── book.toml │ │ ├── contrib/ │ │ │ ├── README.md │ │ │ ├── book.toml │ │ │ └── src/ │ │ │ ├── SUMMARY.md │ │ │ ├── design.md │ │ │ ├── documentation/ │ │ │ │ └── index.md │ │ │ ├── implementation/ │ │ │ │ ├── architecture.md │ │ │ │ ├── console.md │ │ │ │ ├── debugging.md │ │ │ │ ├── filesystem.md │ │ │ │ ├── formatting.md │ │ │ │ ├── index.md │ │ │ │ ├── packages.md │ │ │ │ ├── schemas.md │ │ │ │ └── subcommands.md │ │ │ ├── index.md │ │ │ ├── issues.md │ │ │ ├── process/ │ │ │ │ ├── index.md │ │ │ │ ├── release.md │ │ │ │ ├── rfc.md │ │ │ │ ├── security.md │ │ │ │ ├── unstable.md │ │ │ │ └── working-on-cargo.md │ │ │ ├── team.md │ │ │ └── tests/ │ │ │ ├── crater.md │ │ │ ├── index.md │ │ │ ├── profiling.md │ │ │ ├── running.md │ │ │ └── writing.md │ │ ├── man/ │ │ │ ├── cargo-add.md │ │ │ ├── cargo-bench.md │ │ │ ├── cargo-build.md │ │ │ ├── cargo-check.md │ │ │ ├── cargo-clean.md │ │ │ ├── cargo-doc.md │ │ │ ├── cargo-fetch.md │ │ │ ├── cargo-fix.md │ │ │ ├── cargo-generate-lockfile.md │ │ │ ├── cargo-help.md │ │ │ ├── cargo-info.md │ │ │ ├── cargo-init.md │ │ │ ├── cargo-install.md │ │ │ ├── cargo-locate-project.md │ │ │ ├── cargo-login.md │ │ │ ├── cargo-logout.md │ │ │ ├── cargo-metadata.md │ │ │ ├── cargo-new.md │ │ │ ├── cargo-owner.md │ │ │ ├── cargo-package.md │ │ │ ├── cargo-pkgid.md │ │ │ ├── cargo-publish.md │ │ │ ├── cargo-remove.md │ │ │ ├── cargo-report-future-incompatibilities.md │ │ │ ├── cargo-report.md │ │ │ ├── cargo-run.md │ │ │ ├── cargo-rustc.md │ │ │ ├── cargo-rustdoc.md │ │ │ ├── cargo-search.md │ │ │ ├── cargo-test.md │ │ │ ├── cargo-tree.md │ │ │ ├── cargo-uninstall.md │ │ │ ├── cargo-update.md │ │ │ ├── cargo-vendor.md │ │ │ ├── cargo-version.md │ │ │ ├── cargo-yank.md │ │ │ ├── cargo.md │ │ │ ├── generated_txt/ │ │ │ │ ├── cargo-add.txt │ │ │ │ ├── cargo-bench.txt │ │ │ │ ├── cargo-build.txt │ │ │ │ ├── cargo-check.txt │ │ │ │ ├── cargo-clean.txt │ │ │ │ ├── cargo-doc.txt │ │ │ │ ├── cargo-fetch.txt │ │ │ │ ├── cargo-fix.txt │ │ │ │ ├── cargo-generate-lockfile.txt │ │ │ │ ├── cargo-help.txt │ │ │ │ ├── cargo-info.txt │ │ │ │ ├── cargo-init.txt │ │ │ │ ├── cargo-install.txt │ │ │ │ ├── cargo-locate-project.txt │ │ │ │ ├── cargo-login.txt │ │ │ │ ├── cargo-logout.txt │ │ │ │ ├── cargo-metadata.txt │ │ │ │ ├── cargo-new.txt │ │ │ │ ├── cargo-owner.txt │ │ │ │ ├── cargo-package.txt │ │ │ │ ├── cargo-pkgid.txt │ │ │ │ ├── cargo-publish.txt │ │ │ │ ├── cargo-remove.txt │ │ │ │ ├── cargo-report-future-incompatibilities.txt │ │ │ │ ├── cargo-report.txt │ │ │ │ ├── cargo-run.txt │ │ │ │ ├── cargo-rustc.txt │ │ │ │ ├── cargo-rustdoc.txt │ │ │ │ ├── cargo-search.txt │ │ │ │ ├── cargo-test.txt │ │ │ │ ├── cargo-tree.txt │ │ │ │ ├── cargo-uninstall.txt │ │ │ │ ├── cargo-update.txt │ │ │ │ ├── cargo-vendor.txt │ │ │ │ ├── cargo-version.txt │ │ │ │ ├── cargo-yank.txt │ │ │ │ └── cargo.txt │ │ │ └── includes/ │ │ │ ├── description-install-root.md │ │ │ ├── description-one-target.md │ │ │ ├── options-display.md │ │ │ ├── options-future-incompat.md │ │ │ ├── options-ignore-rust-version.md │ │ │ ├── options-index.md │ │ │ ├── options-jobs.md │ │ │ ├── options-keep-going.md │ │ │ ├── options-locked.md │ │ │ ├── options-manifest-path.md │ │ │ ├── options-message-format.md │ │ │ ├── options-new.md │ │ │ ├── options-output-format.md │ │ │ ├── options-profile-legacy-check.md │ │ │ ├── options-profile.md │ │ │ ├── options-registry.md │ │ │ ├── options-release.md │ │ │ ├── options-target-dir.md │ │ │ ├── options-target-triple.md │ │ │ ├── options-targets-bin-auto-built.md │ │ │ ├── options-targets-lib-bin.md │ │ │ ├── options-targets.md │ │ │ ├── options-test.md │ │ │ ├── options-timings.md │ │ │ ├── options-token.md │ │ │ ├── section-environment.md │ │ │ ├── section-exit-status.md │ │ │ ├── section-features.md │ │ │ ├── section-options-common.md │ │ │ ├── section-options-package.md │ │ │ └── section-package-selection.md │ │ ├── src/ │ │ │ ├── CHANGELOG.md │ │ │ ├── SUMMARY.md │ │ │ ├── appendix/ │ │ │ │ ├── git-authentication.md │ │ │ │ └── glossary.md │ │ │ ├── commands/ │ │ │ │ ├── build-commands.md │ │ │ │ ├── cargo-add.md │ │ │ │ ├── cargo-bench.md │ │ │ │ ├── cargo-build.md │ │ │ │ ├── cargo-check.md │ │ │ │ ├── cargo-clean.md │ │ │ │ ├── cargo-clippy.md │ │ │ │ ├── cargo-doc.md │ │ │ │ ├── cargo-fetch.md │ │ │ │ ├── cargo-fix.md │ │ │ │ ├── cargo-fmt.md │ │ │ │ ├── cargo-generate-lockfile.md │ │ │ │ ├── cargo-help.md │ │ │ │ ├── cargo-info.md │ │ │ │ ├── cargo-init.md │ │ │ │ ├── cargo-install.md │ │ │ │ ├── cargo-locate-project.md │ │ │ │ ├── cargo-login.md │ │ │ │ ├── cargo-logout.md │ │ │ │ ├── cargo-metadata.md │ │ │ │ ├── cargo-miri.md │ │ │ │ ├── cargo-new.md │ │ │ │ ├── cargo-owner.md │ │ │ │ ├── cargo-package.md │ │ │ │ ├── cargo-pkgid.md │ │ │ │ ├── cargo-publish.md │ │ │ │ ├── cargo-remove.md │ │ │ │ ├── cargo-report-future-incompatibilities.md │ │ │ │ ├── cargo-report.md │ │ │ │ ├── cargo-run.md │ │ │ │ ├── cargo-rustc.md │ │ │ │ ├── cargo-rustdoc.md │ │ │ │ ├── cargo-search.md │ │ │ │ ├── cargo-test.md │ │ │ │ ├── cargo-tree.md │ │ │ │ ├── cargo-uninstall.md │ │ │ │ ├── cargo-update.md │ │ │ │ ├── cargo-vendor.md │ │ │ │ ├── cargo-version.md │ │ │ │ ├── cargo-yank.md │ │ │ │ ├── cargo.md │ │ │ │ ├── deprecated-and-removed.md │ │ │ │ ├── general-commands.md │ │ │ │ ├── index.md │ │ │ │ ├── manifest-commands.md │ │ │ │ ├── package-commands.md │ │ │ │ ├── publishing-commands.md │ │ │ │ └── report-commands.md │ │ │ ├── faq.md │ │ │ ├── getting-started/ │ │ │ │ ├── first-steps.md │ │ │ │ ├── index.md │ │ │ │ └── installation.md │ │ │ ├── guide/ │ │ │ │ ├── build-performance.md │ │ │ │ ├── cargo-home.md │ │ │ │ ├── cargo-toml-vs-cargo-lock.md │ │ │ │ ├── continuous-integration.md │ │ │ │ ├── creating-a-new-project.md │ │ │ │ ├── dependencies.md │ │ │ │ ├── index.md │ │ │ │ ├── project-layout.md │ │ │ │ ├── tests.md │ │ │ │ ├── why-cargo-exists.md │ │ │ │ └── working-on-an-existing-project.md │ │ │ ├── index.md │ │ │ └── reference/ │ │ │ ├── build-cache.md │ │ │ ├── build-script-examples.md │ │ │ ├── build-scripts.md │ │ │ ├── cargo-targets.md │ │ │ ├── config.md │ │ │ ├── credential-provider-protocol.md │ │ │ ├── environment-variables.md │ │ │ ├── external-tools.md │ │ │ ├── features-examples.md │ │ │ ├── features.md │ │ │ ├── future-incompat-report.md │ │ │ ├── index.md │ │ │ ├── lints.md │ │ │ ├── manifest.md │ │ │ ├── overriding-dependencies.md │ │ │ ├── pkgid-spec.md │ │ │ ├── profiles.md │ │ │ ├── publishing.md │ │ │ ├── registries.md │ │ │ ├── registry-authentication.md │ │ │ ├── registry-index.md │ │ │ ├── registry-web-api.md │ │ │ ├── resolver.md │ │ │ ├── running-a-registry.md │ │ │ ├── rust-version.md │ │ │ ├── semver.md │ │ │ ├── source-replacement.md │ │ │ ├── specifying-dependencies.md │ │ │ ├── timings.md │ │ │ ├── unstable.md │ │ │ └── workspaces.md │ │ └── theme/ │ │ ├── cargo.css │ │ └── head.hbs │ └── etc/ │ ├── _cargo │ ├── cargo.bashcomp.sh │ └── man/ │ ├── cargo-add.1 │ ├── cargo-bench.1 │ ├── cargo-build.1 │ ├── cargo-check.1 │ ├── cargo-clean.1 │ ├── cargo-doc.1 │ ├── cargo-fetch.1 │ ├── cargo-fix.1 │ ├── cargo-generate-lockfile.1 │ ├── cargo-help.1 │ ├── cargo-info.1 │ ├── cargo-init.1 │ ├── cargo-install.1 │ ├── cargo-locate-project.1 │ ├── cargo-login.1 │ ├── cargo-logout.1 │ ├── cargo-metadata.1 │ ├── cargo-new.1 │ ├── cargo-owner.1 │ ├── cargo-package.1 │ ├── cargo-pkgid.1 │ ├── cargo-publish.1 │ ├── cargo-remove.1 │ ├── cargo-report-future-incompatibilities.1 │ ├── cargo-report.1 │ ├── cargo-run.1 │ ├── cargo-rustc.1 │ ├── cargo-rustdoc.1 │ ├── cargo-search.1 │ ├── cargo-test.1 │ ├── cargo-tree.1 │ ├── cargo-uninstall.1 │ ├── cargo-update.1 │ ├── cargo-vendor.1 │ ├── cargo-version.1 │ ├── cargo-yank.1 │ └── cargo.1 ├── tests/ │ ├── build-std/ │ │ └── main.rs │ └── testsuite/ │ ├── advanced_env.rs │ ├── alt_registry.rs │ ├── artifact_dep.rs │ ├── artifact_dir.rs │ ├── bad_config.rs │ ├── bad_manifest_path.rs │ ├── bench.rs │ ├── binary_name.rs │ ├── build.rs │ ├── build_analysis.rs │ ├── build_dir.rs │ ├── build_dir_legacy.rs │ ├── build_script.rs │ ├── build_script_env.rs │ ├── build_script_extra_link_arg.rs │ ├── build_scripts_multiple.rs │ ├── cache_lock.rs │ ├── cache_messages.rs │ ├── cargo/ │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── z_help/ │ │ └── mod.rs │ ├── cargo_add/ │ │ ├── add-basic.in/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── add_basic/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── add_multiple/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── add_no_vendored_package_with_alter_registry/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── vendor/ │ │ │ │ └── aa/ │ │ │ │ ├── .cargo-checksum.json │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── add_no_vendored_package_with_vendor/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── vendor/ │ │ │ │ └── aa/ │ │ │ │ ├── .cargo-checksum.json │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── add_toolchain/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── build/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── build_prefer_existing_version/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── dependency/ │ │ │ └── Cargo.toml │ │ ├── change_rename_target/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── cyclic_features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── default_features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── deprecated_default_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── deprecated_section/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── detect_workspace_inherit/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── detect_workspace_inherit_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── detect_workspace_inherit_optional/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── detect_workspace_inherit_path_base/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── deps/ │ │ │ │ │ └── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── deps/ │ │ │ │ └── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── detect_workspace_inherit_public/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── dev/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── dev_build_conflict/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── dev_existing_path_base/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── dev_prefer_existing_version/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── dependency/ │ │ │ └── Cargo.toml │ │ ├── dry_run/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── empty_dep_name/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── feature_suggestion_multiple/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── mod.rs │ │ ├── feature_suggestion_none/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── mod.rs │ │ ├── feature_suggestion_single/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── mod.rs │ │ ├── features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_activated_over_limit/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_deactivated_over_limit/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_empty/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_error_activated_over_limit/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_error_deactivated_over_limit/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_multiple_occurrences/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_preserve/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_spaced_values/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_unknown/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── features_unknown_no_features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_branch/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_conflicts_namever/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_dev/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_inferred_name/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_inferred_name_multiple/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_multiple_names/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_multiple_packages_features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_registry/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_rev/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── git_tag/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── infer_prerelease/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_arg/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_git_name/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_inherited_dependency/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── invalid_key_inherit_dependency/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── invalid_key_overwrite_inherit_dependency/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── invalid_key_rename_inherit_dependency/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ ├── dependency-alt/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ ├── dependency-alt/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── invalid_manifest/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_name_external/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_path/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_path_name/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── invalid_path_self/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_target_empty/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_vers/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── list_features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── list_features_path/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ ├── optional/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── list_features_path_no_default/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ ├── optional/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── locked_changed/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── locked_unchanged/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── lockfile_updated/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── manifest_path_package/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── merge_activated_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── missing_at_in_crate_spec/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── mod.rs │ │ ├── multiple_conflicts_with_features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── multiple_conflicts_with_rename/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── namever/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── no_args/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── no_default_features/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── no_optional/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── no_public/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── normalize_name_git/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── normalize_name_path/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── normalize_name_path_existing/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── fuzzy_name/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── primary/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── normalize_name_registry/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── normalize_name_registry_existing/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── normalize_name_registry_yanked/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── normalize_name_workspace_dep/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── primary/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── offline_empty_cache/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── optional/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_default_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_default_features_with_no_default_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_git_with_path/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_inherit_features_noop/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_inherit_noop/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_inherit_optional_noop/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_inline_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_name_dev_noop/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── dependency/ │ │ │ └── Cargo.toml │ │ ├── overwrite_name_noop/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── dependency/ │ │ │ └── Cargo.toml │ │ ├── overwrite_no_default_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_no_default_features_with_default_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_no_optional/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_no_optional_with_optional/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_no_public/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_no_public_with_public/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_optional/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_optional_with_no_optional/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_optional_with_optional/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_path_base_with_version/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_path_noop/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── dependency/ │ │ │ └── Cargo.toml │ │ ├── overwrite_path_with_version/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_preserves_inline_table/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_public/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_public_with_no_public/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_rename_with_no_rename/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_rename_with_rename/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_rename_with_rename_noop/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_version_with_git/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_version_with_path/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_with_rename/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── overwrite_workspace_dep/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── overwrite_workspace_dep_features/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path_base/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path_base_inferred_name/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path_base_missing_base_path/ │ │ │ ├── in/ │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path_base_unstable/ │ │ │ ├── in/ │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path_dev/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path_inferred_name/ │ │ │ ├── in/ │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── path_inferred_name_conflicts_full_feature/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ ├── optional/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── prefixed_v_in_version/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── preserve_dep_std_table/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── preserve_features_sorted/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── preserve_features_table/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── preserve_features_unsorted/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── preserve_sorted/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── preserve_unsorted/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── public/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── public_common_version/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── quiet/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── registry/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── rename/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── require_weak/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── rust_version_ignore/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rust_version_incompatible/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rust_version_latest/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rust_version_older/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rustc_ignore/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rustc_incompatible/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rustc_latest/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rustc_older/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── script_bare/ │ │ │ ├── in/ │ │ │ │ └── cargo-test-fixture.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── cargo-test-fixture.rs │ │ ├── script_escape/ │ │ │ ├── in/ │ │ │ │ └── cargo-test-fixture.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── cargo-test-fixture.rs │ │ ├── script_frontmatter/ │ │ │ ├── in/ │ │ │ │ └── cargo-test-fixture.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── cargo-test-fixture.rs │ │ ├── script_frontmatter_empty/ │ │ │ ├── in/ │ │ │ │ └── cargo-test-fixture.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── cargo-test-fixture.rs │ │ ├── script_shebang/ │ │ │ ├── in/ │ │ │ │ └── cargo-test-fixture.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── cargo-test-fixture.rs │ │ ├── sorted_table_with_dotted_item/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── symlink.rs │ │ ├── target/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── target_cfg/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── unknown_inherited_feature/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── vers/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── workspace_name/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── workspace_path/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ ├── workspace_path_dev/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── dependency/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── primary/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dependency/ │ │ │ │ └── Cargo.toml │ │ │ └── primary/ │ │ │ └── Cargo.toml │ │ └── yanked/ │ │ ├── mod.rs │ │ └── out/ │ │ └── Cargo.toml │ ├── cargo_alias_config.rs │ ├── cargo_bench/ │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── no_keep_going/ │ │ ├── in/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── mod.rs │ ├── cargo_build/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_check/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_clean/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_command.rs │ ├── cargo_config/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_doc/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_env_config.rs │ ├── cargo_features.rs │ ├── cargo_fetch/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_fix/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_generate_lockfile/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_git_checkout/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_help/ │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── nested_alias_dash_joined/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── nested_cmd/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── nested_cmd_dash_joined/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── nested_cmd_suggestion/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── nested_cmd_with_extra_flags/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── nested_subcommand_suggestion/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── single_alias/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── single_cmd/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── single_cmd_space_joined/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ ├── single_cmd_suggestion/ │ │ │ ├── mod.rs │ │ │ └── stdout.term.txt │ │ └── single_cmd_with_extra_flags/ │ │ ├── mod.rs │ │ └── stdout.term.txt │ ├── cargo_info/ │ │ ├── basic/ │ │ │ └── mod.rs │ │ ├── crate_name_normalization_from_hyphen_to_underscore/ │ │ │ └── mod.rs │ │ ├── crate_name_normalization_from_underscore_to_hyphen/ │ │ │ └── mod.rs │ │ ├── features/ │ │ │ └── mod.rs │ │ ├── features_activated_over_limit/ │ │ │ └── mod.rs │ │ ├── features_activated_over_limit_verbose/ │ │ │ └── mod.rs │ │ ├── features_deactivated_over_limit/ │ │ │ └── mod.rs │ │ ├── git_dependency/ │ │ │ └── mod.rs │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── not_found/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── path_dependency/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── crates/ │ │ │ │ │ └── crate1/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── crate1/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── pick_msrv_compatible_package/ │ │ │ └── mod.rs │ │ ├── pick_msrv_compatible_package_within_ws/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── crate1/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── crate2/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crate1/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── crate2/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── pick_msrv_compatible_package_within_ws_and_use_msrv_from_ws/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── crate1/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── crate2/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crate1/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── crate2/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── specify_empty_version_with_url/ │ │ │ └── mod.rs │ │ ├── specify_version_outside_ws/ │ │ │ └── mod.rs │ │ ├── specify_version_with_url_but_registry_is_not_matched/ │ │ │ └── mod.rs │ │ ├── specify_version_within_ws_and_conflict_with_lockfile/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── specify_version_within_ws_and_match_with_lockfile/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── transitive_dependency_within_ws/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── crates/ │ │ │ │ │ ├── direct1/ │ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── lib.rs │ │ │ │ │ ├── direct2/ │ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── lib.rs │ │ │ │ │ ├── transitive1/ │ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── lib.rs │ │ │ │ │ ├── transitive123/ │ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── lib.rs │ │ │ │ │ └── transitive2/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ ├── direct1/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ ├── direct2/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ ├── transitive1/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ ├── transitive123/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── transitive2/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── verbose/ │ │ │ └── mod.rs │ │ ├── with_default_registry_configured/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── crates/ │ │ │ │ │ └── crate1/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── crate1/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── with_default_registry_configured_and_specified/ │ │ │ ├── in/ │ │ │ │ ├── .cargo/ │ │ │ │ │ └── config.toml │ │ │ │ ├── Cargo.toml │ │ │ │ ├── crates/ │ │ │ │ │ └── crate1/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── crate1/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── with_frozen_outside_ws/ │ │ │ └── mod.rs │ │ ├── with_frozen_within_ws/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── with_locked_outside_ws/ │ │ │ └── mod.rs │ │ ├── with_locked_within_ws/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── with_locked_within_ws_and_pick_the_package/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── with_offline/ │ │ │ └── mod.rs │ │ ├── with_quiet/ │ │ │ └── mod.rs │ │ ├── within_workspace.in/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── within_ws/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── within_ws_and_pick_ws_package/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── within_ws_with_alternative_registry/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── within_ws_without_lockfile/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── without_requiring_registry_auth/ │ │ ├── in/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── mod.rs │ │ └── out/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── cargo_init/ │ │ ├── auto_git/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── bin_already_exists_explicit/ │ │ │ ├── in/ │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── bin_already_exists_explicit_nosrc/ │ │ │ ├── in/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── main.rs │ │ ├── bin_already_exists_implicit/ │ │ │ ├── in/ │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── bin_already_exists_implicit_namenosrc/ │ │ │ ├── in/ │ │ │ │ └── case.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── case.rs │ │ ├── bin_already_exists_implicit_namesrc/ │ │ │ ├── in/ │ │ │ │ └── src/ │ │ │ │ └── case.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── case.rs │ │ ├── bin_already_exists_implicit_nosrc/ │ │ │ ├── in/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── main.rs │ │ ├── both_lib_and_bin/ │ │ │ └── mod.rs │ │ ├── cant_create_library_when_both_binlib_present/ │ │ │ ├── in/ │ │ │ │ ├── case.rs │ │ │ │ └── lib.rs │ │ │ └── mod.rs │ │ ├── confused_by_multiple_lib_files/ │ │ │ ├── in/ │ │ │ │ ├── lib.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── lib.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── creates_binary_when_both_binlib_present/ │ │ │ ├── in/ │ │ │ │ ├── case.rs │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── case.rs │ │ │ └── lib.rs │ │ ├── creates_binary_when_instructed_and_has_lib_file/ │ │ │ ├── in/ │ │ │ │ └── case.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── case.rs │ │ ├── creates_library_when_instructed_and_has_bin_file/ │ │ │ ├── in/ │ │ │ │ └── case.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── case.rs │ │ ├── empty_dir/ │ │ │ └── mod.rs │ │ ├── error_on_existing_package.rs │ │ ├── explicit_bin_with_git/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── formats_source/ │ │ │ ├── in/ │ │ │ │ └── rustfmt.toml │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── rustfmt.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── fossil_autodetect/ │ │ │ ├── in/ │ │ │ │ └── .fossil/ │ │ │ │ └── .keep │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .fossil-settings/ │ │ │ │ ├── clean-glob │ │ │ │ └── ignore-glob │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── git_autodetect/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── git_ignore_exists_no_conflicting_entries/ │ │ │ ├── in/ │ │ │ │ └── .gitignore │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── ignores_failure_to_format_source/ │ │ │ ├── in/ │ │ │ │ └── rustfmt.toml │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── rustfmt.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── in_home_directory/ │ │ │ └── mod.rs │ │ ├── inferred_bin_with_git/ │ │ │ ├── in/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── main.rs │ │ ├── inferred_lib_with_git/ │ │ │ ├── in/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── lib.rs │ │ ├── inherit_workspace_package_table/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ ├── crates/ │ │ │ │ │ └── foo/ │ │ │ │ │ └── src/ │ │ │ │ │ └── main.rs │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── foo/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── invalid_dir_name/ │ │ │ └── mod.rs │ │ ├── lib_already_exists_nosrc/ │ │ │ ├── in/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── lib.rs │ │ ├── lib_already_exists_src/ │ │ │ ├── in/ │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── mercurial_autodetect/ │ │ │ ├── in/ │ │ │ │ └── .hg/ │ │ │ │ └── .keep │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .hgignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── mod.rs │ │ ├── multibin_project_name_clash/ │ │ │ ├── in/ │ │ │ │ ├── case.rs │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── case.rs │ │ │ └── main.rs │ │ ├── no_filename/ │ │ │ └── mod.rs │ │ ├── path_contains_separator/ │ │ │ ├── in/ │ │ │ │ └── .keep │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── pijul_autodetect/ │ │ │ ├── in/ │ │ │ │ └── .pijul/ │ │ │ │ └── .keep │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .ignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── reserved_name/ │ │ │ └── mod.rs │ │ ├── reserved_name_core.rs │ │ ├── simple_bin/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── simple_git/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── simple_git_ignore_exists/ │ │ │ ├── in/ │ │ │ │ └── .gitignore │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── simple_hg/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .hgignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── simple_hg_ignore_exists/ │ │ │ ├── in/ │ │ │ │ ├── .hg/ │ │ │ │ │ └── .keep │ │ │ │ └── .hgignore │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── .hgignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── simple_lib/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── unknown_flags/ │ │ │ └── mod.rs │ │ ├── with_argument/ │ │ │ ├── in/ │ │ │ │ └── foo/ │ │ │ │ └── .keep │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── foo/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── workspace_add_member/ │ │ ├── in/ │ │ │ ├── Cargo.toml │ │ │ └── crates/ │ │ │ └── foo/ │ │ │ └── .keep │ │ ├── mod.rs │ │ └── out/ │ │ ├── Cargo.toml │ │ └── crates/ │ │ └── foo/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── cargo_install/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_locate_project/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_login/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_logout/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_metadata/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_new/ │ │ ├── add_members_to_non_workspace/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── bar/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── add_members_to_workspace_format_previous_items/ │ │ │ ├── in/ │ │ │ │ └── Cargo.toml │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── crates/ │ │ │ └── foo/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── add_members_to_workspace_format_sorted/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── crates/ │ │ │ │ ├── bar/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── main.rs │ │ │ │ └── qux/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── crates/ │ │ │ └── foo/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── add_members_to_workspace_with_absolute_package_path/ │ │ │ ├── in/ │ │ │ │ └── Cargo.toml │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── crates/ │ │ │ └── foo/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── add_members_to_workspace_with_empty_members/ │ │ │ ├── in/ │ │ │ │ └── Cargo.toml │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── crates/ │ │ │ └── foo/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── add_members_to_workspace_with_exclude_list/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── crates/ │ │ │ └── foo/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── add_members_to_workspace_with_members_glob/ │ │ │ ├── in/ │ │ │ │ └── Cargo.toml │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── crates/ │ │ │ └── foo/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── add_members_to_workspace_without_members/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── bar/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── empty_name/ │ │ │ ├── in/ │ │ │ │ └── .keep │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── .keep │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── ignore_current_dir_workspace/ │ │ │ ├── in/ │ │ │ │ └── workspace/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── out-of-workspace/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── workspace/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── inherit_workspace_lints/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── foo/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── inherit_workspace_package_table/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── foo/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── inherit_workspace_package_table.in/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── inherit_workspace_package_table_with_edition/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── foo/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── inherit_workspace_package_table_with_registry/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── foo/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── inherit_workspace_package_table_without_version/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── crates/ │ │ │ │ └── foo/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── mod.rs │ │ └── not_inherit_workspace_package_table_if_not_members/ │ │ ├── in/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── mod.rs │ │ └── out/ │ │ ├── Cargo.toml │ │ ├── bar/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── src/ │ │ └── lib.rs │ ├── cargo_owner/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_package/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_pkgid/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_publish/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_read_manifest/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_remove/ │ │ ├── avoid_empty_tables/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── build/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── dev/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── dry_run/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── gc_keep_used_patch/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── serde/ │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ └── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── serde_derive/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── serde/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── serde_derive/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── gc_patch/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── gc_profile/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── gc_replace/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── my-package/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── my-package/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── invalid_arg/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_dep/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_package/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dep-a/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── dep-b/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── invalid_package_multiple/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dep-a/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── dep-b/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── invalid_section/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_section_dep/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_section_missing_flags/ │ │ │ ├── in/ │ │ │ │ └── Cargo.toml │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_target/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── invalid_target_dep/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── last_dep/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── mod.rs │ │ ├── multiple_deps/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── multiple_dev/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── no_arg/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── offline/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── optional_dep_feature/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── optional_dep_feature_formatting/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── optional_feature/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── package/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ ├── dep-a/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── dep-b/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── remove-basic.in/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── remove-package.in/ │ │ │ ├── Cargo.toml │ │ │ ├── dep-a/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── dep-b/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── remove-target.in/ │ │ │ └── Cargo.toml │ │ ├── remove_basic/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── script/ │ │ │ ├── in/ │ │ │ │ └── cargo-remove-test-fixture.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── cargo-remove-test-fixture.rs │ │ ├── script_last/ │ │ │ ├── in/ │ │ │ │ └── cargo-remove-test-fixture.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── cargo-remove-test-fixture.rs │ │ ├── skip_gc_glob_profile/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── target/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── target_build/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── target_dev/ │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ └── Cargo.toml │ │ ├── update_lock_file/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── workspace/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── my-package/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── my-package/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── workspace_non_virtual/ │ │ │ ├── in/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── my-member/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ ├── mod.rs │ │ │ └── out/ │ │ │ ├── Cargo.toml │ │ │ └── my-member/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── workspace_preserved/ │ │ ├── in/ │ │ │ ├── Cargo.toml │ │ │ ├── my-other-package/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── main.rs │ │ │ └── my-package/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── mod.rs │ │ └── out/ │ │ ├── Cargo.toml │ │ ├── my-other-package/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── my-package/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── cargo_report/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_report_future_incompat/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_report_rebuilds/ │ │ └── mod.rs │ ├── cargo_report_sessions/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_report_timings/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_run/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_rustc/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_rustdoc/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_search/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_targets.rs │ ├── cargo_test/ │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── no_keep_going/ │ │ ├── in/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── mod.rs │ ├── cargo_tree/ │ │ ├── deps.rs │ │ ├── dupe/ │ │ │ └── mod.rs │ │ ├── edge_kind/ │ │ │ └── mod.rs │ │ ├── features.rs │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_uninstall/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_update/ │ │ ├── help/ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── toolchain_pkgname/ │ │ ├── in/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── mod.rs │ ├── cargo_vendor/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_verify_project/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_version/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cargo_yank/ │ │ ├── help/ │ │ │ └── mod.rs │ │ └── mod.rs │ ├── cfg.rs │ ├── check.rs │ ├── check_cfg.rs │ ├── clean.rs │ ├── clean_new_layout.rs │ ├── collisions.rs │ ├── compile_time_deps.rs │ ├── concurrent.rs │ ├── config.rs │ ├── config_cli.rs │ ├── config_include.rs │ ├── corrupt_git.rs │ ├── credential_process.rs │ ├── cross_compile.rs │ ├── cross_publish.rs │ ├── custom_target.rs │ ├── death.rs │ ├── dep_info.rs │ ├── diagnostics.rs │ ├── direct_minimal_versions.rs │ ├── directory.rs │ ├── doc.rs │ ├── docscrape.rs │ ├── edition.rs │ ├── error.rs │ ├── feature_unification.rs │ ├── features.rs │ ├── features2.rs │ ├── features_namespaced.rs │ ├── fetch.rs │ ├── fix.rs │ ├── fix_n_times.rs │ ├── freshness.rs │ ├── freshness_checksum.rs │ ├── future_incompat_report.rs │ ├── generate_lockfile.rs │ ├── git.rs │ ├── git_auth.rs │ ├── git_gc.rs │ ├── git_shallow.rs │ ├── glob_targets.rs │ ├── global_cache_tracker.rs │ ├── help.rs │ ├── hints.rs │ ├── https.rs │ ├── inheritable_workspace_fields.rs │ ├── install.rs │ ├── install_upgrade.rs │ ├── jobserver.rs │ ├── lints/ │ │ ├── blanket_hint_mostly_unused.rs │ │ ├── error/ │ │ │ └── mod.rs │ │ ├── implicit_minimum_version_req.rs │ │ ├── inherited/ │ │ │ └── mod.rs │ │ ├── missing_lints_inheritance.rs │ │ ├── mod.rs │ │ ├── non_kebab_case_bins.rs │ │ ├── non_kebab_case_features.rs │ │ ├── non_kebab_case_packages.rs │ │ ├── non_snake_case_features.rs │ │ ├── non_snake_case_packages.rs │ │ ├── redundant_homepage.rs │ │ ├── redundant_readme.rs │ │ ├── unknown_lints.rs │ │ ├── unused_workspace_dependencies.rs │ │ ├── unused_workspace_package_fields.rs │ │ └── warning/ │ │ └── mod.rs │ ├── lints_table.rs │ ├── list_availables.rs │ ├── local_registry.rs │ ├── locate_project.rs │ ├── lockfile_compat.rs │ ├── lockfile_path.rs │ ├── login.rs │ ├── logout.rs │ ├── lto.rs │ ├── main.rs │ ├── member_discovery.rs │ ├── member_errors.rs │ ├── message_format.rs │ ├── messages.rs │ ├── metabuild.rs │ ├── metadata.rs │ ├── minimal_versions.rs │ ├── mock-std/ │ │ ├── dep_test/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── library/ │ │ ├── Cargo.toml │ │ ├── alloc/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── compiler_builtins/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── core/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── panic_unwind/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── proc_macro/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rustc-std-workspace-alloc/ │ │ │ ├── Cargo.toml │ │ │ └── lib.rs │ │ ├── rustc-std-workspace-core/ │ │ │ ├── Cargo.toml │ │ │ └── lib.rs │ │ ├── rustc-std-workspace-std/ │ │ │ ├── Cargo.toml │ │ │ └── lib.rs │ │ ├── std/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── sysroot/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── test/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── multitarget.rs │ ├── net_config.rs │ ├── new.rs │ ├── offline.rs │ ├── old_cargos.rs │ ├── open_namespaces.rs │ ├── owner.rs │ ├── package.rs │ ├── package_features.rs │ ├── package_message_format.rs │ ├── patch.rs │ ├── path.rs │ ├── paths.rs │ ├── pgo.rs │ ├── pkgid.rs │ ├── precise_pre_release.rs │ ├── proc_macro.rs │ ├── profile_config.rs │ ├── profile_custom.rs │ ├── profile_overrides.rs │ ├── profile_panic_immediate_abort.rs │ ├── profile_targets.rs │ ├── profile_trim_paths.rs │ ├── profiles.rs │ ├── progress.rs │ ├── pub_priv.rs │ ├── publish.rs │ ├── publish_lockfile.rs │ ├── read_manifest.rs │ ├── registry.rs │ ├── registry_auth.rs │ ├── registry_overlay.rs │ ├── rename_deps.rs │ ├── replace.rs │ ├── required_features.rs │ ├── run.rs │ ├── rust_version.rs │ ├── rustc.rs │ ├── rustc_info_cache.rs │ ├── rustdoc.rs │ ├── rustdoc_extern_html.rs │ ├── rustdocflags.rs │ ├── rustflags.rs │ ├── rustup.rs │ ├── sbom.rs │ ├── script/ │ │ ├── cargo.rs │ │ ├── mod.rs │ │ ├── rustc.rs │ │ └── rustc_fixtures/ │ │ ├── README.md │ │ ├── auxiliary/ │ │ │ ├── expr.rs │ │ │ ├── lib.rs │ │ │ └── makro.rs │ │ ├── content-contains-whitespace.rs │ │ ├── content-contains-whitespace.stderr │ │ ├── content-non-lexible-tokens.rs │ │ ├── content-non-lexible-tokens.stdout │ │ ├── escape-hyphens-leading.rs │ │ ├── escape-hyphens-leading.stdout │ │ ├── escape-hyphens-nonleading-1.rs │ │ ├── escape-hyphens-nonleading-1.stdout │ │ ├── escape-hyphens-nonleading-2.rs │ │ ├── escape-hyphens-nonleading-2.stdout │ │ ├── escape-hyphens-nonleading-3.rs │ │ ├── escape-hyphens-nonleading-3.stdout │ │ ├── fence-close-extra-after.rs │ │ ├── fence-close-extra-after.stderr │ │ ├── fence-indented-mismatch.rs │ │ ├── fence-indented-mismatch.stderr │ │ ├── fence-indented.rs │ │ ├── fence-indented.stdout │ │ ├── fence-mismatch-1.rs │ │ ├── fence-mismatch-1.stderr │ │ ├── fence-mismatch-2.rs │ │ ├── fence-mismatch-2.stderr │ │ ├── fence-too-many-dashes.rs │ │ ├── fence-too-many-dashes.stderr │ │ ├── fence-unclosed-1.rs │ │ ├── fence-unclosed-1.stderr │ │ ├── fence-unclosed-2.rs │ │ ├── fence-unclosed-2.stderr │ │ ├── fence-unclosed-3.rs │ │ ├── fence-unclosed-3.stderr │ │ ├── fence-unclosed-4.rs │ │ ├── fence-unclosed-4.stderr │ │ ├── fence-unclosed-5.rs │ │ ├── fence-unclosed-5.stderr │ │ ├── fence-unclosed-6.rs │ │ ├── fence-unclosed-6.stderr │ │ ├── fence-whitespace-trailing-1.rs │ │ ├── fence-whitespace-trailing-1.stdout │ │ ├── fence-whitespace-trailing-2.rs │ │ ├── fence-whitespace-trailing-2.stdout │ │ ├── frontmatter-crlf.rs │ │ ├── frontmatter-crlf.stdout │ │ ├── infostring-comma.rs │ │ ├── infostring-comma.stderr │ │ ├── infostring-dot-leading.rs │ │ ├── infostring-dot-leading.stderr │ │ ├── infostring-dot-nonleading.rs │ │ ├── infostring-dot-nonleading.stderr │ │ ├── infostring-hyphen-leading.rs │ │ ├── infostring-hyphen-leading.stderr │ │ ├── infostring-hyphen-nonleading.rs │ │ ├── infostring-hyphen-nonleading.stderr │ │ ├── infostring-space.rs │ │ ├── infostring-space.stderr │ │ ├── location-after-shebang.rs │ │ ├── location-after-shebang.stdout │ │ ├── location-after-tokens.rs │ │ ├── location-after-tokens.stdout │ │ ├── location-include-in-expr-ctxt.rs │ │ ├── location-include-in-expr-ctxt.stdout │ │ ├── location-include-in-item-ctxt.rs │ │ ├── location-include-in-item-ctxt.stdout │ │ ├── location-proc-macro-observer.rs │ │ ├── location-proc-macro-observer.stdout │ │ ├── multifrontmatter.rs │ │ └── multifrontmatter.stderr │ ├── search.rs │ ├── shell_quoting.rs │ ├── source_replacement.rs │ ├── ssh.rs │ ├── standard_lib.rs │ ├── test.rs │ ├── timings.rs │ ├── tool_paths.rs │ ├── unit_graph.rs │ ├── update.rs │ ├── utils/ │ │ ├── cross_compile.rs │ │ ├── ext.rs │ │ ├── mod.rs │ │ └── tools.rs │ ├── vendor.rs │ ├── verify_project.rs │ ├── version.rs │ ├── warn_on_failure.rs │ ├── warning_override.rs │ ├── weak_dep_features.rs │ ├── workspaces.rs │ └── yank.rs ├── triagebot.toml ├── typos.toml └── windows.manifest.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] build-man = "run --package xtask-build-man --" stale-label = "run --package xtask-stale-label --" bump-check = "run --package xtask-bump-check --" lint-docs = "run --package xtask-lint-docs --" spellcheck = "run --package xtask-spellcheck --" [env] # HACK: Until this is stabilized, `snapbox`s polyfill could get confused # inside of the rust-lang/rust repo because it looks for the furthest-away `Cargo.toml` CARGO_RUSTC_CURRENT_DIR = { value = "", relative = true } ================================================ FILE: .git-blame-ignore-revs ================================================ # Use `git config blame.ignorerevsfile .git-blame-ignore-revs` to make `git blame` ignore the following commits. # refactor: rustfmt for tracing e84cc17db6ac48620e2723f51bce0703f5590060 # Rustfmt with latest nightly. 43c253e69a20321cdfd73d632fa5ec641b1aace3 # reformat with rustfmt df62c36b214449003997d8ee630c7d5c1c08a90c # rustfmt c973a6d0af55785f3072623b4d29127051a84b90 # Apply rustfmt ebd0d58c1c11f98e804c80b3378d00f9e1000a95 # rustfmt 4787dd3acfb32c6d608342bca115094e1ed866e7 # Fix for rustfmt ad44f0e898721f35173b59f32dae344b964dff22 # rustfmt b9554f37abe4f802dd615a550773105a8b2eb386 # Normalize raw string indentation. 6f8c7d5a87526d10e3bbfa4098a1002076386068 # rustfmt dcae5fcf2dc6e040d1cadac14276fd306c058b74 # Rustfmt 0dfdba6f7a7702a7f45d523b028c896084411de8 # Run rustfmt e06a911de616b3f402f1e142b5202445d333f4eb # rustfmt d02f476804ae73d79059e3243ecb4c814f53bea1 # Update for nightly rustfmt. 4b6c26dd16786b416190bd0a36e7646d33b9846a # Run rustfmt d0430dd2b11e6ac004e8f39a712ee6425b81e12d # Run rustfmt c56cb4287fa98a42dafea36d0b88bb4441f63c1f # Run rustfmt c3f8cd3cd2999949c49c8f0e76108ffe7a37bec0 # Run code through rustfmt 648b39e98101f9b6417fc6ebc88fa7f397a5f509 # Run rustfmt ba81441f297c6d9799473a62af021236b8bc9ed8 # Wrap some really long lines. d6d15141f8ef21f1cebeaa4d42606f29e7d00239 # rustfmt for nightly changes. 384c311692e6ff537b51bcf57f804b917a702c9c # Rustfmt lint 9dc70a3dabb291052a4d117b8348c9b461d1b570 # Run rustfmt 3a6cd74434cc1fdd57266740d4827ed162580005 # rustfmt, even if I disagree 189fef1173118ee3be47fa59e22fd2d21bb05bbd # Fix zalgo formatting. 56f8848a51e5db5214ec27bb41c7d99ebac21af8 # Rustfmt adjustments a50be59a70af64b83c07f40bc2faef80a73bc908 # Run rustfmt ebd10526f3526b695872a8ecb381f65aaf74c019 # Rustfmt fixes ac2a4382ee5d927d917df026d2236ff5cd668717 # Rustfmt fixes fc4ee774483dc6b40f1edbd259879dc4cbcf20a5 # Fixed formatting with rustfmt a82de176952f7c244adbd392d444baaafdfc819c # Fix some formatting for some strings. a4e9611453cb4ba3f9bcc34f2851546f57152229 # fix(fingerprint): rustfmt 009876a88af659c10b022ec4c4956f77b983d531 # Make rustfmt happy 2415a2980f455238e9422cc07abcfa4cbec9be6b # run rustfmt dac967ce27d4eeb496dcd98ba91641fbe29df957 # $cargo fmt --all db09895f3c123c36e05175e2c0273bf487068b37 # Reformat everything with rustfmt from stable 1.26 d85336084d8855bfbfd431b85beaa5c094dc5e72 # rustfmt a4947c2b47fd38857c7332d333fda7bcc405de0c # rustfmt 404970f2261e2d6c9ba4eac410065fa6226271d9 # Prettify rustfmted single-line strings b0c181d91ccfe1d27d4c7133049a6bfe5bacf29b # cargo fmt 1e6828485eea0f550ed7be46ef96107b46aeb162 # rustfmt for bin/cargo.rs ef4c09f986c0b1f701c765d894da911c2de44724 # rustfmt cargo_doc.rs 87124d5ade897b3fb38ce5cd0a2f19a9038dd8a7 # run rustfmt on core/source.rs 76fb87e2bab025acaff2d6dd919dde0e328267da # cargo fmt 7a67e88baf0f73196973cadf887bf9a31c13e95f # cargo fmt 7a6ff7f068acde98230d712637e934c58fc84805 # cargo fmt 7c8ee49bffdeeb138db5e22f14abbf88cf99c547 # cargo fmt 72d6c5411a215ce5ab0ff22b1e1e1525883b7c33 # rust fmt ddd5c83b8cf63458bf67ae504bb289ed4e987d38 # cargo fmt 1179e7ef6b88a6940168cbc2e806d48ed20d92ef # cargo fmt 0c07056bfeffee213e21cdf1e48875f59f834282 # cargo fmt c7a79be6f8a8da1d6dee54fe3f5c137d293692b0 # cargo fmt 1839ded4aa5607d3564f02b36e517289ad6ed8e8 # cargo fmt f2b5271a09a13f89a50861ebd71da333836bded3 # Run `cargo fmt --all` 3aa99422ca7808f1bc5621fda3a8f32f27273d9b # cargo fmt 4538ade2d55d7353cfac8a0ce320e3c4a29bc450 # style: cargo fmt 3d042688a851d985013ee8ff64a9caef47b7b495 # run cargo fmt to pass the CI build a4e3b81a55b8c142ab1b84f4f209414a72727e86 # Run 'cargo fmt' 60afaa77335ab464a5b6c913883ad145ae97d750 # Run 'cargo fmt' 511050baefa2ec21c2be8c3eaee2aade1da2c06b # cargo fmt 577968e44828457dfa62fc10ccd29f42a359b746 # cargo fmt d0f7c0ee31c81cd405dc2c163e4ca7638895610c # appease cargo fmt c4922052fc7470264f3df491213f74142ac67ebf # named-profiles: formatting fixes from 'cargo fmt' 29b7e90ba37f311baaf448894bbf04721bed5791 # cargo fmt f39302f432f62e29910c876f766eb8a324cee843 # cargo fmt 6b07c8da63c0896cc31fa6cb8b47a71fe29c88e2 # run cargo fmt --all b48ae60e67096f7cda80a17dfa2a9ff7a916bbf7 # $cargo fmt --all 63a9c7aa6d54ff0580288f1a01795a29b3ea1c75 # Run `cargo fmt` f16efff1509be313f4bed825e7ab974673dd03fc # cargo fmt tests/testsuite/rustflags.rs 3ca98adbbac5752cdef337f5ac803e7843ab601a # Format with `cargo fmt` fecb72464328846dacd0ff8252d105b7818733ab # cargo fmt 9b2295d11fd2816aa3032403a88d7c055cd2cf58 # Run `cargo fmt` 5d201944d7aba8380082dcb9e0c340d92e92850f # Correct formatting with cargo fmt 90954d700ccb71b3df6ecf8ff83891dd3570f887 # Correct formatting with cargo fmt 5c241e102756e340991365d7b376997a9ae95f62 # Fix formatting issues with cargo fmt 5c7979cdf879a6cc9011432098f7ef68ed7f5e37 # cargo fmt a6ad2de0484f1910d42793f3ec73b111403099b7 # cargo fmt b0fbc89c33780ca3e1f2bfeacc67922ee7abe1dc # Rustfmt 2024 1ce80236261a3cd42a95b1f1abcffede87cafef4 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug Report description: Create a report to help us improve labels: ["C-bug", "S-triage"] body: - type: markdown attributes: value: Thanks for filing a 🐛 bug report 😄! - type: textarea id: problem attributes: label: Problem description: > Please provide a clear and concise description of what the bug is, including what currently happens and what you expected to happen. validations: required: true - type: textarea id: steps attributes: label: Steps description: Please list the steps to reproduce the bug. placeholder: | 1. 2. 3. - type: textarea id: possible-solutions attributes: label: Possible Solution(s) description: > Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change. - type: textarea id: notes attributes: label: Notes description: Provide any additional notes that might be helpful. - type: textarea id: version attributes: label: Version description: Please paste the output of running `cargo version --verbose`. render: text ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ contact_links: - name: Question url: https://users.rust-lang.org about: > Got a question about Cargo? Ask the community on the user forum. - name: Inspiring Idea url: https://internals.rust-lang.org/c/tools-and-infrastructure/cargo about: > Need more discussions with your next big idea? Reach out the community on the internals forum. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature Request description: Suggest an idea for enhancing Cargo labels: ["C-feature-request", "S-triage"] body: - type: markdown attributes: value: | Thanks for filing a 🙋 feature request 😄! If the feature request is relatively small and already with a possible solution, this might be the place for you. If you are brewing a big feature that needs feedback from the community, [the internal forum] is the best fit, especially for pre-RFC. You can also talk the idea over with other developers in [#t-cargo Zulip stream]. [the internal forum]: https://internals.rust-lang.org/c/tools-and-infrastructure/cargo/15 [#t-cargo Zulip stream]: https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo - type: textarea id: problem attributes: label: Problem description: > Please provide a clear description of your use case and the problem this feature request is trying to solve. validations: required: true - type: textarea id: solution attributes: label: Proposed Solution description: > Please provide a clear and concise description of what you want to happen. - type: textarea id: notes attributes: label: Notes description: Provide any additional context or information that might be helpful. ================================================ FILE: .github/ISSUE_TEMPLATE/new_lint.yml ================================================ name: New lint suggestion description: Suggest a new Cargo lint. labels: ["A-new-lint", "S-triage"] body: - type: markdown attributes: value: Thank you for your lint idea! - type: textarea id: what attributes: label: What it does description: What does this lint do? validations: required: true - type: textarea id: advantage attributes: label: Advantage description: > What is the advantage of the recommended code over the original code? placeholder: | - Remove bounds check inserted by ... - Remove the need to duplicate/store ... - Remove typo ... - type: textarea id: drawbacks attributes: label: Drawbacks description: What might be possible drawbacks of such a lint? - type: textarea id: example attributes: label: Example description: > Include a short example showing when the lint should trigger together with the improved code. value: | ```toml ``` Could be written as: ```toml ``` validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/tracking_issue.yml ================================================ name: Tracking Issue description: A tracking issue for an accepted feature or RFC in Cargo. title: "Tracking Issue for _FEATURE_NAME_" labels: ["C-tracking-issue"] body: - type: markdown attributes: value: > Thank you for creating a tracking issue! Tracking issues are for tracking an accepted feature or RFC from implementation to stabilization. Please do not file a tracking issue until the feature or RFC has been approved. - type: textarea id: summary attributes: label: Summary description: Please provide a very brief summary of the feature. value: | RFC: [#NNNN](https://github.com/rust-lang/rfcs/pull/NNNN) Original issue: #NNNN Implementation: #NNNN Documentation: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#my-feature Please enter a short, one-sentence description here. validations: required: true - type: textarea id: unresolved attributes: label: Unresolved Issues description: List issues that have not yet been resolved. placeholder: | * [ ] Make a list of any known implementation or design issues. - type: textarea id: future attributes: label: Future Extensions description: > An optional section where you can mention where the feature may be extended in the future, but is explicitly not intended to address. - type: textarea id: about attributes: label: About tracking issues description: Please include this notice in the issue. value: | Tracking issues are used to record the overall progress of implementation. They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions. A tracking issue is however *not* meant for large scale discussion, questions, or bug reports about a feature. Instead, open a dedicated issue for the specific matter and add the relevant feature gate label. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ _Thanks for the pull request 🎉!_ _Please read the contribution guide: ._ ### What does this PR try to resolve? _Explain the motivation behind this change._ _A clear overview along with an in-depth explanation are helpful._ ### How to test and review this PR? _Demonstrate how you test this change and guide reviewers through your PR._ _With a smooth review process, a pull request usually gets reviewed quicker._ ================================================ FILE: .github/renovate.json5 ================================================ { schedule: [ 'before 5am on the first day of the month', ], semanticCommits: 'enabled', configMigration: true, dependencyDashboard: true, ignorePaths: [ '**/tests/**', ], // Ignore curl-sys, for now // https://github.com/rust-lang/cargo/issues/16357 ignoreDeps: ['curl-sys'], customManagers: [ { customType: 'regex', managerFilePatterns: [ '/Cargo.toml$/', ], matchStrings: [ '\\bMSRV:1\\b.*?(?\\d+\\.\\d+(\\.\\d+)?)', '(?\\d+\\.\\d+(\\.\\d+)?).*?\\bMSRV:1\\b', ], depNameTemplate: 'MSRV:1', // Support 1 version of rustc packageNameTemplate: 'rust-lang/rust', datasourceTemplate: 'github-releases', }, { customType: 'regex', managerFilePatterns: [ '/Cargo.toml$/', ], matchStrings: [ '\\bMSRV:3\\b.*?(?\\d+\\.\\d+(\\.\\d+)?)', '(?\\d+\\.\\d+(\\.\\d+)?).*?\\bMSRV:3\\b', ], depNameTemplate: 'MSRV:3', // Support 3 versions of rustc packageNameTemplate: 'rust-lang/rust', datasourceTemplate: 'github-releases', }, { customType: 'regex', managerFilePatterns: [ '/^.github.workflows.main.yml$/', ], matchStrings: [ 'cargo-semver-checks.releases.download.v(?\\d+\\.\\d+(\\.\\d+)?)', ], depNameTemplate: 'cargo-semver-checks', packageNameTemplate: 'obi1kenobi/cargo-semver-checks', datasourceTemplate: 'github-releases', }, ], packageRules: [ { commitMessageTopic: 'MSRV (1 version)', matchManagers: [ 'custom.regex', ], matchDepNames: [ 'MSRV:1', ], extractVersion: '^(?\\d+\\.\\d+)', // Drop the patch version schedule: [ '* * * * *', ], groupName: 'msrv', }, { commitMessageTopic: 'MSRV (3 versions)', matchManagers: [ 'custom.regex', ], matchDepNames: [ 'MSRV:3', ], extractVersion: '^(?\\d+\\.\\d+)', // Drop the patch version schedule: [ '* * * * *', ], minimumReleaseAge: '85 days', // 2 releases back * 6 weeks per release * 7 days per week + 1 internalChecksFilter: 'strict', groupName: 'msrv', }, { commitMessageTopic: 'cargo-semver-checks', matchManagers: [ 'custom.regex', ], matchDepNames: [ 'cargo-semver-checks', ], extractVersion: '^v(?\\d+\\.\\d+\\.\\d+)', schedule: [ '* * * * *', ], internalChecksFilter: 'strict', }, // Goals: // - Rollup safe upgrades to reduce CI runner load // - Have lockfile and manifest in-sync (implicit rules) { matchManagers: [ 'cargo', ], matchCurrentVersion: '>=0.1.0', matchUpdateTypes: [ 'patch', ], automerge: false, groupName: 'compatible', }, { matchManagers: [ 'cargo', ], matchCurrentVersion: '>=1.0.0', matchUpdateTypes: [ 'minor', ], automerge: false, groupName: 'compatible', }, ], } ================================================ FILE: .github/workflows/audit.yml ================================================ name: Security audit permissions: contents: read on: pull_request: paths: - '**/Cargo.toml' - '**/Cargo.lock' push: branches: - master jobs: cargo_deny: runs-on: ubuntu-latest strategy: matrix: checks: - advisories - bans licenses sources steps: - uses: actions/checkout@v5 - uses: EmbarkStudios/cargo-deny-action@v2 # Prevent sudden announcement of a new advisory from failing ci: continue-on-error: ${{ matrix.checks == 'advisories' }} with: command: check ${{ matrix.checks }} rust-version: stable ================================================ FILE: .github/workflows/contrib.yml ================================================ name: Contrib Deploy on: push: branches: - master concurrency: cancel-in-progress: false group: "gh-pages" permissions: contents: read env: MDBOOK_VERSION: 0.5.1 jobs: deploy: permissions: contents: write # for Git to git push runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install mdbook run: | mkdir mdbook curl -Lf https://github.com/rust-lang/mdBook/releases/download/v${MDBOOK_VERSION}/mdbook-v${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook echo `pwd`/mdbook >> $GITHUB_PATH - name: Deploy docs run: | GENERATE_PY="$(pwd)/ci/generate.py" cd src/doc/contrib mdbook build # Override previous ref to avoid keeping history. git worktree add --orphan -B gh-pages gh-pages git config user.name "Deploy from CI" git config user.email "" cd gh-pages mv ../book contrib git add contrib # Generate HTML for link redirections. python3 "$GENERATE_PY" git add *.html # WARN: The CNAME file is for GitHub to redirect requests to the custom domain. # Missing this may entail security hazard and domain takeover. # See git add CNAME git commit -m "Deploy $GITHUB_SHA to gh-pages" git push origin +gh-pages ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: merge_group: pull_request: branches: - "**" defaults: run: shell: bash permissions: contents: read concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true env: MDBOOK_VERSION: 0.5.1 jobs: conclusion: needs: - build_std - clippy - msrv - docs - lint-docs - lockfile - resolver - report-timings - rustfmt - schema - spellcheck - test - test_gitoxide permissions: contents: none # We need to ensure this job does *not* get skipped if its dependencies fail, # because a skipped job is considered a success by GitHub. So we have to # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run # when the workflow is canceled manually. # # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! if: ${{ !cancelled() }} runs-on: ubuntu-latest steps: # Manually check the status of all dependencies. `if: failure()` does not work. - name: Conclusion run: | # Print the dependent jobs to see them in the CI log jq -C <<< '${{ toJson(needs) }}' # Check if all jobs that we depend on (in the needs array) were successful. jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' # Check Code style quickly by running `rustfmt` over all code rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update stable && rustup default stable - run: rustup component add rustfmt - run: cargo fmt --all --check # Ensure there are no clippy warnings clippy: strategy: matrix: include: - name: Linux x86_64 os: ubuntu-latest - name: macOS aarch64 os: macos-14 - name: Windows x86_64 MSVC os: windows-latest name: Clippy ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 - run: rustup update stable && rustup default stable - run: rustup component add clippy - run: cargo clippy --workspace --all-targets --no-deps -- -D warnings stale-label: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update stable && rustup default stable - run: cargo stale-label lint-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update stable && rustup default stable - run: cargo lint-docs --check # Ensure Cargo.lock is up-to-date lockfile: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update stable && rustup default stable - run: cargo update -p cargo --locked check-version-bump: runs-on: ubuntu-latest if: github.repository_owner == 'rust-lang' env: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha != '' && github.event.pull_request.head.sha || github.sha }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - run: rustup update stable && rustup default stable - name: Install cargo-semver-checks run: | mkdir installed-bins curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.47.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \ | tar -xz --directory=./installed-bins echo `pwd`/installed-bins >> $GITHUB_PATH - run: ci/validate-version-bump.sh test: runs-on: ${{ matrix.os }} env: CARGO_PROFILE_DEV_DEBUG: 1 CARGO_PROFILE_TEST_DEBUG: 1 CARGO_INCREMENTAL: 0 CARGO_PUBLIC_NETWORK_TESTS: 1 # Workaround for https://github.com/rust-lang/rustup/issues/3036 RUSTUP_WINDOWS_PATH_ADD_BIN: 0 strategy: matrix: include: - name: Linux x86_64 stable os: ubuntu-latest rust: stable other: i686-unknown-linux-gnu - name: Linux x86_64 beta os: ubuntu-latest rust: beta other: i686-unknown-linux-gnu - name: Linux x86_64 nightly os: ubuntu-latest rust: nightly other: i686-unknown-linux-gnu - name: Linux aarch64 stable os: ubuntu-24.04-arm rust: stable other: TODO # cross-compile tests are disabled, this shouldn't matter. - name: Linux aarch64 nightly os: ubuntu-24.04-arm rust: nightly other: TODO # cross-compile tests are disabled, this shouldn't matter. - name: macOS aarch64 stable os: macos-14 rust: stable other: x86_64-apple-darwin - name: macOS aarch64 nightly os: macos-14 rust: nightly other: x86_64-apple-darwin - name: Windows x86_64 MSVC stable os: windows-latest rust: stable-msvc other: i686-pc-windows-msvc - name: Windows x86_64 MSVC nightly os: windows-latest rust: nightly-msvc other: i686-pc-windows-msvc - name: Windows aarch64 MSVC stable os: windows-11-arm rust: stable-msvc other: i686-pc-windows-msvc - name: Windows aarch64 MSVC nightly os: windows-11-arm rust: nightly-msvc other: i686-pc-windows-msvc - name: Windows x86_64 gnu nightly # runs out of space while trying to link the test suite os: windows-latest rust: nightly-gnu other: i686-pc-windows-gnu name: Tests ${{ matrix.name }} steps: - uses: actions/checkout@v5 - name: Dump Environment run: ci/dump-environment.sh # Some tests require stable. Make sure it is set to the most recent stable # so that we can predictably handle updates if necessary (and not randomly # when GitHub updates its image). - run: rustup update --no-self-update stable - run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - run: rustup target add ${{ matrix.other }} if: matrix.os != 'ubuntu-24.04-arm' # cross-compile tests are disabled on ARM machines - run: rustup target add wasm32-unknown-unknown - run: rustup target add aarch64-unknown-none # need this for build-std mock tests if: startsWith(matrix.rust, 'nightly') - run: rustup component add rustc-dev llvm-tools-preview rust-docs if: startsWith(matrix.rust, 'nightly') - run: sudo apt update -y && sudo apt install lldb gcc-multilib libsecret-1-0 libsecret-1-dev -y if: matrix.os == 'ubuntu-latest' - run: rustup component add rustfmt || echo "rustfmt not available" - name: Add Windows debuggers bin to PATH shell: pwsh run: Add-Content $env:GITHUB_PATH "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64" if: matrix.os == 'windows-latest' - name: Add Windows debuggers bin to PATH shell: pwsh run: Add-Content $env:GITHUB_PATH "C:\Program Files (x86)\Windows Kits\10\Debuggers\arm64" if: matrix.os == 'windows-11-arm' - name: Configure extra test environment run: echo CARGO_CONTAINER_TESTS=1 >> $GITHUB_ENV if: matrix.os == 'ubuntu-latest' - run: cargo test -p cargo - name: Clear intermediate test output run: ci/clean-test-output.sh - name: gitoxide tests (all git-related tests) run: cargo test -p cargo git env: __CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2: 1 # The testsuite generates a huge amount of data, and fetch-smoke-test was # running out of disk space. - name: Clear test output run: ci/clean-test-output.sh # This only tests `cargo fix` because fix-proxy-mode is one of the most # complicated subprocess management in Cargo. - name: Check operability of rustc invocation with argfile run: 'cargo test -p cargo --test testsuite -- fix::' env: __CARGO_TEST_FORCE_ARGFILE: 1 - run: cargo test --workspace --exclude cargo --exclude benchsuite --exclude resolver-tests - name: Check benchmarks run: | # This only tests one benchmark since it can take over 10 minutes to # download all workspaces. cargo test -p benchsuite --all-targets -- cargo cargo check -p capture # The testsuite generates a huge amount of data, and fetch-smoke-test was # running out of disk space. - name: Clear benchmark output run: ci/clean-test-output.sh - name: Fetch smoke test run: ci/fetch-smoke-test.sh schema: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update stable && rustup default stable - run: cargo test -p cargo-util-schemas -F unstable-schema resolver: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update stable && rustup default stable - run: cargo test -p resolver-tests test_gitoxide: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update --no-self-update stable && rustup default stable - run: rustup target add i686-unknown-linux-gnu - run: rustup target add wasm32-unknown-unknown - run: sudo apt update -y && sudo apt install gcc-multilib libsecret-1-0 libsecret-1-dev -y - run: rustup component add rustfmt || echo "rustfmt not available" - run: cargo test -p cargo env: __CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2: 1 build_std: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update nightly && rustup default nightly - run: rustup component add rust-src - run: cargo build - run: cargo test -p cargo --test build-std env: CARGO_RUN_BUILD_STD_TESTS: 1 docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update nightly && rustup default nightly - run: rustup update stable - run: rustup component add rust-docs - run: rustup component add rustfmt --toolchain stable - run: ci/validate-man.sh # This requires rustfmt, use stable. - name: Run semver-check run: cargo +stable run -p semver-check - name: Ensure intradoc links are valid run: cargo doc --workspace --document-private-items --no-deps env: RUSTDOCFLAGS: -D warnings - name: Install mdbook run: | mkdir mdbook curl -Lf https://github.com/rust-lang/mdBook/releases/download/v${MDBOOK_VERSION}/mdbook-v${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook echo `pwd`/mdbook >> $GITHUB_PATH - run: cd src/doc && mdbook build --dest-dir ../../target/doc - name: Run linkchecker.sh run: | cd target curl -sSLO https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh sh linkcheck.sh --all --path ../src/doc cargo msrv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: taiki-e/install-action@cargo-hack - run: cargo hack check --all-targets --rust-version --workspace --ignore-private --locked spellcheck: name: Spell Check with Typos runs-on: ubuntu-latest steps: - name: Checkout Actions Repository uses: actions/checkout@v5 - name: Spell Check Repo uses: crate-ci/typos@v1.44.0 report-timings: name: Timing HTML report runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: rustup update nightly && rustup default nightly - run: cargo build - name: Generate timing report for rustfix run: | cargo run -- build -p rustfix -Zbuild-analysis -Zsection-timings --config build.analysis.enabled=true --config 'build.build-dir="tmp"' cargo run -- report timings -Zbuild-analysis - uses: actions/upload-artifact@v6 with: name: timing-report path: target/cargo-timings/*.html if-no-files-found: error ================================================ FILE: .github/workflows/release.yml ================================================ # Publish Cargo to crates.io whenever a new tag is pushed. Tags are pushed by # the Rust release process (https://github.com/rust-lang/promote-release), # which will cause this workflow to run. name: Release on: push: tags: - "0.*" # Prevent multiple releases from starting at the same time. concurrency: group: release jobs: crates-io: name: Publish on crates.io runs-on: ubuntu-latest permissions: contents: read # Gain access to the crates.io publishing token. environment: name: release steps: - name: Checkout the source code uses: actions/checkout@v5 - name: Publish Cargo to crates.io run: ./publish.py env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} ================================================ FILE: .gitignore ================================================ /target /Cargo.lock /config.stamp /Makefile /config.mk /src/doc/build /src/etc/*.pyc /src/registry/target rustc __pycache__ .idea/ .vscode/ *.iml *.swp ================================================ FILE: .ignore ================================================ # Output generated from src/doc/man # # The goal is to help people find the right file to edit src/doc/man/generated_txt src/doc/src/commands/* src/etc/man !src/doc/src/commands/build-commands.md !src/doc/src/commands/cargo-clippy.md !src/doc/src/commands/cargo-fmt.md !src/doc/src/commands/cargo-miri.md !src/doc/src/commands/general-commands.md !src/doc/src/commands/index.md !src/doc/src/commands/manifest-commands.md !src/doc/src/commands/package-commands.md !src/doc/src/commands/publishing-commands.md !src/doc/src/commands/report-commands.md # Snapshots of HTML reports and log files are just too large tests/testsuite/**/*.jsonl tests/testsuite/**/*.html ================================================ FILE: CHANGELOG.md ================================================ # Changelog The changelog has moved to the [Cargo Book](https://doc.rust-lang.org/nightly/cargo/CHANGELOG.html). ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # The Rust Code of Conduct The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html). ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Cargo Contributing documentation has moved to the **[Cargo Contributor Guide]**. [Cargo Contributor Guide]: https://rust-lang.github.io/cargo/contrib/ ## Before hacking on Cargo We encourage people to discuss their design before hacking on code. Typically, you [file an issue] or start a thread on the [internals forum] before submitting a pull request. Please read [the process] of how features and bugs are managed in Cargo. **Only issues that have been explicitly marked as [accepted] will be reviewed.** [internals forum]: https://internals.rust-lang.org/c/tools-and-infrastructure/cargo [file an issue]: https://github.com/rust-lang/cargo/issues [the process]: https://doc.crates.io/contrib/process/index.html [accepted]: https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AS-accepted ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "crates/*", "credential/*", "benches/benchsuite", "benches/capture", ] exclude = [ "target/", # exclude bench testing ] [workspace.package] rust-version = "1.92" # MSRV:3 edition = "2024" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/cargo" [workspace.dependencies] annotate-snippets = { version = "0.12.12", features = ["simd"] } anstream = "1.0.0" anstyle = "1.0.13" anstyle-hyperlink = "1.0.1" anstyle-progress = "0.1.0" anyhow = "1.0.102" base64 = "0.22.1" blake3 = "1.8.3" build-rs = { version = "0.3.4", path = "crates/build-rs" } cargo = { path = "" } cargo-credential = { version = "0.4.10", path = "credential/cargo-credential" } cargo-credential-libsecret = { version = "0.5.6", path = "credential/cargo-credential-libsecret" } cargo-credential-macos-keychain = { version = "0.4.21", path = "credential/cargo-credential-macos-keychain" } cargo-credential-wincred = { version = "0.4.21", path = "credential/cargo-credential-wincred" } cargo-platform = { path = "crates/cargo-platform", version = "0.3.3" } cargo-test-macro = { version = "0.4.10", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.11.0", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.28", path = "crates/cargo-util" } cargo-util-schemas = { version = "0.13.0", path = "crates/cargo-util-schemas" } cargo_metadata = "0.23.1" clap = "4.5.60" clap_complete = { version = "4.5.66", features = ["unstable-dynamic"] } color-print = "0.3.7" core-foundation = { version = "0.10.1", features = ["mac_os_10_7_support"] } crates-io = { version = "0.40.18", path = "crates/crates-io" } criterion = { version = "0.8.2", features = ["html_reports"] } curl = "0.4.49" # Do not upgrade curl-sys past 0.4.83 # https://github.com/rust-lang/cargo/issues/16357 curl-sys = "=0.4.83" filetime = "0.2.27" flate2 = { version = "1.1.9", default-features = false, features = ["zlib-rs"] } git2 = "0.20.4" git2-curl = "0.21.0" # When updating this, also see if `gix-transport` further down needs updating or some auth-related tests will fail. gix = { version = "0.80.0", default-features = false, features = ["progress-tree", "parallel", "dirwalk", "status"] } gix-transport = "0.55.0" glob = "0.3.3" handlebars = { version = "6.4.0", features = ["dir_source"] } heck = "0.5.0" hex = "0.4.3" hmac = "0.12.1" home = "0.5.12" http-auth = { version = "0.1.10", default-features = false } ignore = "0.4.25" im-rc = "15.1.0" indexmap = "2.13.0" itertools = "0.14.0" jiff = { version = "0.2.22", default-features = false, features = [ "std" ] } jobserver = "0.1.34" libc = "0.2.180" libgit2-sys = "0.18.3" libloading = "0.9.0" memchr = "2.8.0" memfd = "0.6.5" miow = "0.6.1" opener = "0.8.4" openssl = "0.10.75" # Pinned due to ppc64 ELFv1/v2 ABI issue in 3.5.5 # https://github.com/openssl/openssl/issues/29815 openssl-src = "=300.5.4" os_info = { version = "3.14.0", default-features = false } pasetors = { version = "0.7.8", features = ["v3", "paserk", "std", "serde"] } pathdiff = "0.2.3" percent-encoding = "2.3.2" proptest = "1.10.0" pulldown-cmark = { version = "0.13.1", default-features = false, features = ["html"] } rand = "0.10.0" regex = "1.12.3" rusqlite = { version = "0.38.0", features = ["bundled"] } rustc-hash = "2.1.1" rustc-stable-hash = "0.1.2" rustfix = { version = "0.9.5", path = "crates/rustfix" } same-file = "1.0.6" schemars = "1.2.1" security-framework = "3.7.0" semver = { version = "1.0.27", features = ["serde"] } serde = "1.0.228" serde_core = "1.0.228" serde-untagged = "0.1.9" serde-value = "0.7.0" serde_ignored = "0.1.14" serde_json = "1.0.149" sha1 = "0.10.6" sha2 = "0.10.9" shell-escape = "0.1.5" similar = "2.7.0" supports-hyperlinks = "3.2.0" supports-unicode = "3.0.0" snapbox = { version = "1.0.0", features = ["diff", "dir", "term-svg", "regex", "json"] } tar = { version = "0.4.45", default-features = false } tempfile = "3.25.0" thiserror = "2.0.18" time = { version = "0.3.47", features = ["parsing", "formatting", "serde"] } toml = { version = "1.0.3", default-features = false } toml_edit = { version = "0.25.3", features = ["serde"] } tracing = { version = "0.1.44", default-features = false, features = ["std"] } # be compatible with rustc_log: https://github.com/rust-lang/rust/blob/e51e98dde6a/compiler/rustc_log/Cargo.toml#L9 tracing-chrome = "0.7.2" tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } unicase = "2.9.0" unicode-ident = "1.0.24" unicode-width = "0.2.2" url = "2.5.8" varisat = "0.2.2" walkdir = "2.5.0" windows-sys = "0.61" winnow = "0.7.14" [workspace.lints.rust] rust_2018_idioms = "warn" # TODO: could this be removed? [workspace.lints.rustdoc] private_intra_doc_links = "allow" [workspace.lints.clippy] all = { level = "allow", priority = -2 } correctness = { level = "warn", priority = -1 } dbg_macro = "warn" disallowed_methods = "warn" print_stderr = "warn" print_stdout = "warn" self_named_module_files = "warn" [package] name = "cargo" version = "0.97.0" edition.workspace = true license.workspace = true rust-version = "1.94" # MSRV:1 homepage = "https://doc.rust-lang.org/cargo/index.html" repository.workspace = true documentation = "https://docs.rs/cargo" description = """ Cargo, a package manager for Rust. """ [lib] name = "cargo" path = "src/cargo/lib.rs" [dependencies] annotate-snippets.workspace = true anstream.workspace = true anstyle.workspace = true anstyle-hyperlink = { workspace = true, features = ["file"] } anstyle-progress.workspace = true anyhow.workspace = true base64.workspace = true blake3.workspace = true cargo-credential.workspace = true cargo-platform.workspace = true cargo-util-schemas.workspace = true cargo-util.workspace = true clap = { workspace = true, features = ["wrap_help"] } clap_complete.workspace = true color-print.workspace = true crates-io.workspace = true curl = { workspace = true, features = ["http2"] } curl-sys.workspace = true filetime.workspace = true flate2.workspace = true git2.workspace = true git2-curl.workspace = true gix.workspace = true glob.workspace = true heck.workspace = true hex.workspace = true hmac.workspace = true home.workspace = true http-auth.workspace = true ignore.workspace = true im-rc.workspace = true indexmap.workspace = true itertools.workspace = true jiff = { workspace = true, features = ["serde", "std"] } jobserver.workspace = true libgit2-sys.workspace = true memchr.workspace = true opener.workspace = true os_info.workspace = true pasetors.workspace = true pathdiff.workspace = true rand.workspace = true regex.workspace = true rusqlite = { workspace = true, features = ["fallible_uint"] } rustc-hash.workspace = true rustc-stable-hash.workspace = true rustfix.workspace = true same-file.workspace = true semver.workspace = true serde = { workspace = true, features = ["derive"] } serde-untagged.workspace = true serde_ignored.workspace = true serde_json = { workspace = true, features = ["raw_value"] } sha1.workspace = true shell-escape.workspace = true supports-hyperlinks.workspace = true supports-unicode.workspace = true tar.workspace = true tempfile.workspace = true thiserror.workspace = true time.workspace = true toml = { workspace = true, features = ["std", "serde", "parse", "display", "preserve_order"] } toml_edit.workspace = true tracing = { workspace = true, features = ["attributes"] } tracing-subscriber.workspace = true unicase.workspace = true unicode-width.workspace = true unicode-ident.workspace = true url.workspace = true walkdir.workspace = true winnow.workspace = true [target.'cfg(target_has_atomic = "64")'.dependencies] tracing-chrome.workspace = true [target.'cfg(unix)'.dependencies] libc.workspace = true [target.'cfg(target_os = "linux")'.dependencies] cargo-credential-libsecret.workspace = true [target.'cfg(target_os = "macos")'.dependencies] cargo-credential-macos-keychain.workspace = true [target.'cfg(not(windows))'.dependencies] openssl = { workspace = true, optional = true } openssl-src = { workspace = true, optional = true } [target.'cfg(windows)'.dependencies] cargo-credential-wincred.workspace = true [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ "Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_JobObjects", "Win32_System_Threading", ] [dev-dependencies] annotate-snippets = { workspace = true, features = ["testing-colors"] } cargo-test-support.workspace = true gix = { workspace = true, features = ["revision"] } # When building Cargo for tests, a safety-measure in `gix` needs to be disabled # to allow sending credentials over HTTP connections. gix-transport = { workspace = true, features = ["http-client-insecure-credentials"] } same-file.workspace = true snapbox.workspace = true [target.'cfg(target_os = "linux")'.dev-dependencies] memfd.workspace = true [build-dependencies] flate2.workspace = true tar.workspace = true [[bin]] name = "cargo" test = false doc = false [features] default = ["http-transport-curl"] vendored-openssl = ["openssl/vendored"] vendored-libgit2 = ["libgit2-sys/vendored"] # This is primarily used by rust-lang/rust distributing cargo the executable. all-static = ['vendored-openssl', 'curl/static-curl', 'curl/force-system-lib-on-osx', 'vendored-libgit2'] # Exactly one of 'http-transport-curl' or 'http-transport-reqwest' must be enabled # when using Cargo as a library. By default, it is 'http-transport-curl'. http-transport-curl = ["gix/blocking-http-transport-curl"] http-transport-reqwest = ["gix/blocking-http-transport-reqwest"] [lints] workspace = true ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/LICENSE-2.0 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: LICENSE-THIRD-PARTY ================================================ The Cargo source code itself does not bundle any third party libraries, but it depends on a number of libraries which carry their own copyright notices and license terms. These libraries are normally all linked static into the binary distributions of Cargo: * OpenSSL - https://www.openssl.org/source/license.html Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (https://www.openssl.org/)" 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org. 5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project. 6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (https://www.openssl.org/)" THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================== This product includes cryptographic software written by Eric Young (eay@cryptsoft.com). This product includes software written by Tim Hudson (tjh@cryptsoft.com). --- Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved. This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). The implementation was written so as to conform with Netscapes SSL. This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com). Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed. If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: "This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-). 4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The licence and distribution terms for any publically available version or derivative of this code cannot be changed. i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.] * libgit2 - https://github.com/libgit2/libgit2/blob/master/COPYING libgit2 is Copyright (C) the libgit2 contributors, unless otherwise stated. See the AUTHORS file for details. Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. ---------------------------------------------------------------------- LINKING EXCEPTION In addition to the permissions in the GNU General Public License, the authors give you unlimited permission to link the compiled version of this library into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combined executable.) ---------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ---------------------------------------------------------------------- The bundled ZLib code is licensed under the ZLib license: Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly Mark Adler jloup@gzip.org madler@alumni.caltech.edu ---------------------------------------------------------------------- The Clar framework is licensed under the MIT license: Copyright (C) 2011 by Vicent Marti Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------------------- The regex library (deps/regex/) is licensed under the GNU LGPL GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ---------------------------------------------------------------------- * libssh2 - https://www.libssh2.org/license.html Copyright (c) 2004-2007 Sara Golemon Copyright (c) 2005,2006 Mikhail Gusarov Copyright (c) 2006-2007 The Written Word, Inc. Copyright (c) 2007 Eli Fant Copyright (c) 2009 Daniel Stenberg Copyright (C) 2008, 2009 Simon Josefsson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the copyright holder nor the names of any other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * libcurl - https://curl.haxx.se/docs/copyright.html COPYRIGHT AND PERMISSION NOTICE Copyright (c) 1996 - 2014, Daniel Stenberg, daniel@haxx.se. All rights reserved. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. * flate2-rs - https://github.com/alexcrichton/flate2-rs/blob/master/LICENSE-MIT * link-config - https://github.com/alexcrichton/link-config/blob/master/LICENSE-MIT * openssl-static-sys - https://github.com/alexcrichton/openssl-static-sys/blob/master/LICENSE-MIT * toml-rs - https://github.com/alexcrichton/toml-rs/blob/master/LICENSE-MIT * libssh2-static-sys - https://github.com/alexcrichton/libssh2-static-sys/blob/master/LICENSE-MIT * git2-rs - https://github.com/alexcrichton/git2-rs/blob/master/LICENSE-MIT * tar-rs - https://github.com/alexcrichton/tar-rs/blob/master/LICENSE-MIT Copyright (c) 2014 Alex Crichton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * glob - https://github.com/rust-lang/glob/blob/master/LICENSE-MIT * semver - https://github.com/rust-lang/semver/blob/master/LICENSE-MIT Copyright (c) 2014 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * rust-url - https://github.com/servo/rust-url/blob/master/LICENSE-MIT Copyright (c) 2006-2009 Graydon Hoare Copyright (c) 2009-2013 Mozilla Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * rust-encoding - https://github.com/lifthrasiir/rust-encoding/blob/master/LICENSE.txt The MIT License (MIT) Copyright (c) 2013, Kang Seonghoon. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * curl-rust - https://github.com/carllerche/curl-rust/blob/master/LICENSE Copyright (c) 2014 Carl Lerche Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * docopt.rs - https://github.com/docopt/docopt.rs/blob/master/UNLICENSE This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================ # Cargo Cargo downloads your Rust project’s dependencies and compiles your project. **To start using Cargo**, learn more at [The Cargo Book]. **To start developing Cargo itself**, read the [Cargo Contributor Guide]. [The Cargo Book]: https://doc.rust-lang.org/cargo/ [Cargo Contributor Guide]: https://rust-lang.github.io/cargo/contrib/ > The Cargo binary distributed through with Rust is maintained by the Cargo > team for use by the wider ecosystem. > For all other uses of this crate (as a binary or library) this is maintained > by the Cargo team, primarily for use by Cargo and not intended for external > use (except as a transitive dependency). This crate may make major changes to > its APIs. ## Code Status [![CI](https://github.com/rust-lang/cargo/actions/workflows/main.yml/badge.svg?branch=auto-cargo)](https://github.com/rust-lang/cargo/actions/workflows/main.yml) Code documentation: ## Compiling from Source ### Requirements Cargo requires the following tools and packages to build: * `cargo` and `rustc` * A C compiler [for your platform](https://github.com/rust-lang/cc-rs#compile-time-requirements) * `git` (to clone this repository) **Other requirements:** The following are optional based on your platform and needs. * `pkg-config` — This is used to help locate system packages, such as `libssl` headers/libraries. This may not be required in all cases, such as using vendored OpenSSL, or on Windows. * OpenSSL — Only needed on Unix-like systems and only if the `vendored-openssl` Cargo feature is not used. This requires the development headers, which can be obtained from the `libssl-dev` package on Ubuntu or `openssl-devel` with apk or yum or the `openssl` package from Homebrew on macOS. If using the `vendored-openssl` Cargo feature, then a static copy of OpenSSL will be built from source instead of using the system OpenSSL. This may require additional tools such as `perl` and `make`. On macOS, common installation directories from Homebrew, MacPorts, or pkgsrc will be checked. Otherwise it will fall back to `pkg-config`. On Windows, the system-provided Schannel will be used instead. LibreSSL is also supported. **Optional system libraries:** The build will automatically use vendored versions of the following libraries. However, if they are provided by the system and can be found with `pkg-config`, then the system libraries will be used instead: * [`libcurl`](https://curl.se/libcurl/) — Used for network transfers. * [`libgit2`](https://libgit2.org/) — Used for fetching git dependencies. * [`libssh2`](https://www.libssh2.org/) — Used for SSH access to git repositories. * [`libz`](https://zlib.net/) (AKA zlib) — Used by the above C libraries for data compression. (Rust code uses [`zlib-rs`](https://github.com/trifectatechfoundation/zlib-rs) instead.) It is recommended to use the vendored versions as they are the versions that are tested to work with Cargo. ### Compiling First, you'll want to check out this repository ``` git clone https://github.com/rust-lang/cargo.git cd cargo ``` With `cargo` already installed, you can simply run: ``` cargo build --release ``` ## Adding new subcommands to Cargo Cargo is designed to be extensible with new subcommands without having to modify Cargo itself. See [the Wiki page][third-party-subcommands] for more details and a list of known community-developed subcommands. [third-party-subcommands]: https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands ## Releases Cargo releases coincide with Rust releases. High level release notes are available as part of [Rust's release notes][rel]. Detailed release notes are available in the [changelog]. [rel]: https://github.com/rust-lang/rust/blob/master/RELEASES.md [changelog]: https://doc.rust-lang.org/nightly/cargo/CHANGELOG.html ## Reporting issues Found a bug? We'd love to know about it! Please report all issues on the GitHub [issue tracker][issues]. [issues]: https://github.com/rust-lang/cargo/issues ## Contributing See the **[Cargo Contributor Guide]** for a complete introduction to contributing to Cargo. ## License Cargo is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. ### Third party software This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (https://www.openssl.org/). In binary form, this product includes software that is licensed under the terms of the GNU General Public License, version 2, with a linking exception, which can be obtained from the [upstream repository][1]. See [LICENSE-THIRD-PARTY](LICENSE-THIRD-PARTY) for details. [1]: https://github.com/libgit2/libgit2 ================================================ FILE: benches/README.md ================================================ # Cargo Benchmarking This directory contains some benchmarks for cargo itself. This uses [Criterion] for running benchmarks. It is recommended to read the Criterion book to get familiar with how to use it. A basic usage would be: ```sh cd benches/benchsuite cargo bench ``` However, running all benchmarks would take many minutes, so in most cases it is recommended to just run the benchmarks relevant to whatever section of code you are working on. ## Benchmarks There are several different kinds of benchmarks in the `benchsuite/benches` directory: * `global_cache_tracker` — Benchmarks saving data to the global cache tracker database using samples of real-world data. * `resolve` — Benchmarks the resolver against simulations of real-world workspaces. * `workspace_initialization` — Benchmarks initialization of a workspace against simulations of real-world workspaces. ### Resolve benchmarks The resolve benchmarks involve downloading the index and benchmarking against some real-world and artificial workspaces located in the [`workspaces`](workspaces) directory. **Beware** that the initial download can take a fairly long amount of time (10 minutes minimum on an extremely fast network) and require significant disk space (around 4.5GB). The benchsuite will cache the index and downloaded crates in the `target/tmp/bench` directory, so subsequent runs should be faster. You can (and probably should) specify individual benchmarks to run to narrow it down to a more reasonable set, for example: ```sh cargo bench -p benchsuite --bench resolve -- resolve_ws/rust ``` This will only download what's necessary for the rust-lang/rust workspace (which is about 330MB) and run the benchmarks against it (which should take about a minute). To get a list of all the benchmarks, run: ```sh cargo bench -p benchsuite --bench resolve -- --list ``` ### Global cache tracker The `global_cache_tracker` benchmark tests saving data to the global cache tracker database using samples of real-world data. This benchmark should run relatively quickly. The real-world data is based on a capture of my personal development environment which has accumulated a large cache. So it is somewhat arbitrary, but hopefully representative of a challenging environment. Capturing of the data is done with the `capture-last-use` binary, which you can run if you need to rebuild the database. Just try to run on a system with a relatively full cache in your cargo home directory. ```sh cargo bench -p benchsuite --bench global_cache_tracker ``` ## Viewing reports The benchmarks display some basic information on the command-line while they run. A more complete HTML report can be found at `target/criterion/report/index.html` which contains links to all the benchmarks and summaries. Check out the Criterion book for more information on the extensive reporting capabilities. ## Comparing implementations Knowing the raw numbers can be useful, but what you're probably most interested in is checking if your changes help or hurt performance. To do that, you need to run the benchmarks multiple times. First, run the benchmarks from the master branch of cargo without any changes. To make it easier to compare, Criterion supports naming the baseline so that you can iterate on your code and compare against it multiple times. ```sh cargo bench -- --save-baseline master ``` Now you can switch to your branch with your changes. Re-run the benchmarks compared against the baseline: ```sh cargo bench -- --baseline master ``` You can repeat the last command as you make changes to re-compare against the master baseline. Without the baseline arguments, it will compare against the last run, which can be helpful for comparing incremental changes. ## Capturing workspaces The [`workspaces`](workspaces) directory contains several workspaces that provide a variety of different workspaces intended to provide good exercises for benchmarks. Some of these are shadow copies of real-world workspaces. This is done with the tool in the [`capture`](capture) directory. The tool will copy `Cargo.lock` and all of the `Cargo.toml` files of the workspace members. It also adds an empty `lib.rs` so Cargo won't error, and sanitizes the `Cargo.toml` to some degree, removing unwanted elements. Finally, it compresses everything into a `tgz`. To run it, do: ```sh cd benches/capture cargo run -- /path/to/workspace/foo ``` The resolver benchmarks also support the `CARGO_BENCH_WORKSPACES` environment variable, which you can point to a Cargo workspace if you want to try different workspaces. For example: ```sh CARGO_BENCH_WORKSPACES=/path/to/some/workspace cargo bench ``` ## TODO This is just a start for establishing a benchmarking suite for Cargo. There's a lot that can be added. Some ideas: * Fix the benchmarks so that the resolver setup doesn't run every iteration. * Benchmark [this section of code](https://github.com/rust-lang/cargo/blob/a821e2cb24d7b6013433f069ab3bad53d160e100/src/cargo/ops/cargo_compile.rs#L470-L549) which builds the unit graph. The performance there isn't great, and it would be good to keep an eye on it. Unfortunately that would mean doing a bit of work to make `generate_targets` publicly visible, and there is a bunch of setup code that may need to be duplicated. * Benchmark the fingerprinting code. * Benchmark running the `cargo` executable. Running something like `cargo build` or `cargo check` with everything "Fresh" would be a good end-to-end exercise to measure the overall overhead of Cargo. * Benchmark pathological resolver scenarios. There might be some cases where the resolver can spend a significant amount of time. It would be good to identify if these exist, and create benchmarks for them. This may require creating an artificial index, similar to the `resolver-tests`. This should also consider scenarios where the resolver ultimately fails. * Benchmark without `Cargo.lock`. I'm not sure if this is particularly valuable, since we are mostly concerned with incremental builds which will always have a lock file. * Benchmark just [`resolve::resolve`](https://github.com/rust-lang/cargo/blob/a821e2cb24d7b6013433f069ab3bad53d160e100/src/cargo/core/resolver/mod.rs#L122) without anything else. This can help focus on just the resolver. [Criterion]: https://bheisler.github.io/criterion.rs/book/ ================================================ FILE: benches/benchsuite/Cargo.toml ================================================ [package] name = "benchsuite" version = "0.0.0" edition.workspace = true license.workspace = true repository.workspace = true description = "Benchmarking suite for Cargo." publish = false [dependencies] cargo.workspace = true cargo-util.workspace = true criterion.workspace = true flate2.workspace = true rand.workspace = true tar.workspace = true url.workspace = true [lib] bench = false [[bench]] name = "resolve" harness = false [[bench]] name = "workspace_initialization" harness = false [[bench]] name = "global_cache_tracker" harness = false [lints] workspace = true ================================================ FILE: benches/benchsuite/README.md ================================================ > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use. This > crate may make major changes to its APIs or be deprecated without warning. ================================================ FILE: benches/benchsuite/benches/global_cache_tracker.rs ================================================ //! Benchmarks for the global cache tracker. use cargo::core::global_cache_tracker::{self, DeferredGlobalLastUse, GlobalCacheTracker}; use cargo::util::GlobalContext; use cargo::util::cache_lock::CacheLockMode; use cargo::util::interning::InternedString; use criterion::{Criterion, criterion_group, criterion_main}; use std::fs; use std::path::{Path, PathBuf}; // Samples of real-world data. const GLOBAL_CACHE_SAMPLE: &str = "global-cache-tracker/global-cache-sample"; const GLOBAL_CACHE_RANDOM: &str = "global-cache-tracker/random-sample"; /// A scratch directory where the benchmark can place some files. fn root() -> PathBuf { let mut p = PathBuf::from(env!("CARGO_TARGET_TMPDIR")); p.push("bench_global_cache_tracker"); p } fn cargo_home() -> PathBuf { let mut p = root(); p.push("chome"); p } fn initialize_context() -> GlobalContext { // Set up config. let shell = cargo::core::Shell::new(); let homedir = cargo_home(); if !homedir.exists() { fs::create_dir_all(&homedir).unwrap(); } let cwd = homedir.clone(); let mut gctx = GlobalContext::new(shell, cwd, homedir); gctx.nightly_features_allowed = true; gctx.set_search_stop_path(root()); gctx.configure( 0, false, None, false, false, false, &None, &["gc".to_string()], &[], ) .unwrap(); // Set up database sample. let db_path = GlobalCacheTracker::db_path(&gctx).into_path_unlocked(); if db_path.exists() { fs::remove_file(&db_path).unwrap(); } let sample = Path::new(env!("CARGO_MANIFEST_DIR")).join(GLOBAL_CACHE_SAMPLE); fs::copy(sample, &db_path).unwrap(); gctx } /// Benchmarks how long it takes to initialize `GlobalCacheTracker` with an already /// existing full database. fn global_tracker_init(c: &mut Criterion) { let gctx = initialize_context(); let _lock = gctx .acquire_package_cache_lock(CacheLockMode::DownloadExclusive) .unwrap(); c.bench_function("global_tracker_init", |b| { b.iter(|| { GlobalCacheTracker::new(&gctx).unwrap(); }) }); } /// Benchmarks how long it takes to save a `GlobalCacheTracker` when there are zero /// updates. fn global_tracker_empty_save(c: &mut Criterion) { let gctx = initialize_context(); let _lock = gctx .acquire_package_cache_lock(CacheLockMode::DownloadExclusive) .unwrap(); let mut deferred = DeferredGlobalLastUse::new(); let mut tracker = GlobalCacheTracker::new(&gctx).unwrap(); c.bench_function("global_tracker_empty_save", |b| { b.iter(|| { deferred.save(&mut tracker).unwrap(); }) }); } fn load_random_sample() -> Vec<(InternedString, InternedString, u64)> { let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(GLOBAL_CACHE_RANDOM); fs::read_to_string(path) .unwrap() .lines() .map(|s| { let mut s = s.split(','); ( s.next().unwrap().into(), s.next().unwrap().into(), s.next().unwrap().parse().unwrap(), ) }) .collect() } /// Tests performance of updating the last-use timestamps in an already /// populated database. /// /// This runs for different sizes of number of crates to update (selecting /// from the random sample stored on disk). fn global_tracker_update(c: &mut Criterion) { let gctx = initialize_context(); let _lock = gctx .acquire_package_cache_lock(CacheLockMode::DownloadExclusive) .unwrap(); let sample = Path::new(env!("CARGO_MANIFEST_DIR")).join(GLOBAL_CACHE_SAMPLE); let db_path = GlobalCacheTracker::db_path(&gctx).into_path_unlocked(); let random_sample = load_random_sample(); let mut group = c.benchmark_group("global_tracker_update"); for size in [1, 10, 100, 500] { if db_path.exists() { fs::remove_file(&db_path).unwrap(); } fs::copy(&sample, &db_path).unwrap(); let mut deferred = DeferredGlobalLastUse::new(); let mut tracker = GlobalCacheTracker::new(&gctx).unwrap(); group.bench_with_input(size.to_string(), &size, |b, &size| { b.iter(|| { for (encoded_registry_name, name, size) in &random_sample[..size] { deferred.mark_registry_crate_used(global_cache_tracker::RegistryCrate { encoded_registry_name: *encoded_registry_name, crate_filename: format!("{}.crate", name).into(), size: *size, }); deferred.mark_registry_src_used(global_cache_tracker::RegistrySrc { encoded_registry_name: *encoded_registry_name, package_dir: *name, size: Some(*size), }); } deferred.save(&mut tracker).unwrap(); }) }); } } criterion_group!( benches, global_tracker_init, global_tracker_empty_save, global_tracker_update ); criterion_main!(benches); ================================================ FILE: benches/benchsuite/benches/resolve.rs ================================================ use benchsuite::fixtures; use cargo::GlobalContext; use cargo::core::compiler::{CompileKind, RustcTargetData}; use cargo::core::resolver::features::{FeatureOpts, FeatureResolver}; use cargo::core::resolver::{CliFeatures, ForceAllTargets, HasDevUnits, ResolveBehavior}; use cargo::core::{PackageIdSpec, Workspace}; use cargo::ops::WorkspaceResolve; use criterion::{Criterion, criterion_group, criterion_main}; use std::path::Path; struct ResolveInfo<'gctx> { ws: Workspace<'gctx>, requested_kinds: [CompileKind; 1], target_data: RustcTargetData<'gctx>, cli_features: CliFeatures, specs: Vec, has_dev_units: HasDevUnits, force_all_targets: ForceAllTargets, ws_resolve: WorkspaceResolve<'gctx>, } /// Helper for resolving a workspace. This will run the resolver once to /// download everything, and returns all the data structures that are used /// during resolution. fn do_resolve<'gctx>(gctx: &'gctx GlobalContext, ws_root: &Path) -> ResolveInfo<'gctx> { let requested_kinds = [CompileKind::Host]; let ws = Workspace::new(&ws_root.join("Cargo.toml"), gctx).unwrap(); let mut target_data = RustcTargetData::new(&ws, &requested_kinds).unwrap(); let cli_features = CliFeatures::from_command_line(&[], false, true).unwrap(); let pkgs = cargo::ops::Packages::Default; let specs = pkgs.to_package_id_specs(&ws).unwrap(); let has_dev_units = HasDevUnits::Yes; let force_all_targets = ForceAllTargets::No; // Do an initial run to download anything necessary so that it does // not confuse criterion's warmup. let dry_run = false; let ws_resolve = cargo::ops::resolve_ws_with_opts( &ws, &mut target_data, &requested_kinds, &cli_features, &specs, has_dev_units, force_all_targets, dry_run, ) .unwrap(); ResolveInfo { ws, requested_kinds, target_data, cli_features, specs, has_dev_units, force_all_targets, ws_resolve, } } /// Benchmark of the full `resolve_ws_with_opts` which runs the resolver /// twice, the feature resolver, and more. This is a major component of a /// regular cargo build. fn resolve_ws(c: &mut Criterion) { let fixtures = fixtures!(); let mut group = c.benchmark_group("resolve_ws"); for (ws_name, ws_root) in fixtures.workspaces() { let gctx = fixtures.make_context(&ws_root); // The resolver info is initialized only once in a lazy fashion. This // allows criterion to skip this workspace if the user passes a filter // on the command-line (like `cargo bench -- resolve_ws/tikv`). // // Due to the way criterion works, it tends to only run the inner // iterator once, and we don't want to call `do_resolve` in every // "step", since that would just be some useless work. let mut lazy_info = None; let dry_run = false; group.bench_function(&ws_name, |b| { let ResolveInfo { ws, requested_kinds, target_data, cli_features, specs, has_dev_units, force_all_targets, .. } = lazy_info.get_or_insert_with(|| do_resolve(&gctx, &ws_root)); b.iter(|| { cargo::ops::resolve_ws_with_opts( ws, target_data, requested_kinds, cli_features, specs, *has_dev_units, *force_all_targets, dry_run, ) .unwrap(); }) }); } group.finish(); } /// Benchmark of the feature resolver. fn feature_resolver(c: &mut Criterion) { let fixtures = fixtures!(); let mut group = c.benchmark_group("feature_resolver"); for (ws_name, ws_root) in fixtures.workspaces() { let gctx = fixtures.make_context(&ws_root); let mut lazy_info = None; group.bench_function(&ws_name, |b| { let ResolveInfo { ws, requested_kinds, target_data, cli_features, specs, has_dev_units, ws_resolve, .. } = lazy_info.get_or_insert_with(|| do_resolve(&gctx, &ws_root)); b.iter(|| { let feature_opts = FeatureOpts::new_behavior(ResolveBehavior::V2, *has_dev_units); FeatureResolver::resolve( ws, target_data, &ws_resolve.targeted_resolve, &ws_resolve.pkg_set, cli_features, specs, requested_kinds, feature_opts, ) .unwrap(); }) }); } group.finish(); } // Criterion complains about the measurement time being too small, but the // measurement time doesn't seem important to me, what is more important is // the number of iterations which defaults to 100, which seems like a // reasonable default. Otherwise, the measurement time would need to be // changed per workspace. We wouldn't want to spend 60s on every workspace, // that would take too long and isn't necessary for the smaller workspaces. criterion_group!(benches, resolve_ws, feature_resolver); criterion_main!(benches); ================================================ FILE: benches/benchsuite/benches/workspace_initialization.rs ================================================ use benchsuite::fixtures; use cargo::core::Workspace; use criterion::{Criterion, criterion_group, criterion_main}; fn workspace_initialization(c: &mut Criterion) { let fixtures = fixtures!(); let mut group = c.benchmark_group("workspace_initialization"); for (ws_name, ws_root) in fixtures.workspaces() { let gctx = fixtures.make_context(&ws_root); // The resolver info is initialized only once in a lazy fashion. This // allows criterion to skip this workspace if the user passes a filter // on the command-line (like `cargo bench -- workspace_initialization/tikv`). group.bench_function(ws_name, |b| { b.iter(|| Workspace::new(&ws_root.join("Cargo.toml"), &gctx).unwrap()) }); } group.finish(); } // Criterion complains about the measurement time being too small, but the // measurement time doesn't seem important to me, what is more important is // the number of iterations which defaults to 100, which seems like a // reasonable default. Otherwise, the measurement time would need to be // changed per workspace. We wouldn't want to spend 60s on every workspace, // that would take too long and isn't necessary for the smaller workspaces. criterion_group!(benches, workspace_initialization); criterion_main!(benches); ================================================ FILE: benches/benchsuite/global-cache-tracker/random-sample ================================================ github.com-1ecc6299db9ec823,tungstenite-0.18.0,218740 github.com-1ecc6299db9ec823,integer-encoding-1.1.5,30672 github.com-1ecc6299db9ec823,tungstenite-0.14.0,315676 github.com-1ecc6299db9ec823,oxcable-0.5.1,163196 github.com-1ecc6299db9ec823,swc_ecma_transforms_typescript-0.32.0,245522 github.com-1ecc6299db9ec823,hyper-0.12.35,601153 github.com-1ecc6299db9ec823,resiter-0.4.0,59880 github.com-1ecc6299db9ec823,net2-0.2.37,115813 github.com-1ecc6299db9ec823,str_inflector-0.12.0,182460 github.com-1ecc6299db9ec823,derive_builder_macro-0.10.2,16441 github.com-1ecc6299db9ec823,smol_str-0.1.23,42436 github.com-1ecc6299db9ec823,wasm-bindgen-multi-value-xform-0.2.83,35347 github.com-1ecc6299db9ec823,time-macros-0.1.0,1620 github.com-1ecc6299db9ec823,unicode-bidi-0.3.7,140153 github.com-1ecc6299db9ec823,socket2-0.4.0,167295 github.com-1ecc6299db9ec823,ppv-lite86-0.2.10,125234 github.com-1ecc6299db9ec823,tracing-wasm-0.2.1,31449 github.com-1ecc6299db9ec823,eframe-0.19.0,158130 github.com-1ecc6299db9ec823,block-modes-0.7.0,42530 github.com-1ecc6299db9ec823,rangemap-0.1.11,144157 github.com-1ecc6299db9ec823,metal-0.23.1,1038699 github.com-1ecc6299db9ec823,os_str_bytes-6.0.1,86390 github.com-1ecc6299db9ec823,plotters-backend-0.3.4,53018 github.com-1ecc6299db9ec823,spidev-0.4.0,45301 github.com-1ecc6299db9ec823,axum-macros-0.2.3,102058 github.com-1ecc6299db9ec823,embedded-time-0.12.1,246450 github.com-1ecc6299db9ec823,envmnt-0.10.4,2328079 github.com-1ecc6299db9ec823,camino-1.1.1,133976 github.com-1ecc6299db9ec823,siphasher-0.3.5,46666 github.com-1ecc6299db9ec823,lexical-write-integer-0.8.5,388374 github.com-1ecc6299db9ec823,reqwest-0.11.14,686608 github.com-1ecc6299db9ec823,enum-map-2.4.1,51184 github.com-1ecc6299db9ec823,sentry-panic-0.29.0,18211 github.com-1ecc6299db9ec823,msf-srtp-0.2.0,73164 github.com-1ecc6299db9ec823,near-sandbox-utils-0.4.1,7543 github.com-1ecc6299db9ec823,ablescript-0.5.2,129318 github.com-1ecc6299db9ec823,apecs-derive-0.2.3,10620 github.com-1ecc6299db9ec823,libc-0.2.133,3417382 github.com-1ecc6299db9ec823,tracing-0.1.35,380627 github.com-1ecc6299db9ec823,serde-wasm-bindgen-0.3.1,55371 github.com-1ecc6299db9ec823,compiler_builtins-0.1.71,692853 github.com-1ecc6299db9ec823,mockito-0.7.2,1179718 github.com-1ecc6299db9ec823,tonic-0.5.2,420299 github.com-1ecc6299db9ec823,tracing-core-0.1.30,240058 github.com-1ecc6299db9ec823,tower-timeout-0.3.0-alpha.2,7486 github.com-1ecc6299db9ec823,js-intern-0.3.1,7026 github.com-1ecc6299db9ec823,json-ld-context-processing-0.12.1,78101 github.com-1ecc6299db9ec823,generic-array-0.14.6,67349 github.com-1ecc6299db9ec823,synstructure-0.12.3,93523 github.com-1ecc6299db9ec823,version-compare-0.0.10,74950 github.com-1ecc6299db9ec823,dirs-1.0.5,51075 github.com-1ecc6299db9ec823,worker-kv-0.5.1,67351 github.com-1ecc6299db9ec823,vsimd-0.8.0,170805 github.com-1ecc6299db9ec823,mockall-0.9.1,187734 github.com-1ecc6299db9ec823,nan-preserving-float-0.1.0,6341 github.com-1ecc6299db9ec823,wasmer-types-2.3.0,192436 github.com-1ecc6299db9ec823,sodiumoxide-0.2.7,5131115 github.com-1ecc6299db9ec823,tracing-attributes-0.1.11,74857 github.com-1ecc6299db9ec823,treediff-4.0.2,72588 github.com-1ecc6299db9ec823,wiggle-generate-5.0.0,103044 github.com-1ecc6299db9ec823,lapin-1.6.6,497368 github.com-1ecc6299db9ec823,cranelift-entity-0.93.1,114206 github.com-1ecc6299db9ec823,pcap-parser-0.13.3,184131 github.com-1ecc6299db9ec823,rustfft-5.1.1,1638221 github.com-1ecc6299db9ec823,string_cache-0.7.5,75074 github.com-1ecc6299db9ec823,maybe-uninit-2.0.0,38492 github.com-1ecc6299db9ec823,diesel_full_text_search-2.0.0,10179 github.com-1ecc6299db9ec823,quinn-proto-0.8.4,687565 github.com-1ecc6299db9ec823,semver-0.5.1,73365 github.com-1ecc6299db9ec823,rocket_http-0.5.0-rc.2,409939 github.com-1ecc6299db9ec823,dialoguer-0.7.1,95159 github.com-1ecc6299db9ec823,fallible_collections-0.4.5,244152 github.com-1ecc6299db9ec823,parking_lot_core-0.9.0,138932 github.com-1ecc6299db9ec823,relative-path-1.6.0,103315 github.com-1ecc6299db9ec823,lua52-sys-0.1.2,584054 github.com-1ecc6299db9ec823,actix-files-0.6.0,126121 github.com-1ecc6299db9ec823,crates-io-0.35.1,29498 github.com-1ecc6299db9ec823,sentry-backtrace-0.19.1,20268 github.com-1ecc6299db9ec823,text_unit-0.1.10,26100 github.com-1ecc6299db9ec823,ascii-1.0.0,143025 github.com-1ecc6299db9ec823,crossbeam-utils-0.8.6,169542 github.com-1ecc6299db9ec823,nelf-0.1.0,28868 github.com-1ecc6299db9ec823,colorsys-0.6.5,86989 github.com-1ecc6299db9ec823,enum-iterator-1.2.0,31042 github.com-1ecc6299db9ec823,ansi-str-0.7.2,111689 github.com-1ecc6299db9ec823,anyhow-1.0.68,209123 github.com-1ecc6299db9ec823,gix-lock-5.0.1,65110 github.com-1ecc6299db9ec823,nom-supreme-0.8.0,147530 github.com-1ecc6299db9ec823,path-slash-0.1.4,28655 github.com-1ecc6299db9ec823,crates-io-0.35.0,29406 github.com-1ecc6299db9ec823,stb_truetype-0.2.8,22939 github.com-1ecc6299db9ec823,proc-macro2-1.0.50,185288 github.com-1ecc6299db9ec823,snapbox-0.4.1,169526 github.com-1ecc6299db9ec823,hyper-0.14.9,764075 github.com-1ecc6299db9ec823,ab_glyph-0.2.15,61722 github.com-1ecc6299db9ec823,uuid-0.1.18,47889 github.com-1ecc6299db9ec823,data-url-0.2.0,123480 github.com-1ecc6299db9ec823,threadpool-1.7.1,59558 github.com-1ecc6299db9ec823,thiserror-impl-1.0.29,65149 github.com-1ecc6299db9ec823,sha1-0.6.0,31102 github.com-1ecc6299db9ec823,tokio-tls-0.2.1,51467 github.com-1ecc6299db9ec823,locspan-derive-0.6.0,59360 github.com-1ecc6299db9ec823,ureq-1.5.1,249335 github.com-1ecc6299db9ec823,protoc-rust-2.24.1,13459 github.com-1ecc6299db9ec823,serde-1.0.159,509060 github.com-1ecc6299db9ec823,unescape-0.1.0,6047 github.com-1ecc6299db9ec823,data-encoding-2.2.0,113191 github.com-1ecc6299db9ec823,bytestring-1.1.0,23705 github.com-1ecc6299db9ec823,ab_glyph_rasterizer-0.1.8,34773 github.com-1ecc6299db9ec823,syn-0.12.15,912964 github.com-1ecc6299db9ec823,reqwest-0.11.9,656209 github.com-1ecc6299db9ec823,rustls-0.17.0,903717 github.com-1ecc6299db9ec823,term_size-0.3.2,36226 github.com-1ecc6299db9ec823,ordered-float-3.1.0,91357 github.com-1ecc6299db9ec823,cookie-0.2.5,44912 github.com-1ecc6299db9ec823,debugid-0.8.0,44521 github.com-1ecc6299db9ec823,conrod-0.51.1,2154016 github.com-1ecc6299db9ec823,indexmap-1.6.1,247801 github.com-1ecc6299db9ec823,target-spec-1.3.1,68315 github.com-1ecc6299db9ec823,lexical-parse-integer-0.8.6,139671 github.com-1ecc6299db9ec823,time-0.1.38,131629 github.com-1ecc6299db9ec823,glib-macros-0.14.1,102959 github.com-1ecc6299db9ec823,metrics-macros-0.6.0,37750 github.com-1ecc6299db9ec823,structopt-0.3.12,224213 github.com-1ecc6299db9ec823,criterion-0.3.2,439241 github.com-1ecc6299db9ec823,lyon_path-0.17.7,186745 github.com-1ecc6299db9ec823,miette-5.5.0,312945 github.com-1ecc6299db9ec823,tokio-codec-0.2.0-alpha.6,118193 github.com-1ecc6299db9ec823,structopt-derive-0.4.14,84883 github.com-1ecc6299db9ec823,objekt-0.1.2,24191 github.com-1ecc6299db9ec823,sqlx-macros-0.5.7,110890 github.com-1ecc6299db9ec823,systemstat-0.1.10,127295 github.com-1ecc6299db9ec823,colorful-0.2.2,99698 github.com-1ecc6299db9ec823,quick-xml-0.20.0,645935 github.com-1ecc6299db9ec823,selinux-sys-0.6.2,27060 github.com-1ecc6299db9ec823,vsmtp-mail-parser-1.4.0-rc.10,137699 github.com-1ecc6299db9ec823,sec1-0.7.2,64870 github.com-1ecc6299db9ec823,nix-0.22.1,1161830 github.com-1ecc6299db9ec823,snow-0.9.0,2658286 github.com-1ecc6299db9ec823,per_test_directory_macros-0.1.0,2962 github.com-1ecc6299db9ec823,syn-helpers-0.4.3,58801 github.com-1ecc6299db9ec823,terminal_size-0.2.2,29633 github.com-1ecc6299db9ec823,bevy_hierarchy-0.7.0,41018 github.com-1ecc6299db9ec823,dynamic_reload-0.4.0,74455 github.com-1ecc6299db9ec823,http-signature-normalization-actix-0.5.0-beta.14,126857 github.com-1ecc6299db9ec823,http-body-0.4.1,24138 github.com-1ecc6299db9ec823,gix-index-0.13.0,207795 github.com-1ecc6299db9ec823,darling_macro-0.13.1,4156 github.com-1ecc6299db9ec823,serde_json-1.0.66,543072 github.com-1ecc6299db9ec823,minreq-1.4.1,41355 github.com-1ecc6299db9ec823,sct-0.6.1,60974 github.com-1ecc6299db9ec823,openssl-0.10.50,1173941 github.com-1ecc6299db9ec823,bevy_pbr-0.6.0,201163 github.com-1ecc6299db9ec823,security-framework-2.3.1,290512 github.com-1ecc6299db9ec823,pin-project-internal-0.4.30,128419 github.com-1ecc6299db9ec823,serde_yaml-0.7.5,158524 github.com-1ecc6299db9ec823,cid-0.3.2,17269 github.com-1ecc6299db9ec823,plotters-backend-0.3.0,51995 github.com-1ecc6299db9ec823,serde_yaml-0.8.12,179579 github.com-1ecc6299db9ec823,cosmwasm-schema-derive-1.1.9,34956 github.com-1ecc6299db9ec823,docopt-0.6.86,175553 github.com-1ecc6299db9ec823,git-testament-0.2.4,27685 github.com-1ecc6299db9ec823,htmlescape-0.3.1,143378 github.com-1ecc6299db9ec823,is_proc_translated-0.1.1,16533 github.com-1ecc6299db9ec823,futures-macro-0.3.4,33147 github.com-1ecc6299db9ec823,futures-intrusive-0.4.2,520476 github.com-1ecc6299db9ec823,rustix-0.35.13,1581355 github.com-1ecc6299db9ec823,glsl-layout-0.3.2,75515 github.com-1ecc6299db9ec823,darling-0.12.0,67446 github.com-1ecc6299db9ec823,blake3-0.1.5,394136 github.com-1ecc6299db9ec823,async-stripe-0.15.0,3157635 github.com-1ecc6299db9ec823,hbs-common-sys-0.2.1,1034 github.com-1ecc6299db9ec823,base58-0.1.0,7019 github.com-1ecc6299db9ec823,time-0.2.23,342720 github.com-1ecc6299db9ec823,memoffset-0.5.6,27595 github.com-1ecc6299db9ec823,colored-1.9.3,85161 github.com-1ecc6299db9ec823,lrpar-0.13.1,153317 github.com-1ecc6299db9ec823,clap-2.34.0,975823 github.com-1ecc6299db9ec823,chalk-engine-0.55.0,203718 github.com-1ecc6299db9ec823,cosmic-space-0.3.6,800331 github.com-1ecc6299db9ec823,syn-1.0.93,1886902 github.com-1ecc6299db9ec823,futures-core-0.3.5,43430 github.com-1ecc6299db9ec823,prost-derive-0.11.6,99428 github.com-1ecc6299db9ec823,toml_edit-0.15.0,491549 github.com-1ecc6299db9ec823,pcb-llvm-0.2.0,17328 github.com-1ecc6299db9ec823,rusticata-macros-2.1.0,35537 github.com-1ecc6299db9ec823,rustyline-with-hint-fix-10.1.0,548833 github.com-1ecc6299db9ec823,sharded-slab-0.1.1,239224 github.com-1ecc6299db9ec823,literally-0.1.3,20415 github.com-1ecc6299db9ec823,riff-1.0.1,20582 github.com-1ecc6299db9ec823,futures-macro-0.3.23,38691 github.com-1ecc6299db9ec823,criterion-0.3.1,431723 github.com-1ecc6299db9ec823,atty-0.2.14,14567 github.com-1ecc6299db9ec823,vergen-3.1.0,49089 github.com-1ecc6299db9ec823,peeking_take_while-0.1.2,18604 github.com-1ecc6299db9ec823,serde_derive-1.0.156,316173 github.com-1ecc6299db9ec823,geo-0.23.1,1022596 github.com-1ecc6299db9ec823,persy-1.4.3,778219 github.com-1ecc6299db9ec823,futures-lite-1.13.0,214632 github.com-1ecc6299db9ec823,ms_dtyp-0.0.3,44387 github.com-1ecc6299db9ec823,thiserror-1.0.33,66618 github.com-1ecc6299db9ec823,marksman_escape-0.1.2,587235 github.com-1ecc6299db9ec823,serde_derive-1.0.101,289156 github.com-1ecc6299db9ec823,gix-ref-0.29.0,214105 github.com-1ecc6299db9ec823,der-0.7.5,384316 github.com-1ecc6299db9ec823,promptly-0.3.0,35216 github.com-1ecc6299db9ec823,libc-0.2.115,3166629 github.com-1ecc6299db9ec823,ppv-lite86-0.1.2,33514 github.com-1ecc6299db9ec823,gfx-hal-0.6.0,254453 github.com-1ecc6299db9ec823,as-slice-0.1.3,20306 github.com-1ecc6299db9ec823,gpu-alloc-0.3.0,78823 github.com-1ecc6299db9ec823,arc-swap-0.4.8,167950 github.com-1ecc6299db9ec823,libusb1-sys-0.5.0,1458763 github.com-1ecc6299db9ec823,sysinfo-0.26.8,609932 github.com-1ecc6299db9ec823,refinery-macros-0.8.7,6514 github.com-1ecc6299db9ec823,assert_float_eq-1.1.3,38445 github.com-1ecc6299db9ec823,tinyvec-1.1.0,363582 github.com-1ecc6299db9ec823,predicates-1.0.7,1168580 github.com-1ecc6299db9ec823,pulldown-cmark-0.9.3,595681 github.com-1ecc6299db9ec823,aws-sigv4-0.46.0,97885 github.com-1ecc6299db9ec823,fastrand-1.5.0,39175 github.com-1ecc6299db9ec823,futures-channel-0.3.17,131816 github.com-1ecc6299db9ec823,usbd_scsi-0.1.0,172205 github.com-1ecc6299db9ec823,tinyvec-1.4.0,379505 github.com-1ecc6299db9ec823,structsy-0.5.1,513822 github.com-1ecc6299db9ec823,aws-sdk-ssm-0.21.0,9755619 github.com-1ecc6299db9ec823,pin-project-lite-0.1.1,63942 github.com-1ecc6299db9ec823,tokio-rustls-0.13.0,78252 github.com-1ecc6299db9ec823,tinyvec_macros-0.1.0,2912 github.com-1ecc6299db9ec823,extended_matrix_float-1.0.0,6233 github.com-1ecc6299db9ec823,displaydoc-0.2.3,68676 github.com-1ecc6299db9ec823,typed-arena-2.0.2,43549 github.com-1ecc6299db9ec823,cranelift-0.86.1,16294 github.com-1ecc6299db9ec823,modular-bitfield-impl-0.10.0,64389 github.com-1ecc6299db9ec823,schemafy_core-0.5.2,7696 github.com-1ecc6299db9ec823,sea-orm-macros-0.8.0,86930 github.com-1ecc6299db9ec823,core-foundation-sys-0.4.6,61859 github.com-1ecc6299db9ec823,move-symbol-pool-0.3.2,14473 github.com-1ecc6299db9ec823,glutin-0.25.1,300518 github.com-1ecc6299db9ec823,postcard-cobs-0.2.0,41524 github.com-1ecc6299db9ec823,quote-0.6.11,69636 github.com-1ecc6299db9ec823,encoding_rs-0.8.32,5022316 github.com-1ecc6299db9ec823,clap-2.32.0,946148 github.com-1ecc6299db9ec823,term-0.6.1,181220 github.com-1ecc6299db9ec823,enumset-1.0.12,85911 github.com-1ecc6299db9ec823,ctest2-0.4.1,100745 github.com-1ecc6299db9ec823,serde-xml-any-0.0.3,70554 github.com-1ecc6299db9ec823,proc-macro-hack-0.5.11,39025 github.com-1ecc6299db9ec823,remove_dir_all-0.5.1,23418 github.com-1ecc6299db9ec823,weezl-0.1.5,134218 github.com-1ecc6299db9ec823,windows_x86_64_gnullvm-0.42.1,3254874 github.com-1ecc6299db9ec823,rocket-0.5.0-rc.2,1225987 github.com-1ecc6299db9ec823,pin-project-0.4.27,282004 github.com-1ecc6299db9ec823,criterion-cycles-per-byte-0.1.3,18296 github.com-1ecc6299db9ec823,coco-0.1.1,107143 github.com-1ecc6299db9ec823,solana-bloom-1.15.1,22207 github.com-1ecc6299db9ec823,qoqo_calculator-1.1.1,163666 github.com-1ecc6299db9ec823,aes-gcm-0.9.4,381036 github.com-1ecc6299db9ec823,blowfish-0.9.1,39658 github.com-1ecc6299db9ec823,pango-0.14.3,258440 github.com-1ecc6299db9ec823,clap_derive-3.0.0,129105 github.com-1ecc6299db9ec823,content_inspector-0.2.4,27568 github.com-1ecc6299db9ec823,jsona-0.2.0,104104 github.com-1ecc6299db9ec823,gix-quote-0.4.3,32314 github.com-1ecc6299db9ec823,bcs-0.1.3,93194 github.com-1ecc6299db9ec823,statrs-0.14.0,681982 github.com-1ecc6299db9ec823,cw-controllers-0.16.0,32195 github.com-1ecc6299db9ec823,hyper-0.12.36,578470 github.com-1ecc6299db9ec823,argon2-0.4.1,112707 github.com-1ecc6299db9ec823,fraction-0.12.2,482976 github.com-1ecc6299db9ec823,quickcheck-0.7.2,89884 github.com-1ecc6299db9ec823,typetag-0.1.8,135149 github.com-1ecc6299db9ec823,object-0.20.0,916661 github.com-1ecc6299db9ec823,pest_derive-2.2.1,60318 github.com-1ecc6299db9ec823,coremidi-sys-3.1.0,40849 github.com-1ecc6299db9ec823,either-1.6.0,48881 github.com-1ecc6299db9ec823,tarpc-0.29.0,244416 github.com-1ecc6299db9ec823,num-integer-0.1.42,88403 github.com-1ecc6299db9ec823,oid-registry-0.6.0,46996 github.com-1ecc6299db9ec823,historian-3.0.11,23818 github.com-1ecc6299db9ec823,ui-sys-0.1.3,1784250 github.com-1ecc6299db9ec823,cranelift-frontend-0.92.0,166902 github.com-1ecc6299db9ec823,pin-project-lite-0.1.12,77882 github.com-1ecc6299db9ec823,piston2d-gfx_graphics-0.72.0,91826 github.com-1ecc6299db9ec823,stylist-macros-0.9.2,78647 github.com-1ecc6299db9ec823,valico-3.4.0,1394467 github.com-1ecc6299db9ec823,inventory-0.3.3,40329 github.com-1ecc6299db9ec823,wrapping_arithmetic-0.1.0,8774 github.com-1ecc6299db9ec823,serde-1.0.138,502921 github.com-1ecc6299db9ec823,ra_common-0.1.3,16920 github.com-1ecc6299db9ec823,markup5ever-0.10.0,213742 github.com-1ecc6299db9ec823,libp2p-core-0.20.1,460422 github.com-1ecc6299db9ec823,inout-0.1.2,40474 github.com-1ecc6299db9ec823,flatbuffers-23.1.21,103944 github.com-1ecc6299db9ec823,gdk-pixbuf-sys-0.10.0,42914 github.com-1ecc6299db9ec823,miniz_oxide-0.5.1,223551 github.com-1ecc6299db9ec823,merge-0.1.0,70214 github.com-1ecc6299db9ec823,pagecache-0.6.0,260742 github.com-1ecc6299db9ec823,ritelinked-0.3.2,142063 github.com-1ecc6299db9ec823,ethers-contract-1.0.2,589452 github.com-1ecc6299db9ec823,color_quant-1.1.0,21284 github.com-1ecc6299db9ec823,libykpers-sys-0.3.1,14270 github.com-1ecc6299db9ec823,cgmath-0.17.0,367702 github.com-1ecc6299db9ec823,clap-4.0.18,1096299 github.com-1ecc6299db9ec823,ears-0.5.1,165152 github.com-1ecc6299db9ec823,h2-0.2.5,765073 github.com-1ecc6299db9ec823,image-0.22.5,725576 github.com-1ecc6299db9ec823,digest-0.10.1,83013 github.com-1ecc6299db9ec823,js-sys-0.3.46,410849 github.com-1ecc6299db9ec823,psl-types-2.0.11,25329 github.com-1ecc6299db9ec823,apub-core-0.2.0,52434 github.com-1ecc6299db9ec823,thiserror-1.0.22,59077 github.com-1ecc6299db9ec823,num-complex-0.4.3,139539 github.com-1ecc6299db9ec823,autocfg-1.0.1,41521 github.com-1ecc6299db9ec823,amethyst_locale-0.15.3,4896 github.com-1ecc6299db9ec823,tokio-timer-0.2.11,167147 github.com-1ecc6299db9ec823,pipe-trait-0.2.1,11031 github.com-1ecc6299db9ec823,http-muncher-0.3.2,259101 github.com-1ecc6299db9ec823,thin-dst-1.1.0,46297 github.com-1ecc6299db9ec823,float-ord-0.2.0,21145 github.com-1ecc6299db9ec823,trust-dns-proto-0.21.2,1312809 github.com-1ecc6299db9ec823,ordered-multimap-0.4.3,178966 github.com-1ecc6299db9ec823,bitflags-0.4.0,33932 github.com-1ecc6299db9ec823,windows_x86_64_gnullvm-0.42.0,3240134 github.com-1ecc6299db9ec823,cargo-util-0.1.2,72189 github.com-1ecc6299db9ec823,serde_with_macros-1.5.2,72325 github.com-1ecc6299db9ec823,wasmer-2.3.0,529984 github.com-1ecc6299db9ec823,tokio-codec-0.1.2,30428 github.com-1ecc6299db9ec823,pico-args-0.5.0,54991 github.com-1ecc6299db9ec823,migformatting-0.1.1,1680 github.com-1ecc6299db9ec823,lexical-core-0.6.7,2382284 github.com-1ecc6299db9ec823,katex-wasmbind-0.10.0,274096 github.com-1ecc6299db9ec823,blender-armature-0.0.1,51371 github.com-1ecc6299db9ec823,twoway-0.2.1,129719 github.com-1ecc6299db9ec823,sha3-0.10.0,540582 github.com-1ecc6299db9ec823,ringbuf-0.2.8,92733 github.com-1ecc6299db9ec823,pest_meta-2.1.3,175833 github.com-1ecc6299db9ec823,selectme-macros-0.7.1,79130 github.com-1ecc6299db9ec823,secp256k1-sys-0.7.0,5303296 github.com-1ecc6299db9ec823,panic-probe-0.3.0,18841 github.com-1ecc6299db9ec823,ron-0.6.6,208755 github.com-1ecc6299db9ec823,defmt-macros-0.3.3,78405 github.com-1ecc6299db9ec823,winapi-x86_64-pc-windows-gnu-0.4.0,53158182 github.com-1ecc6299db9ec823,aph-0.2.0,30088 github.com-1ecc6299db9ec823,winnow-0.4.6,959730 github.com-1ecc6299db9ec823,syntex_syntax-0.54.0,1272567 github.com-1ecc6299db9ec823,prost-derive-0.11.9,99428 github.com-1ecc6299db9ec823,commoncrypto-sys-0.2.0,16095 github.com-1ecc6299db9ec823,yew-router-macro-0.15.0,42667 github.com-1ecc6299db9ec823,http-range-header-0.3.0,29647 github.com-1ecc6299db9ec823,crossbeam-queue-0.2.3,60131 github.com-1ecc6299db9ec823,slice-deque-0.3.0,271889 github.com-1ecc6299db9ec823,libc-0.2.65,2334946 github.com-1ecc6299db9ec823,minidom-0.14.0,102507 github.com-1ecc6299db9ec823,tokio-native-tls-0.3.0,60313 github.com-1ecc6299db9ec823,glam-0.17.3,1191013 github.com-1ecc6299db9ec823,semver-1.0.6,114819 github.com-1ecc6299db9ec823,cortex-m-rtfm-macros-0.5.1,112048 github.com-1ecc6299db9ec823,bitvec-1.0.0,1006982 github.com-1ecc6299db9ec823,gfx-backend-metal-0.6.5,660301 github.com-1ecc6299db9ec823,object-0.30.1,1467041 github.com-1ecc6299db9ec823,proc-macro-error-attr-0.4.11,18220 github.com-1ecc6299db9ec823,proteus-0.5.0,179567 github.com-1ecc6299db9ec823,crunchy-0.1.6,6678 github.com-1ecc6299db9ec823,once_cell-1.7.2,121632 github.com-1ecc6299db9ec823,rel-0.2.0,14524 github.com-1ecc6299db9ec823,lexical-core-0.7.5,2355166 github.com-1ecc6299db9ec823,windows_x86_64_gnu-0.42.1,10581222 github.com-1ecc6299db9ec823,thread_local-1.1.5,49409 github.com-1ecc6299db9ec823,openssl-sys-0.9.63,285709 github.com-1ecc6299db9ec823,simplelog-0.11.2,85170 github.com-1ecc6299db9ec823,thiserror-impl-1.0.25,55249 github.com-1ecc6299db9ec823,quanta-0.10.0,82241 github.com-1ecc6299db9ec823,vsmtp-common-1.4.0-rc.10,122740 github.com-1ecc6299db9ec823,tonic-0.1.0-alpha.6,302938 github.com-1ecc6299db9ec823,ecdsa-0.16.1,121203 github.com-1ecc6299db9ec823,deltae-0.3.0,2871017 github.com-1ecc6299db9ec823,phf_shared-0.11.1,30454 github.com-1ecc6299db9ec823,trustfall-rustdoc-adapter-22.5.2,5348192 github.com-1ecc6299db9ec823,mockall_derive-0.11.0,227736 github.com-1ecc6299db9ec823,wasm-bindgen-0.2.64,584320 github.com-1ecc6299db9ec823,sg-std-0.12.0,27020 github.com-1ecc6299db9ec823,chalk-ir-0.87.0,288472 github.com-1ecc6299db9ec823,environment-0.1.1,9957 github.com-1ecc6299db9ec823,crash-handler-0.3.3,125183 github.com-1ecc6299db9ec823,bindgen-0.59.2,958852 github.com-1ecc6299db9ec823,serde_path_to_error-0.1.7,101591 github.com-1ecc6299db9ec823,tinyvec-0.3.3,77508 github.com-1ecc6299db9ec823,precomputed-hash-0.1.1,2853 github.com-1ecc6299db9ec823,rustc-rayon-core-0.4.1,264995 github.com-1ecc6299db9ec823,gix-sec-0.6.2,57428 github.com-1ecc6299db9ec823,pistoncore-input-0.19.0,83490 github.com-1ecc6299db9ec823,gloo-utils-0.1.5,15602 github.com-1ecc6299db9ec823,redox_intelflash-0.1.3,28056 github.com-1ecc6299db9ec823,block2-0.2.0-alpha.6,39192 github.com-1ecc6299db9ec823,fastly-shared-0.9.1,19292 github.com-1ecc6299db9ec823,ibc-chain-registry-0.1.0,48243 github.com-1ecc6299db9ec823,socket2-0.4.4,205035 github.com-1ecc6299db9ec823,futures-channel-0.3.19,132274 github.com-1ecc6299db9ec823,structopt-0.3.16,217443 github.com-1ecc6299db9ec823,rusty-fork-0.2.2,64570 github.com-1ecc6299db9ec823,parking_lot_core-0.9.7,139601 github.com-1ecc6299db9ec823,async-lock-2.6.0,99844 github.com-1ecc6299db9ec823,bindgen-0.56.0,923373 github.com-1ecc6299db9ec823,quad-rand-0.2.1,9108 github.com-1ecc6299db9ec823,wasmflow-codec-0.10.0,12343 github.com-1ecc6299db9ec823,gix-0.38.0,883190 github.com-1ecc6299db9ec823,futures-macro-0.3.27,38519 github.com-1ecc6299db9ec823,portable-atomic-0.3.13,549649 github.com-1ecc6299db9ec823,portable-atomic-1.3.2,799707 github.com-1ecc6299db9ec823,bevy-crevice-derive-0.6.0,16165 github.com-1ecc6299db9ec823,gltf-json-0.15.2,118263 github.com-1ecc6299db9ec823,struple-impl-0.1.0,4096 github.com-1ecc6299db9ec823,annotate-snippets-0.9.1,153174 github.com-1ecc6299db9ec823,futures-core-0.3.28,46207 github.com-1ecc6299db9ec823,wezterm-bidi-0.2.2,361283 github.com-1ecc6299db9ec823,mildew-0.1.2,3002 github.com-1ecc6299db9ec823,bytecount-0.6.3,46567 github.com-1ecc6299db9ec823,numext-fixed-hash-core-0.1.6,7403 github.com-1ecc6299db9ec823,bytesize-1.1.0,34012 github.com-1ecc6299db9ec823,oxsdatatypes-0.1.0,174662 github.com-1ecc6299db9ec823,hostname-0.1.5,4811 github.com-1ecc6299db9ec823,io-lifetimes-1.0.4,207652 github.com-1ecc6299db9ec823,derive_builder_core-0.11.2,135502 github.com-1ecc6299db9ec823,ttf-parser-0.15.2,711615 github.com-1ecc6299db9ec823,tracing-opentelemetry-0.17.4,187675 github.com-1ecc6299db9ec823,ab_glyph_rasterizer-0.1.7,34278 github.com-1ecc6299db9ec823,bevy_diagnostic-0.6.0,14396 github.com-1ecc6299db9ec823,toml_datetime-0.5.0,34801 github.com-1ecc6299db9ec823,wasm-parser-0.1.7,39726 github.com-1ecc6299db9ec823,ppv-null-0.1.2,26098 github.com-1ecc6299db9ec823,ci_info-0.10.2,1197933 github.com-1ecc6299db9ec823,jobserver-0.1.21,72720 github.com-1ecc6299db9ec823,sentencepiece-sys-0.10.0,10055292 github.com-1ecc6299db9ec823,zstd-sys-2.0.1+zstd.1.5.2,3387955 github.com-1ecc6299db9ec823,byte-strings-proc_macros-0.2.2,7886 github.com-1ecc6299db9ec823,snapbox-0.4.11,193312 github.com-1ecc6299db9ec823,ron-0.6.4,198516 github.com-1ecc6299db9ec823,gix-object-0.28.0,102536 github.com-1ecc6299db9ec823,strum_macros-0.23.1,87403 github.com-1ecc6299db9ec823,defmt-0.3.2,93568 github.com-1ecc6299db9ec823,openssl-0.10.35,971227 github.com-1ecc6299db9ec823,gtk-sys-0.14.0,1376726 github.com-1ecc6299db9ec823,gpu-alloc-0.4.7,99476 github.com-1ecc6299db9ec823,colored-2.0.0,91075 github.com-1ecc6299db9ec823,fixedbitset-0.4.2,67872 github.com-1ecc6299db9ec823,argparse-0.2.2,95032 github.com-1ecc6299db9ec823,bevy_mod_raycast-0.6.2,456756 github.com-1ecc6299db9ec823,byte-strings-0.2.2,35209 github.com-1ecc6299db9ec823,mem_tools-0.1.0,937956 github.com-1ecc6299db9ec823,deno_core-0.167.0,11067700 github.com-1ecc6299db9ec823,rocksdb-0.19.0,628015 github.com-1ecc6299db9ec823,num-traits-0.2.12,231414 github.com-1ecc6299db9ec823,type-info-derive-0.2.0,56221 github.com-1ecc6299db9ec823,structopt-derive-0.3.4,68017 github.com-1ecc6299db9ec823,extendr-macros-0.3.1,49695 github.com-1ecc6299db9ec823,secret-cosmwasm-std-1.0.0,632711 github.com-1ecc6299db9ec823,skim-0.7.0,380243 github.com-1ecc6299db9ec823,serde-1.0.135,501463 github.com-1ecc6299db9ec823,lock_api-0.1.5,109183 github.com-1ecc6299db9ec823,cw-multi-test-0.16.2,445599 github.com-1ecc6299db9ec823,quote-1.0.10,120640 github.com-1ecc6299db9ec823,safemem-0.3.2,17382 github.com-1ecc6299db9ec823,gloo-dialogs-0.1.1,4653 github.com-1ecc6299db9ec823,dashmap-4.0.2,105438 github.com-1ecc6299db9ec823,oorandom-11.1.0,31893 github.com-1ecc6299db9ec823,polars-core-0.21.1,1678691 github.com-1ecc6299db9ec823,claxon-0.4.2,259276 github.com-1ecc6299db9ec823,cc-1.0.35,179169 github.com-1ecc6299db9ec823,cocoa-0.19.1,296083 github.com-1ecc6299db9ec823,tokio-1.9.0,2490393 github.com-1ecc6299db9ec823,gix-refspec-0.10.1,105495 github.com-1ecc6299db9ec823,futures-task-0.3.12,39561 github.com-1ecc6299db9ec823,sqlx-core-0.4.2,1064795 github.com-1ecc6299db9ec823,futures-task-0.3.14,39566 github.com-1ecc6299db9ec823,datastore_grpc-0.4.0,18233399 github.com-1ecc6299db9ec823,directories-4.0.1,74013 github.com-1ecc6299db9ec823,wgpu-hal-0.15.1,1201034 github.com-1ecc6299db9ec823,discard-1.0.4,14342 github.com-1ecc6299db9ec823,tinytga-0.1.0,102322 github.com-1ecc6299db9ec823,prost-types-0.10.1,126121 github.com-1ecc6299db9ec823,assert2-0.3.6,36145 github.com-1ecc6299db9ec823,syn-inline-mod-0.5.0,35740 github.com-1ecc6299db9ec823,bat-0.22.1,5407476 github.com-1ecc6299db9ec823,minidumper-child-0.1.0,32329 github.com-1ecc6299db9ec823,libp2p-kad-0.21.0,416675 github.com-1ecc6299db9ec823,asn1_der-0.6.3,1102166 github.com-1ecc6299db9ec823,h2-0.2.4,764682 github.com-1ecc6299db9ec823,ena-0.14.2,90713 github.com-1ecc6299db9ec823,prost-build-0.8.0,31248726 github.com-1ecc6299db9ec823,wasmer-compiler-cranelift-3.1.1,300456 github.com-1ecc6299db9ec823,gfx-hal-0.7.0,238750 github.com-1ecc6299db9ec823,nom-4.2.3,644514 github.com-1ecc6299db9ec823,os_str_bytes-2.4.0,52159 github.com-1ecc6299db9ec823,sourcemap-6.2.1,135303 github.com-1ecc6299db9ec823,actix-router-0.5.1,150753 github.com-1ecc6299db9ec823,markup5ever-0.9.0,229731 github.com-1ecc6299db9ec823,gloo-worker-0.2.1,31624 github.com-1ecc6299db9ec823,object-0.25.3,1313095 github.com-1ecc6299db9ec823,rustversion-1.0.0,41602 ================================================ FILE: benches/benchsuite/src/bin/capture-last-use.rs ================================================ //! Utility for capturing a global cache last-use database based on the files //! on a real-world system. //! //! This will look in the `CARGO_HOME` of the current system and record last-use //! data for all files in the cache. This is intended to provide a real-world //! example for a benchmark that should be close to what a real set of data //! should look like. //! //! See `benches/global_cache_tracker.rs` for the benchmark that uses this //! data. //! //! The database is kept in git. It usually shouldn't need to be re-generated //! unless there is a change in the schema or the benchmark. use cargo::GlobalContext; use cargo::core::global_cache_tracker::{self, DeferredGlobalLastUse, GlobalCacheTracker}; use cargo::util::cache_lock::CacheLockMode; use rand::prelude::*; use std::collections::HashMap; use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; fn main() { // Set up config. let shell = cargo::core::Shell::new(); let homedir = Path::new(env!("CARGO_MANIFEST_DIR")).join("global-cache-tracker"); let cwd = homedir.clone(); let mut gctx = GlobalContext::new(shell, cwd, homedir.clone()); gctx.configure( 0, false, None, false, false, false, &None, &["gc".to_string()], &[], ) .unwrap(); let db_path = GlobalCacheTracker::db_path(&gctx).into_path_unlocked(); if db_path.exists() { fs::remove_file(&db_path).unwrap(); } let _lock = gctx .acquire_package_cache_lock(CacheLockMode::DownloadExclusive) .unwrap(); let mut deferred = DeferredGlobalLastUse::new(); let mut tracker = GlobalCacheTracker::new(&gctx).unwrap(); let real_home = cargo::util::homedir(&std::env::current_dir().unwrap()).unwrap(); let cache_dir = real_home.join("registry/cache"); for dir_ent in fs::read_dir(cache_dir).unwrap() { let registry = dir_ent.unwrap(); let encoded_registry_name = registry.file_name().to_string_lossy().into(); for krate in fs::read_dir(registry.path()).unwrap() { let krate = krate.unwrap(); let meta = krate.metadata().unwrap(); deferred.mark_registry_crate_used_stamp( global_cache_tracker::RegistryCrate { encoded_registry_name, crate_filename: krate.file_name().to_string_lossy().as_ref().into(), size: meta.len(), }, Some(&meta.modified().unwrap()), ); } } let mut src_entries = Vec::new(); let cache_dir = real_home.join("registry/src"); for dir_ent in fs::read_dir(cache_dir).unwrap() { let registry = dir_ent.unwrap(); let encoded_registry_name = registry.file_name().to_string_lossy().into(); for krate in fs::read_dir(registry.path()).unwrap() { let krate = krate.unwrap(); let meta = krate.metadata().unwrap(); let src = global_cache_tracker::RegistrySrc { encoded_registry_name, package_dir: krate.file_name().to_string_lossy().as_ref().into(), size: Some(cargo_util::du(&krate.path(), &[]).unwrap()), }; src_entries.push(src.clone()); let timestamp = meta.modified().unwrap(); deferred.mark_registry_src_used_stamp(src, Some(×tamp)); } } let git_co_dir = real_home.join("git/checkouts"); for dir_ent in fs::read_dir(git_co_dir).unwrap() { let git_source = dir_ent.unwrap(); let encoded_git_name = git_source.file_name().to_string_lossy().into(); for co in fs::read_dir(git_source.path()).unwrap() { let co = co.unwrap(); let meta = co.metadata().unwrap(); deferred.mark_git_checkout_used_stamp( global_cache_tracker::GitCheckout { encoded_git_name, short_name: co.file_name().to_string_lossy().as_ref().into(), size: Some(cargo_util::du(&co.path(), &[]).unwrap()), }, Some(&meta.modified().unwrap()), ); } } deferred.save(&mut tracker).unwrap(); drop(deferred); drop(tracker); fs::rename(&db_path, homedir.join("global-cache-sample")).unwrap(); // Clean up the lock file created above. fs::remove_file(homedir.join(".package-cache")).unwrap(); // Save a random sample of crates that the benchmark should update. // Pick whichever registry has the most entries. This is to be somewhat // realistic for the common case that all dependencies come from one // registry (crates.io). let mut counts = HashMap::new(); for src in &src_entries { let c: &mut u32 = counts.entry(src.encoded_registry_name).or_default(); *c += 1; } let mut counts: Vec<_> = counts.into_iter().map(|(k, v)| (v, k)).collect(); counts.sort(); let biggest = counts.last().unwrap().1; src_entries.retain(|src| src.encoded_registry_name == biggest); let mut rng = &mut rand::rng(); let sample: Vec<_> = src_entries.sample(&mut rng, 500).collect(); let mut f = File::create(homedir.join("random-sample")).unwrap(); for src in sample { writeln!( f, "{},{},{}", src.encoded_registry_name, src.package_dir, src.size.unwrap() ) .unwrap(); } } ================================================ FILE: benches/benchsuite/src/lib.rs ================================================ //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use. This //! > crate may make major changes to its APIs or be deprecated without warning. #![allow(clippy::disallowed_methods)] use cargo::GlobalContext; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use url::Url; #[macro_export] macro_rules! fixtures { () => { $crate::Fixtures::new(env!("CARGO_TARGET_TMPDIR")) }; } // This is an arbitrary commit that existed when I started. This helps // ensure consistent results. It can be updated if needed, but that can // make it harder to compare results with older versions of cargo. const CRATES_IO_COMMIT: &str = "85f7bfd61ea4fee08ec68c468762e886b2aebec6"; pub struct Fixtures { cargo_target_tmpdir: PathBuf, } impl Fixtures { pub fn new(cargo_target_tmpdir: &str) -> Self { let bench = Self { cargo_target_tmpdir: PathBuf::from(cargo_target_tmpdir), }; bench.create_home(); bench.create_target_dir(); bench.clone_index(); bench.unpack_workspaces(); bench } fn root(&self) -> PathBuf { self.cargo_target_tmpdir.join("bench") } fn target_dir(&self) -> PathBuf { let mut p = self.root(); p.push("target"); p } fn cargo_home(&self) -> PathBuf { let mut p = self.root(); p.push("chome"); p } fn index(&self) -> PathBuf { let mut p = self.root(); p.push("index"); p } fn workspaces_path(&self) -> PathBuf { let mut p = self.root(); p.push("workspaces"); p } fn registry_url(&self) -> Url { Url::from_file_path(self.index()).unwrap() } fn create_home(&self) { let home = self.cargo_home(); if !home.exists() { fs::create_dir_all(&home).unwrap(); } fs::write( home.join("config.toml"), format!( r#" [source.crates-io] replace-with = 'local-snapshot' [source.local-snapshot] registry = '{}' "#, self.registry_url() ), ) .unwrap(); } fn create_target_dir(&self) { // This is necessary to ensure the .rustc_info.json file is written. // Otherwise it won't be written, and it is very expensive to create. if !self.target_dir().exists() { fs::create_dir_all(self.target_dir()).unwrap(); } } /// This clones crates.io at a specific point in time into tmp/index. fn clone_index(&self) { let index = self.index(); let maybe_git = |command: &str| { let status = Command::new("git") .current_dir(&index) .args(command.split_whitespace().collect::>()) .status() .expect("git should be installed"); status.success() }; let git = |command: &str| { if !maybe_git(command) { panic!("failed to run git command: {}", command); } }; if index.exists() { if maybe_git(&format!( "rev-parse -q --verify {}^{{commit}}", CRATES_IO_COMMIT )) { // Already fetched. return; } } else { fs::create_dir_all(&index).unwrap(); // git 2.48.0 changed the behavior of setting HEAD when doing a // fetch, so let's just force it to match // crates.io-index-archive's default branch. This also accounts // for users who may override init.defaultBranch. git("init --bare --initial-branch=main"); git("remote add origin https://github.com/rust-lang/crates.io-index-archive"); } git(&format!("fetch origin {}", CRATES_IO_COMMIT)); git("branch -f main FETCH_HEAD"); } /// This unpacks the compressed workspace skeletons into tmp/workspaces. fn unpack_workspaces(&self) { let ws_dir = Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .join("workspaces"); let archives = fs::read_dir(ws_dir) .unwrap() .map(|e| e.unwrap().path()) .filter(|p| p.extension() == Some(std::ffi::OsStr::new("tgz"))); for archive in archives { let name = archive.file_stem().unwrap(); let f = fs::File::open(&archive).unwrap(); let f = flate2::read::GzDecoder::new(f); let dest = self.workspaces_path().join(&name); if dest.exists() { fs::remove_dir_all(&dest).unwrap(); } let mut archive = tar::Archive::new(f); archive.unpack(self.workspaces_path()).unwrap(); } } /// Vec of `(ws_name, ws_root)`. pub fn workspaces(&self) -> Vec<(String, PathBuf)> { // CARGO_BENCH_WORKSPACES can be used to override, otherwise it just uses // the workspaces in the workspaces directory. let mut ps: Vec<_> = match std::env::var_os("CARGO_BENCH_WORKSPACES") { Some(s) => std::env::split_paths(&s).collect(), None => fs::read_dir(self.workspaces_path()) .unwrap() .map(|e| e.unwrap().path()) // These currently fail in most cases on Windows due to long // filenames in the git checkouts. .filter(|p| { !(cfg!(windows) && matches!(p.file_name().unwrap().to_str().unwrap(), "servo" | "tikv")) }) .collect(), }; // Sort so it is consistent. ps.sort(); ps.into_iter() .map(|p| (p.file_name().unwrap().to_str().unwrap().to_owned(), p)) .collect() } /// Creates a new Context. pub fn make_context(&self, ws_root: &Path) -> GlobalContext { let shell = cargo::core::Shell::new(); let mut gctx = GlobalContext::new(shell, ws_root.to_path_buf(), self.cargo_home()); // Configure is needed to set the target_dir which is needed to write // the .rustc_info.json file which is very expensive. gctx.configure( 0, false, None, false, false, false, &Some(self.target_dir()), &[], &[], ) .unwrap(); gctx } } ================================================ FILE: benches/capture/Cargo.toml ================================================ [package] name = "capture" version = "0.1.0" edition.workspace = true license.workspace = true description = "Tool for capturing a real-world workspace for benchmarking." publish = false [dependencies] cargo_metadata.workspace = true flate2.workspace = true tar.workspace = true toml = { workspace = true, features = ["display", "parse", "serde"] } [lints] workspace = true ================================================ FILE: benches/capture/src/main.rs ================================================ //! This tool helps to capture the `Cargo.toml` files of a workspace. //! //! Run it by passing a list of workspaces to capture. //! Use the `-f` flag to allow it to overwrite existing captures. //! The workspace will be saved in a `.tgz` file in the `../workspaces` directory. #![allow(clippy::disallowed_methods)] #![allow(clippy::print_stderr)] use flate2::{Compression, GzBuilder}; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; fn main() { let force = std::env::args().any(|arg| arg == "-f"); let dest = Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .join("workspaces"); if !dest.exists() { panic!("expected {} to exist", dest.display()); } for arg in std::env::args().skip(1).filter(|arg| !arg.starts_with("-")) { let source_root = fs::canonicalize(arg).unwrap(); capture(&source_root, &dest, force); } } fn capture(source_root: &Path, dest: &Path, force: bool) { let name = Path::new(source_root.file_name().unwrap()); let mut dest_gz = PathBuf::from(dest); dest_gz.push(name); dest_gz.set_extension("tgz"); if dest_gz.exists() { if !force { panic!( "dest {:?} already exists, use -f to force overwriting", dest_gz ); } fs::remove_file(&dest_gz).unwrap(); } let vcs_info = capture_vcs_info(source_root, force); let dst = fs::File::create(&dest_gz).unwrap(); let encoder = GzBuilder::new() .filename(format!("{}.tar", name.to_str().unwrap())) .write(dst, Compression::best()); let mut ar = tar::Builder::new(encoder); ar.mode(tar::HeaderMode::Deterministic); if let Some(info) = &vcs_info { add_ar_file(&mut ar, &name.join(".cargo_vcs_info.json"), info); } // Gather all local packages. let metadata = cargo_metadata::MetadataCommand::new() .manifest_path(source_root.join("Cargo.toml")) .features(cargo_metadata::CargoOpt::AllFeatures) .exec() .expect("cargo_metadata failed"); let mut found_root = false; for package in &metadata.packages { if package.source.is_some() { continue; } let manifest_path = package.manifest_path.as_std_path(); copy_manifest(&manifest_path, &mut ar, name, &source_root); found_root |= manifest_path == source_root.join("Cargo.toml"); } if !found_root { // A virtual workspace. let contents = fs::read_to_string(source_root.join("Cargo.toml")).unwrap(); assert!(!contents.contains("[package]")); add_ar_file(&mut ar, &name.join("Cargo.toml"), &contents); } let lock = fs::read_to_string(source_root.join("Cargo.lock")).unwrap(); add_ar_file(&mut ar, &name.join("Cargo.lock"), &lock); let encoder = ar.into_inner().unwrap(); encoder.finish().unwrap(); eprintln!("created {}", dest_gz.display()); } fn copy_manifest( manifest_path: &Path, ar: &mut tar::Builder, name: &Path, source_root: &Path, ) { let relative_path = manifest_path .parent() .unwrap() .strip_prefix(source_root) .expect("workspace member should be under workspace root"); let relative_path = name.join(relative_path); let contents = fs::read_to_string(&manifest_path).unwrap(); let mut manifest: toml::Value = toml::from_str(&contents).unwrap(); let remove = |obj: &mut toml::Value, name| { let table = obj.as_table_mut().unwrap(); if table.contains_key(name) { table.remove(name); } }; remove(&mut manifest, "lib"); remove(&mut manifest, "bin"); remove(&mut manifest, "example"); remove(&mut manifest, "test"); remove(&mut manifest, "bench"); remove(&mut manifest, "profile"); if let Some(package) = manifest.get_mut("package") { remove(package, "default-run"); } let contents = toml::to_string(&manifest).unwrap(); add_ar_file(ar, &relative_path.join("Cargo.toml"), &contents); add_ar_file(ar, &relative_path.join("src").join("lib.rs"), ""); } fn add_ar_file(ar: &mut tar::Builder, path: &Path, contents: &str) { let mut header = tar::Header::new_gnu(); header.set_entry_type(tar::EntryType::file()); header.set_mode(0o644); header.set_size(contents.len() as u64); header.set_mtime(123456789); header.set_cksum(); ar.append_data(&mut header, path, contents.as_bytes()) .unwrap(); } fn capture_vcs_info(ws_root: &Path, force: bool) -> Option { let maybe_git = |command: &str| { Command::new("git") .current_dir(ws_root) .args(command.split_whitespace().collect::>()) .output() .expect("git should be installed") }; assert!(ws_root.join("Cargo.toml").exists()); let relative = maybe_git("ls-files --full-name Cargo.toml"); if !relative.status.success() { if !force { panic!("git repository not detected, use -f to force"); } return None; } let p = Path::new(std::str::from_utf8(&relative.stdout).unwrap().trim()); let relative = p.parent().unwrap(); if !force { let has_changes = !maybe_git("diff-index --quiet HEAD .").status.success(); if has_changes { panic!("git repo appears to have changes, use -f to force, or clean the repo"); } } let commit = maybe_git("rev-parse HEAD"); assert!(commit.status.success()); let commit = std::str::from_utf8(&commit.stdout).unwrap().trim(); let remote = maybe_git("remote get-url origin"); assert!(remote.status.success()); let remote = std::str::from_utf8(&remote.stdout).unwrap().trim(); let info = format!( "{{\n \"git\": {{\n \"sha1\": \"{}\",\n \"remote\": \"{}\"\n }},\ \n \"path_in_vcs\": \"{}\"\n}}\n", commit, remote, relative.display() ); eprintln!("recording vcs info:\n{}", info); Some(info) } ================================================ FILE: build.rs ================================================ use flate2::{Compression, GzBuilder}; use std::ffi::OsStr; use std::fs; use std::path::Path; use std::process::Command; fn main() { commit_info(); compress_man(); windows_manifest(); #[expect( clippy::disallowed_methods, reason = "not `cargo`, not needing to load from config" )] let target = std::env::var("TARGET").unwrap(); println!("cargo:rustc-env=RUST_HOST_TARGET={target}"); } fn compress_man() { #[expect( clippy::disallowed_methods, reason = "not `cargo`, not needing to load from config" )] let out_path = Path::new(&std::env::var("OUT_DIR").unwrap()).join("man.tgz"); let dst = fs::File::create(out_path).unwrap(); let encoder = GzBuilder::new() .filename("man.tar") .write(dst, Compression::best()); let mut ar = tar::Builder::new(encoder); ar.mode(tar::HeaderMode::Deterministic); let mut add_files = |dir, extension| { let mut files = fs::read_dir(dir) .unwrap() .map(|e| e.unwrap().path()) .collect::>(); files.sort(); for path in files { if path.extension() != Some(extension) { continue; } println!("cargo:rerun-if-changed={}", path.display()); ar.append_path_with_name(&path, path.file_name().unwrap()) .unwrap(); } }; add_files(Path::new("src/etc/man"), OsStr::new("1")); add_files(Path::new("src/doc/man/generated_txt"), OsStr::new("txt")); let encoder = ar.into_inner().unwrap(); encoder.finish().unwrap(); } struct CommitInfo { hash: String, short_hash: String, date: String, } fn commit_info_from_git() -> Option { if !Path::new(".git").exists() { return None; } let output = match Command::new("git") .arg("log") .arg("-1") .arg("--date=short") .arg("--format=%H %h %cd") .arg("--abbrev=9") .output() { Ok(output) if output.status.success() => output, _ => return None, }; let stdout = String::from_utf8(output.stdout).unwrap(); let mut parts = stdout.split_whitespace().map(|s| s.to_string()); Some(CommitInfo { hash: parts.next()?, short_hash: parts.next()?, date: parts.next()?, }) } // The rustc source tarball is meant to contain all the source code to build an exact copy of the // toolchain, but it doesn't include the git repository itself. It wouldn't thus be possible to // populate the version information with the commit hash and the commit date. // // To work around this, the rustc build process obtains the git information when creating the // source tarball and writes it to the `git-commit-info` file. The build process actually creates // at least *two* of those files, one for Rust as a whole (in the root of the tarball) and one // specifically for Cargo (in src/tools/cargo). This function loads that file. // // The file is a newline-separated list of full commit hash, short commit hash, and commit date. fn commit_info_from_rustc_source_tarball() -> Option { let path = Path::new("git-commit-info"); if !path.exists() { return None; } // Dependency tracking is a nice to have for this (git doesn't do it), so if the path is not // valid UTF-8 just avoid doing it rather than erroring out. if let Some(utf8) = path.to_str() { println!("cargo:rerun-if-changed={utf8}"); } let content = std::fs::read_to_string(&path).ok()?; let mut parts = content.split('\n').map(|s| s.to_string()); Some(CommitInfo { hash: parts.next()?, short_hash: parts.next()?, date: parts.next()?, }) } fn commit_info() { // Var set by bootstrap whenever omit-git-hash is enabled in rust-lang/rust's config.toml. println!("cargo:rerun-if-env-changed=CFG_OMIT_GIT_HASH"); #[expect( clippy::disallowed_methods, reason = "not `cargo`, not needing to load from config" )] if std::env::var_os("CFG_OMIT_GIT_HASH").is_some() { return; } let Some(git) = commit_info_from_git().or_else(commit_info_from_rustc_source_tarball) else { return; }; println!("cargo:rustc-env=CARGO_COMMIT_HASH={}", git.hash); println!("cargo:rustc-env=CARGO_COMMIT_SHORT_HASH={}", git.short_hash); println!("cargo:rustc-env=CARGO_COMMIT_DATE={}", git.date); } #[expect( clippy::disallowed_methods, reason = "not `cargo`, not needing to load from config" )] fn windows_manifest() { use std::env; let target_os = env::var("CARGO_CFG_TARGET_OS"); let target_env = env::var("CARGO_CFG_TARGET_ENV"); if Ok("windows") == target_os.as_deref() && Ok("msvc") == target_env.as_deref() { static WINDOWS_MANIFEST_FILE: &str = "windows.manifest.xml"; let mut manifest = env::current_dir().unwrap(); manifest.push(WINDOWS_MANIFEST_FILE); println!("cargo:rerun-if-changed={WINDOWS_MANIFEST_FILE}"); // Embed the Windows application manifest file. println!("cargo:rustc-link-arg-bin=cargo=/MANIFEST:EMBED"); println!( "cargo:rustc-link-arg-bin=cargo=/MANIFESTINPUT:{}", manifest.to_str().unwrap() ); // Turn linker warnings into errors. println!("cargo:rustc-link-arg-bin=cargo=/WX"); } } ================================================ FILE: ci/clean-test-output.sh ================================================ #!/bin/bash # This script remove test and benchmark output and displays disk usage. set -euo pipefail df -h rm -rf target/tmp df -h ================================================ FILE: ci/dump-environment.sh ================================================ #!/bin/bash # This script dumps information about the build environment to stdout. set -euo pipefail IFS=$'\n\t' echo "environment variables:" printenv | sort echo echo "disk usage:" df -h echo echo "CPU info:" if [[ "${OSTYPE}" = "darwin"* ]]; then system_profiler SPHardwareDataType || true sysctl hw || true else cat /proc/cpuinfo || true cat /proc/meminfo || true fi ================================================ FILE: ci/fetch-smoke-test.sh ================================================ #!/bin/bash # This script builds with static curl, and verifies that fetching works. set -ex if [[ -z "$RUNNER_TEMP" ]] then echo "RUNNER_TEMP must be set" exit 1 fi if [ ! -f Cargo.toml ]; then echo "Must be run from root of project." exit 1 fi # Building openssl on Windows is a pain. if [[ $(rustc -Vv | grep host:) != *windows* ]]; then FEATURES='vendored-openssl,curl-sys/static-curl,curl-sys/force-system-lib-on-osx' export LIBZ_SYS_STATIC=1 fi cargo build --features "$FEATURES" export CARGO_HOME=$RUNNER_TEMP/chome target/debug/cargo fetch rm -rf $CARGO_HOME ================================================ FILE: ci/generate.py ================================================ #!/usr/bin/env python3 MAPPING = { "build-script.html": "https://doc.rust-lang.org/cargo/reference/build-scripts.html", "config.html": None, "crates-io.html": "https://doc.rust-lang.org/cargo/reference/publishing.html", "environment-variables.html": None, "external-tools.html": None, "faq.html": "https://doc.rust-lang.org/cargo/faq.html", "guide.html": "https://doc.rust-lang.org/cargo/guide/", "index.html": "https://doc.rust-lang.org/cargo/", "manifest.html": None, "pkgid-spec.html": None, "policies.html": "https://crates.io/policies", "source-replacement.html": None, "specifying-dependencies.html": None, } TEMPLATE = """\ Page Moved This page has moved. Click here to go to the new page. """ def main(): for name in sorted(MAPPING): with open(name, 'w') as f: mapped = MAPPING[name] if mapped is None: mapped = "https://doc.rust-lang.org/cargo/reference/{}".format(name) f.write(TEMPLATE.format(name=name, mapped=mapped)) # WARN: The CNAME file is for GitHub to redirect requests to the custom domain. # Missing this may entail security hazard and domain takeover. # See with open('CNAME', 'w') as f: f.write('doc.crates.io') if __name__ == '__main__': main() ================================================ FILE: ci/validate-man.sh ================================================ #!/bin/bash # This script validates that there aren't any changes to the man pages. set -e cargo_man="src/doc" mdman_man="crates/mdman/doc" man_out="src/etc/man" changes=$(git status --porcelain -- $cargo_man $mdman_man $man_out) if [ -n "$changes" ] then echo "git directory must be clean before running this script." exit 1 fi cargo build-man changes=$(git status --porcelain -- $cargo_man $mdman_man $man_out) if [ -n "$changes" ] then echo "Detected changes of man pages:" echo "$changes" echo echo 'Please run `cargo build-man` to rebuild the man pages' echo "and commit the changes." exit 1 fi ================================================ FILE: ci/validate-version-bump.sh ================================================ #!/bin/bash # This script checks if a crate needs a version bump. # # At the time of writing, it doesn't check what kind of bump is required. # In the future, we could take SemVer compatibility into account, like # integrating `cargo-semver-checks` of else # # Inputs: # BASE_SHA The commit SHA of the branch where the PR wants to merge into. # HEAD_SHA The commit SHA that triggered the workflow. set -euo pipefail # When `BASE_SHA` is missing, we assume it is from GitHub merge queue merge commit, # so hope `HEAD~` to find the previous commit on master branch. base_sha=$(git rev-parse "${BASE_SHA:-HEAD~1}") head_sha=$(git rev-parse "${HEAD_SHA:-HEAD}") echo "Base revision is $base_sha" echo "Head revision is $head_sha" echo "::group::Building xtask" cargo bump-check --help echo "::endgroup::" cargo bump-check --github --base-rev "$base_sha" --head-rev "$head_sha" ================================================ FILE: clippy.toml ================================================ allow-print-in-tests = true allow-dbg-in-tests = true disallowed-methods = [ { path = "std::env::var", reason = "use `Config::get_env` instead. See rust-lang/cargo#11588" }, { path = "std::env::var_os", reason = "use `Config::get_env_os` instead. See rust-lang/cargo#11588" }, { path = "std::env::vars", reason = "not recommended to use in Cargo. See rust-lang/cargo#11588" }, { path = "std::env::vars_os", reason = "not recommended to use in Cargo. See rust-lang/cargo#11588" }, ] disallowed-types = [ { path = "std::sync::atomic::AtomicU64", reason = "not portable. See rust-lang/cargo#12988" }, ] ================================================ FILE: crates/build-rs/Cargo.toml ================================================ [package] name = "build-rs" version = "0.3.4" rust-version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true description = "API for writing Cargo `build.rs` files" [features] ## Experimental API. This feature flag is **NOT** semver stable. unstable = [] [dependencies] unicode-ident.workspace = true ================================================ FILE: crates/build-rs/README.md ================================================ > This crate is maintained by the Cargo team for use by the wider > ecosystem. This crate follows semver compatibility for its APIs. ================================================ FILE: crates/build-rs/src/ident.rs ================================================ use unicode_ident::{is_xid_continue, is_xid_start}; pub(crate) fn is_feature_name(s: &str) -> bool { s.chars() .all(|ch| is_xid_continue(ch) || matches!(ch, '-' | '+' | '.')) } pub(crate) fn is_ident(s: &str) -> bool { let mut cs = s.chars(); cs.next() .is_some_and(|ch| is_xid_start(ch) || matches!(ch, '_')) && cs.all(is_xid_continue) } pub(crate) fn is_ascii_ident(s: &str) -> bool { let mut cs = s.chars(); cs.next() .is_some_and(|ch| ch.is_ascii_alphabetic() || matches!(ch, '_')) && cs.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_')) } pub(crate) fn is_crate_name(s: &str) -> bool { let mut cs = s.chars(); cs.next() .is_some_and(|ch| is_xid_start(ch) || matches!(ch, '-' | '_')) && cs.all(|ch| is_xid_continue(ch) || matches!(ch, '-')) } ================================================ FILE: crates/build-rs/src/input.rs ================================================ //! Inputs from the build system to the build script. //! //! This crate does not do any caching or interpreting of the values provided by //! Cargo beyond the communication protocol itself. It is up to the build script //! to interpret the string values and decide what to do with them. //! //! Reference: use std::path::PathBuf; use crate::ident::{is_ascii_ident, is_crate_name, is_feature_name}; use crate::output::rerun_if_env_changed; /// [`ProcessEnv`] wrapper that implicit calls [`rerun_if_env_changed`] const ENV: RerunIfEnvChanged = RerunIfEnvChanged::new(); /// Abstraction over environment variables trait Env { /// Fetches the environment variable `key`, returning `None` if the variable isn’t set or if /// there is another error. /// /// It may return `None` if the environment variable’s name contains the equal sign character /// (`=`) or the NUL character. /// /// Note that this function will not check if the environment variable is valid Unicode. fn get(&self, key: &str) -> Option; /// Checks the environment variable `key` is present /// /// It may not be considered present if the environment variable’s name contains the equal sign character /// (`=`) or the NUL character. fn is_present(&self, key: &str) -> bool; } /// Fetches environment variables from the current process struct ProcessEnv; impl Env for ProcessEnv { fn get(&self, key: &str) -> Option { std::env::var_os(key) } fn is_present(&self, key: &str) -> bool { self.get(key).is_some() } } /// [`Env`] wrapper that implicitly calls [`rerun_if_env_changed`] struct RerunIfEnvChanged(E); impl RerunIfEnvChanged { const fn new() -> Self { Self(ProcessEnv) } } impl Env for RerunIfEnvChanged { #[track_caller] fn get(&self, key: &str) -> Option { rerun_if_env_changed(key); self.0.get(key) } #[track_caller] fn is_present(&self, key: &str) -> bool { self.get(key).is_some() } } /// Path to the `cargo` binary performing the build. #[track_caller] pub fn cargo() -> PathBuf { to_path(var_or_panic("CARGO")) } /// The directory containing the manifest for the package being built (the package /// containing the build script). /// /// Also note that this is the value of the current /// working directory of the build script when it starts. #[track_caller] pub fn cargo_manifest_dir() -> PathBuf { to_path(var_or_panic("CARGO_MANIFEST_DIR")) } /// The path to the manifest of your package. #[track_caller] pub fn cargo_manifest_path() -> PathBuf { ENV.get("CARGO_MANIFEST_PATH") .map(to_path) .unwrap_or_else(|| { let mut path = cargo_manifest_dir(); path.push("Cargo.toml"); path }) } /// The manifest `links` value. #[track_caller] pub fn cargo_manifest_links() -> Option { ENV.get("CARGO_MANIFEST_LINKS").map(to_string) } /// Contains parameters needed for Cargo’s [jobserver] implementation to parallelize /// subprocesses. /// /// Rustc or cargo invocations from build.rs can already read /// `CARGO_MAKEFLAGS`, but GNU Make requires the flags to be specified either /// directly as arguments, or through the `MAKEFLAGS` environment variable. /// Currently Cargo doesn’t set the `MAKEFLAGS` variable, but it’s free for build /// scripts invoking GNU Make to set it to the contents of `CARGO_MAKEFLAGS`. /// /// [jobserver]: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html #[track_caller] pub fn cargo_makeflags() -> Option { ENV.get("CARGO_MAKEFLAGS").map(to_string) } /// For each activated feature of the package being built, this will be `true`. #[track_caller] pub fn cargo_feature(name: &str) -> bool { if !is_feature_name(name) { panic!("invalid feature name {name:?}") } let name = name.to_uppercase().replace('-', "_"); let key = format!("CARGO_FEATURE_{name}"); ENV.is_present(&key) } /// For each [configuration option] of the package being built, this will contain /// the value of the configuration. /// /// This includes values built-in to the compiler /// (which can be seen with `rustc --print=cfg`) and values set by build scripts /// and extra flags passed to rustc (such as those defined in `RUSTFLAGS`). /// /// [configuration option]: https://doc.rust-lang.org/stable/reference/conditional-compilation.html #[track_caller] pub fn cargo_cfg(cfg: &str) -> Option> { let var = cargo_cfg_var(cfg); ENV.get(&var).map(|v| to_strings(v, ',')) } #[track_caller] fn cargo_cfg_var(cfg: &str) -> String { if !is_ascii_ident(cfg) { panic!("invalid configuration option {cfg:?}") } let cfg = cfg.to_uppercase().replace('-', "_"); let key = format!("CARGO_CFG_{cfg}"); key } pub use self::cfg::*; mod cfg { use super::*; // those disabled with #[cfg(any())] don't seem meaningfully useful // but we list all cfg that are default known to check-cfg /// Each activated feature of the package being built #[doc = requires_msrv!("1.85")] #[track_caller] pub fn cargo_cfg_feature() -> Vec { to_strings(var_or_panic(&cargo_cfg_var("feature")), ',') } #[cfg(any())] #[track_caller] pub fn cargo_cfg_clippy() -> bool { ENV.is_present("CARGO_CFG_CLIPPY") } /// If we are compiling with debug assertions enabled. #[track_caller] pub fn cargo_cfg_debug_assertions() -> bool { ENV.is_present("CARGO_CFG_DEBUG_ASSERTIONS") } #[cfg(any())] #[track_caller] pub fn cargo_cfg_doc() -> bool { ENV.is_present("CARGO_CFG_DOC") } #[cfg(any())] #[track_caller] pub fn cargo_cfg_docsrs() -> bool { ENV.is_present("CARGO_CFG_DOCSRS") } #[cfg(any())] #[track_caller] pub fn cargo_cfg_doctest() -> bool { ENV.is_present("CARGO_CFG_DOCTEST") } /// The level of detail provided by derived [`Debug`] implementations. #[doc = unstable!(fmt_dbg, 129709)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_fmt_debug() -> String { to_string(var_or_panic("CARGO_CFG_FMT_DEBUG")) } #[cfg(any())] #[track_caller] pub fn cargo_cfg_miri() -> bool { ENV.is_present("CARGO_CFG_MIRI") } /// If we are compiling with overflow checks enabled. #[doc = unstable!(cfg_overflow_checks, 111466)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_overflow_checks() -> bool { ENV.is_present("CARGO_CFG_OVERFLOW_CHECKS") } /// The [panic strategy](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#panic). #[track_caller] pub fn cargo_cfg_panic() -> String { to_string(var_or_panic("CARGO_CFG_PANIC")) } /// If the crate is being compiled as a procedural macro. #[track_caller] pub fn cargo_cfg_proc_macro() -> bool { ENV.is_present("CARGO_CFG_PROC_MACRO") } /// The target relocation model. #[doc = unstable!(cfg_relocation_model, 114929)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_relocation_model() -> String { to_string(var_or_panic("CARGO_CFG_RELOCATION_MODEL")) } #[cfg(any())] #[track_caller] pub fn cargo_cfg_rustfmt() -> bool { ENV.is_present("CARGO_CFG_RUSTFMT") } /// Sanitizers enabled for the crate being compiled. #[doc = unstable!(cfg_sanitize, 39699)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_sanitize() -> Option> { ENV.get("CARGO_CFG_SANITIZE").map(|v| to_strings(v, ',')) } /// If CFI sanitization is generalizing pointers. #[doc = unstable!(cfg_sanitizer_cfi, 89653)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_sanitizer_cfi_generalize_pointers() -> bool { ENV.is_present("CARGO_CFG_SANITIZER_CFI_GENERALIZE_POINTERS") } /// If CFI sanitization is normalizing integers. #[doc = unstable!(cfg_sanitizer_cfi, 89653)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_sanitizer_cfi_normalize_integers() -> bool { ENV.is_present("CARGO_CFG_SANITIZER_CFI_NORMALIZE_INTEGERS") } /// Disambiguation of the [target ABI](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_abi) /// when the [target env](cargo_cfg_target_env) isn't sufficient. /// /// For historical reasons, this value is only defined as `Some` when /// actually needed for disambiguation. Thus, for example, on many GNU platforms, /// this value will be `None`. #[track_caller] pub fn cargo_cfg_target_abi() -> Option { to_opt(var_or_panic("CARGO_CFG_TARGET_ABI")).map(to_string) } /// The CPU [target architecture](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_arch). /// This is similar to the first element of the platform's target triple, but not identical. #[track_caller] pub fn cargo_cfg_target_arch() -> String { to_string(var_or_panic("CARGO_CFG_TARGET_ARCH")) } /// The CPU [target endianness](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_endian). #[track_caller] pub fn cargo_cfg_target_endian() -> String { to_string(var_or_panic("CARGO_CFG_TARGET_ENDIAN")) } /// The [target environment](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_env) ABI. /// This value is similar to the fourth element of the platform's target triple. /// /// For historical reasons, this value is only defined as not the empty-string when /// actually needed for disambiguation. Thus, for example, on many GNU platforms, /// this value will be empty. #[track_caller] pub fn cargo_cfg_target_env() -> String { to_string(var_or_panic("CARGO_CFG_TARGET_ENV")) } /// The [target family](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_family). #[track_caller] pub fn cargo_target_family() -> Vec { to_strings(var_or_panic(&cargo_cfg_var("target_family")), ',') } /// List of CPU [target features](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_feature) enabled. #[track_caller] pub fn cargo_cfg_target_feature() -> Vec { to_strings(var_or_panic(&cargo_cfg_var("target_feature")), ',') } /// List of CPU [supported atomic widths](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_has_atomic). #[track_caller] pub fn cargo_cfg_target_has_atomic() -> Vec { to_strings(var_or_panic(&cargo_cfg_var("target_has_atomic")), ',') } /// List of atomic widths that have equal alignment requirements. #[doc = unstable!(cfg_target_has_atomic_equal_alignment, 93822)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_target_has_atomic_equal_alignment() -> Vec { to_strings( var_or_panic(&cargo_cfg_var("target_has_atomic_equal_alignment")), ',', ) } /// List of atomic widths that have atomic load and store operations. #[doc = unstable!(cfg_target_has_atomic_load_store, 94039)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_target_has_atomic_load_store() -> Vec { to_strings( var_or_panic(&cargo_cfg_var("target_has_atomic_load_store")), ',', ) } /// The [target operating system](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_os). /// This value is similar to the second and third element of the platform's target triple. #[track_caller] pub fn cargo_cfg_target_os() -> String { to_string(var_or_panic("CARGO_CFG_TARGET_OS")) } /// The CPU [pointer width](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_pointer_width). #[track_caller] pub fn cargo_cfg_target_pointer_width() -> u32 { to_parsed(var_or_panic("CARGO_CFG_TARGET_POINTER_WIDTH")) } /// If the target supports thread-local storage. #[doc = unstable!(cfg_target_thread_local, 29594)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_target_thread_local() -> bool { ENV.is_present("CARGO_CFG_TARGET_THREAD_LOCAL") } /// The [target vendor](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_vendor). #[track_caller] pub fn cargo_cfg_target_vendor() -> String { to_string(var_or_panic("CARGO_CFG_TARGET_VENDOR")) } #[cfg(any())] #[track_caller] pub fn cargo_cfg_test() -> bool { ENV.is_present("CARGO_CFG_TEST") } /// If we are compiling with UB checks enabled. #[doc = unstable!(cfg_ub_checks, 123499)] #[cfg(feature = "unstable")] #[track_caller] pub fn cargo_cfg_ub_checks() -> bool { ENV.is_present("CARGO_CFG_UB_CHECKS") } /// Set on [unix-like platforms](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#unix-and-windows). #[track_caller] pub fn cargo_cfg_unix() -> bool { ENV.is_present("CARGO_CFG_UNIX") } /// Set on [windows-like platforms](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#unix-and-windows). #[track_caller] pub fn cargo_cfg_windows() -> bool { ENV.is_present("CARGO_CFG_WINDOWS") } } /// The folder in which all output and intermediate artifacts should be placed. /// /// This folder is inside the build directory for the package being built, and /// it is unique for the package in question. #[track_caller] pub fn out_dir() -> PathBuf { to_path(var_or_panic("OUT_DIR")) } /// The [target triple] that is being compiled for. Native code should be compiled /// for this triple. /// /// [target triple]: https://doc.rust-lang.org/stable/cargo/appendix/glossary.html#target #[track_caller] pub fn target() -> String { to_string(var_or_panic("TARGET")) } /// The host triple of the Rust compiler. #[track_caller] pub fn host() -> String { to_string(var_or_panic("HOST")) } /// The parallelism specified as the top-level parallelism. /// /// This can be useful to /// pass a `-j` parameter to a system like `make`. Note that care should be taken /// when interpreting this value. For historical purposes this is still provided /// but Cargo, for example, does not need to run `make -j`, and instead can set the /// `MAKEFLAGS` env var to the content of `CARGO_MAKEFLAGS` to activate the use of /// Cargo’s GNU Make compatible [jobserver] for sub-make invocations. /// /// [jobserver]: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html #[track_caller] pub fn num_jobs() -> u32 { to_parsed(var_or_panic("NUM_JOBS")) } /// The [level of optimization](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#opt-level). #[track_caller] pub fn opt_level() -> String { to_string(var_or_panic("OPT_LEVEL")) } /// The amount of [debug information](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#debug) included. #[track_caller] pub fn debug() -> String { to_string(var_or_panic("DEBUG")) } /// `release` for release builds, `debug` for other builds. /// /// This is determined based /// on if the [profile] inherits from the [`dev`] or [`release`] profile. Using this /// function is not recommended. Using other functions like [`opt_level`] provides /// a more correct view of the actual settings being used. /// /// [profile]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html /// [`dev`]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html#dev /// [`release`]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html#release #[track_caller] pub fn profile() -> String { to_string(var_or_panic("PROFILE")) } /// [Metadata] set by dependencies. For more information, see build script /// documentation about [the `links` manifest key][links]. /// /// [metadata]: crate::output::metadata /// [links]: https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#the-links-manifest-key #[track_caller] pub fn dep_metadata(name: &str, key: &str) -> Option { if !is_crate_name(name) { panic!("invalid dependency name {name:?}") } if !is_ascii_ident(key) { panic!("invalid metadata key {key:?}") } let name = name.to_uppercase().replace('-', "_"); let key = key.to_uppercase().replace('-', "_"); let key = format!("DEP_{name}_{key}"); ENV.get(&key).map(to_string) } /// The compiler that Cargo has resolved to use. #[track_caller] pub fn rustc() -> PathBuf { to_path(var_or_panic("RUSTC")) } /// The documentation generator that Cargo has resolved to use. #[track_caller] pub fn rustdoc() -> PathBuf { to_path(var_or_panic("RUSTDOC")) } /// The rustc wrapper, if any, that Cargo is using. See [`build.rustc-wrapper`]. /// /// [`build.rustc-wrapper`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustc-wrapper #[track_caller] pub fn rustc_wrapper() -> Option { ENV.get("RUSTC_WRAPPER").map(to_path) } /// The rustc wrapper, if any, that Cargo is using for workspace members. See /// [`build.rustc-workspace-wrapper`]. /// /// [`build.rustc-workspace-wrapper`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustc-workspace-wrapper #[track_caller] pub fn rustc_workspace_wrapper() -> Option { ENV.get("RUSTC_WORKSPACE_WRAPPER").map(to_path) } /// The linker that Cargo has resolved to use for the current target, if specified. /// /// [`target.*.linker`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#targettriplelinker #[track_caller] pub fn rustc_linker() -> Option { ENV.get("RUSTC_LINKER").map(to_path) } /// Extra flags that Cargo invokes rustc with. See [`build.rustflags`]. /// /// [`build.rustflags`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustflags #[track_caller] pub fn cargo_encoded_rustflags() -> Vec { to_strings(var_or_panic("CARGO_ENCODED_RUSTFLAGS"), '\x1f') } /// The full version of your package. #[track_caller] pub fn cargo_pkg_version() -> String { to_string(var_or_panic("CARGO_PKG_VERSION")) } /// The major version of your package. #[track_caller] pub fn cargo_pkg_version_major() -> u64 { to_parsed(var_or_panic("CARGO_PKG_VERSION_MAJOR")) } /// The minor version of your package. #[track_caller] pub fn cargo_pkg_version_minor() -> u64 { to_parsed(var_or_panic("CARGO_PKG_VERSION_MINOR")) } /// The patch version of your package. #[track_caller] pub fn cargo_pkg_version_patch() -> u64 { to_parsed(var_or_panic("CARGO_PKG_VERSION_PATCH")) } /// The pre-release version of your package. #[track_caller] pub fn cargo_pkg_version_pre() -> Option { to_opt(var_or_panic("CARGO_PKG_VERSION_PRE")).map(to_string) } /// The authors from the manifest of your package. #[track_caller] pub fn cargo_pkg_authors() -> Vec { to_strings(var_or_panic("CARGO_PKG_AUTHORS"), ':') } /// The name of your package. #[track_caller] pub fn cargo_pkg_name() -> String { to_string(var_or_panic("CARGO_PKG_NAME")) } /// The description from the manifest of your package. #[track_caller] pub fn cargo_pkg_description() -> Option { to_opt(var_or_panic("CARGO_PKG_DESCRIPTION")).map(to_string) } /// The home page from the manifest of your package. #[track_caller] pub fn cargo_pkg_homepage() -> Option { to_opt(var_or_panic("CARGO_PKG_HOMEPAGE")).map(to_string) } /// The repository from the manifest of your package. #[track_caller] pub fn cargo_pkg_repository() -> Option { to_opt(var_or_panic("CARGO_PKG_REPOSITORY")).map(to_string) } /// The license from the manifest of your package. #[track_caller] pub fn cargo_pkg_license() -> Option { to_opt(var_or_panic("CARGO_PKG_LICENSE")).map(to_string) } /// The license file from the manifest of your package. #[track_caller] pub fn cargo_pkg_license_file() -> Option { to_opt(var_or_panic("CARGO_PKG_LICENSE_FILE")).map(to_path) } /// The Rust version from the manifest of your package. Note that this is the /// minimum Rust version supported by the package, not the current Rust version. #[track_caller] pub fn cargo_pkg_rust_version() -> Option { to_opt(var_or_panic("CARGO_PKG_RUST_VERSION")).map(to_string) } /// Path to the README file of your package. #[track_caller] pub fn cargo_pkg_readme() -> Option { to_opt(var_or_panic("CARGO_PKG_README")).map(to_path) } #[track_caller] fn var_or_panic(key: &str) -> std::ffi::OsString { ENV.get(key) .unwrap_or_else(|| panic!("cargo environment variable `{key}` is missing")) } fn to_path(value: std::ffi::OsString) -> PathBuf { PathBuf::from(value) } #[track_caller] fn to_string(value: std::ffi::OsString) -> String { match value.into_string() { Ok(s) => s, Err(value) => { let err = std::str::from_utf8(value.as_encoded_bytes()).unwrap_err(); panic!("{err}") } } } fn to_opt(value: std::ffi::OsString) -> Option { (!value.is_empty()).then_some(value) } #[track_caller] fn to_strings(value: std::ffi::OsString, sep: char) -> Vec { if value.is_empty() { return Vec::new(); } let value = to_string(value); value.split(sep).map(str::to_owned).collect() } #[track_caller] fn to_parsed(value: std::ffi::OsString) -> T where T: std::str::FromStr, T::Err: std::fmt::Display, { let value = to_string(value); match value.parse() { Ok(s) => s, Err(err) => { panic!("{err}") } } } ================================================ FILE: crates/build-rs/src/lib.rs ================================================ //! build-rs provides a strongly typed interface around the Cargo build script //! protocol. Cargo provides inputs to the build script by environment variable //! and accepts commands by printing to stdout. //! //! > This crate is maintained by the Cargo team for use by the wider //! > ecosystem. This crate follows semver compatibility for its APIs. #![cfg_attr(all(doc, feature = "unstable"), feature(doc_auto_cfg, doc_cfg))] #![allow(clippy::disallowed_methods)] // HACK: deferred resoling this #![allow(clippy::print_stdout)] // HACK: deferred resoling this #[cfg(feature = "unstable")] macro_rules! unstable { ($feature:ident, $issue:literal) => { concat!( r#"
"#, r#"🔬"#, r#"This is a nightly-only experimental API. ("#, stringify!($feature), r#" #"#, $issue, r#")"#, r#"
"# ) }; } macro_rules! respected_msrv { ($ver:literal) => { concat!( r#"
MSRV: Respected as of "#, $ver, r#".
"# ) }; } macro_rules! requires_msrv { ($ver:literal) => { concat!( r#"
MSRV: Requires "#, $ver, r#".
"# ) }; } mod ident; pub mod input; pub mod output; ================================================ FILE: crates/build-rs/src/output.rs ================================================ //! Outputs from the build script to the build system. //! //! This crate assumes that stdout is at a new line whenever an output directive //! is called. Printing to stdout without a terminating newline (i.e. not using //! [`println!`]) may lead to surprising behavior. //! //! Reference: use std::ffi::OsStr; use std::path::Path; use std::{fmt::Display, fmt::Write as _}; use crate::ident::{is_ascii_ident, is_ident}; fn emit(directive: &str, value: impl Display) { println!("cargo::{directive}={value}"); } /// The `rerun-if-changed` instruction tells Cargo to re-run the build script if the /// file at the given path has changed. /// /// Currently, Cargo only uses the filesystem /// last-modified “mtime” timestamp to determine if the file has changed. It /// compares against an internal cached timestamp of when the build script last ran. /// /// If the path points to a directory, it will scan the entire directory for any /// modifications. /// /// If the build script inherently does not need to re-run under any circumstance, /// then calling `rerun_if_changed("build.rs")` is a simple way to prevent it from /// being re-run (otherwise, the default if no `rerun-if` instructions are emitted /// is to scan the entire package directory for changes). Cargo automatically /// handles whether or not the script itself needs to be recompiled, and of course /// the script will be re-run after it has been recompiled. Otherwise, specifying /// `build.rs` is redundant and unnecessary. #[track_caller] pub fn rerun_if_changed(path: impl AsRef) { let Some(path) = path.as_ref().to_str() else { panic!("cannot emit rerun-if-changed: path is not UTF-8"); }; if path.contains('\n') { panic!("cannot emit rerun-if-changed: path contains newline"); } emit("rerun-if-changed", path); } /// The `rerun-if-env-changed` instruction tells Cargo to re-run the build script /// if the value of an environment variable of the given name has changed. /// /// Note that the environment variables here are intended for global environment /// variables like `CC` and such, it is not possible to use this for environment /// variables like `TARGET` that [Cargo sets for build scripts][build-env]. The /// environment variables in use are those received by cargo invocations, not /// those received by the executable of the build script. /// /// [build-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts #[track_caller] pub fn rerun_if_env_changed(key: impl AsRef) { let Some(key) = key.as_ref().to_str() else { panic!("cannot emit rerun-if-env-changed: key is not UTF-8"); }; if key.contains('\n') { panic!("cannot emit rerun-if-env-changed: key contains newline"); } emit("rerun-if-env-changed", key); } /// The `rustc-link-arg` instruction tells Cargo to pass the /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building /// supported targets (benchmarks, binaries, cdylib crates, examples, and tests). /// /// Its usage is highly platform specific. It is useful to set the shared library /// version or linker script. /// /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg #[track_caller] pub fn rustc_link_arg(flag: &str) { if flag.contains([' ', '\n']) { panic!("cannot emit rustc-link-arg: invalid flag {flag:?}"); } emit("rustc-link-arg", flag); } /// The `rustc-link-arg-bin` instruction tells Cargo to pass the /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building /// the binary target with name `BIN`. Its usage is highly platform specific. /// /// It /// is useful to set a linker script or other linker options. /// /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg #[track_caller] pub fn rustc_link_arg_bin(bin: &str, flag: &str) { if !is_ident(bin) { panic!("cannot emit rustc-link-arg-bin: invalid bin name {bin:?}"); } if flag.contains([' ', '\n']) { panic!("cannot emit rustc-link-arg-bin: invalid flag {flag:?}"); } emit("rustc-link-arg-bin", format_args!("{bin}={flag}")); } /// The `rustc-link-arg-bins` instruction tells Cargo to pass the /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building /// the binary target. /// /// Its usage is highly platform specific. It is useful to set /// a linker script or other linker options. /// /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg #[track_caller] pub fn rustc_link_arg_bins(flag: &str) { if flag.contains([' ', '\n']) { panic!("cannot emit rustc-link-arg-bins: invalid flag {flag:?}"); } emit("rustc-link-arg-bins", flag); } /// The `rustc-link-arg-tests` instruction tells Cargo to pass the /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building /// a tests target. /// /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg #[track_caller] pub fn rustc_link_arg_tests(flag: &str) { if flag.contains([' ', '\n']) { panic!("cannot emit rustc-link-arg-tests: invalid flag {flag:?}"); } emit("rustc-link-arg-tests", flag); } /// The `rustc-link-arg-examples` instruction tells Cargo to pass the /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building /// an examples target. /// /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg #[track_caller] pub fn rustc_link_arg_examples(flag: &str) { if flag.contains([' ', '\n']) { panic!("cannot emit rustc-link-arg-examples: invalid flag {flag:?}"); } emit("rustc-link-arg-examples", flag); } /// The `rustc-link-arg-benches` instruction tells Cargo to pass the /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building /// a benchmark target. /// /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg #[track_caller] pub fn rustc_link_arg_benches(flag: &str) { if flag.contains([' ', '\n']) { panic!("cannot emit rustc-link-arg-benches: invalid flag {flag:?}"); } emit("rustc-link-arg-benches", flag); } /// The `rustc-link-lib` instruction tells Cargo to link the given library using /// the compiler’s [`-l` flag][-l]. /// /// This is typically used to link a native library /// using [FFI]. /// /// The `LIB` string is passed directly to rustc, so it supports any syntax that /// `-l` does. Currently the full supported syntax for `LIB` is /// `[KIND[:MODIFIERS]=]NAME[:RENAME]`. /// /// The `-l` flag is only passed to the library target of the package, unless there /// is no library target, in which case it is passed to all targets. This is done /// because all other targets have an implicit dependency on the library target, /// and the given library to link should only be included once. This means that /// if a package has both a library and a binary target, the library has access /// to the symbols from the given lib, and the binary should access them through /// the library target’s public API. /// /// The optional `KIND` may be one of `dylib`, `static`, or `framework`. See the /// [rustc book][-l] for more detail. /// /// [-l]: https://doc.rust-lang.org/stable/rustc/command-line-arguments.html#option-l-link-lib /// [FFI]: https://doc.rust-lang.org/stable/nomicon/ffi.html #[track_caller] pub fn rustc_link_lib(lib: &str) { if lib.contains([' ', '\n']) { panic!("cannot emit rustc-link-lib: invalid lib {lib:?}"); } emit("rustc-link-lib", lib); } /// Like [`rustc_link_lib`], but with `KIND[:MODIFIERS]` specified separately. #[track_caller] pub fn rustc_link_lib_kind(kind: &str, lib: &str) { if kind.contains(['=', ' ', '\n']) { panic!("cannot emit rustc-link-lib: invalid kind {kind:?}"); } if lib.contains([' ', '\n']) { panic!("cannot emit rustc-link-lib: invalid lib {lib:?}"); } emit("rustc-link-lib", format_args!("{kind}={lib}")); } /// The `rustc-link-search` instruction tells Cargo to pass the [`-L` flag] to the /// compiler to add a directory to the library search path. /// /// The optional `KIND` may be one of `dependency`, `crate`, `native`, `framework`, /// or `all`. See the [rustc book][-L] for more detail. /// /// These paths are also added to the /// [dynamic library search path environment variable][search-path] if they are /// within the `OUT_DIR`. Depending on this behavior is discouraged since this /// makes it difficult to use the resulting binary. In general, it is best to /// avoid creating dynamic libraries in a build script (using existing system /// libraries is fine). /// /// [-L]: https://doc.rust-lang.org/stable/rustc/command-line-arguments.html#option-l-search-path /// [search-path]: https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#dynamic-library-paths #[track_caller] pub fn rustc_link_search(path: impl AsRef) { let Some(path) = path.as_ref().to_str() else { panic!("cannot emit rustc-link-search: path is not UTF-8"); }; if path.contains('\n') { panic!("cannot emit rustc-link-search: path contains newline"); } emit("rustc-link-search", path); } /// Like [`rustc_link_search`], but with KIND specified separately. #[track_caller] pub fn rustc_link_search_kind(kind: &str, path: impl AsRef) { if kind.contains(['=', '\n']) { panic!("cannot emit rustc-link-search: invalid kind {kind:?}"); } let Some(path) = path.as_ref().to_str() else { panic!("cannot emit rustc-link-search: path is not UTF-8"); }; if path.contains('\n') { panic!("cannot emit rustc-link-search: path contains newline"); } emit("rustc-link-search", format_args!("{kind}={path}")); } /// The `rustc-flags` instruction tells Cargo to pass the given space-separated /// flags to the compiler. /// /// This only allows the `-l` and `-L` flags, and is /// equivalent to using [`rustc_link_lib`] and [`rustc_link_search`]. #[track_caller] pub fn rustc_flags(flags: &str) { if flags.contains('\n') { panic!("cannot emit rustc-flags: invalid flags"); } emit("rustc-flags", flags); } /// The `rustc-cfg` instruction tells Cargo to pass the given value to the /// [`--cfg` flag][cfg] to the compiler. /// /// This may be used for compile-time /// detection of features to enable conditional compilation. /// /// Note that this does not affect Cargo’s dependency resolution. This cannot /// be used to enable an optional dependency, or enable other Cargo features. /// /// Be aware that [Cargo features] use the form `feature="foo"`. `cfg` values /// passed with this flag are not restricted to that form, and may provide just /// a single identifier, or any arbitrary key/value pair. For example, emitting /// `rustc_cfg("abc")` will then allow code to use `#[cfg(abc)]` (note the lack /// of `feature=`). Or an arbitrary key/value pair may be used with an `=` symbol /// like `rustc_cfg(r#"my_component="foo""#)`. The key should be a Rust identifier, /// the value should be a string. /// /// [cfg]: https://doc.rust-lang.org/rustc/command-line-arguments.html#option-cfg /// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html #[track_caller] pub fn rustc_cfg(key: &str) { if !is_ident(key) { panic!("cannot emit rustc-cfg: invalid key {key:?}"); } emit("rustc-cfg", key); } /// Like [`rustc_cfg`], but with the value specified separately. /// /// To replace the /// less convenient `rustc_cfg(r#"my_component="foo""#)`, you can instead use /// `rustc_cfg_value("my_component", "foo")`. #[track_caller] pub fn rustc_cfg_value(key: &str, value: &str) { if !is_ident(key) { panic!("cannot emit rustc-cfg-value: invalid key"); } let value = value.escape_default(); emit("rustc-cfg", format_args!("{key}=\"{value}\"")); } /// Add to the list of expected config names that is used when checking the /// *reachable* cfg expressions with the [`unexpected_cfgs`] lint. /// /// This form is for keys without an expected value, such as `cfg(name)`. /// /// It is recommended to group the `rustc_check_cfg` and `rustc_cfg` calls as /// closely as possible in order to avoid typos, missing check_cfg, stale cfgs, /// and other mistakes. /// /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs #[doc = respected_msrv!("1.80")] #[track_caller] pub fn rustc_check_cfgs(keys: &[&str]) { if keys.is_empty() { return; } for key in keys { if !is_ident(key) { panic!("cannot emit rustc-check-cfg: invalid key {key:?}"); } } let mut directive = keys[0].to_string(); for key in &keys[1..] { write!(directive, ", {key}").expect("writing to string should be infallible"); } emit("rustc-check-cfg", format_args!("cfg({directive})")); } /// Add to the list of expected config names that is used when checking the /// *reachable* cfg expressions with the [`unexpected_cfgs`] lint. /// /// This form is for keys with expected values, such as `cfg(name = "value")`. /// /// It is recommended to group the `rustc_check_cfg` and `rustc_cfg` calls as /// closely as possible in order to avoid typos, missing check_cfg, stale cfgs, /// and other mistakes. /// /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs #[doc = respected_msrv!("1.80")] #[track_caller] pub fn rustc_check_cfg_values(key: &str, values: &[&str]) { if !is_ident(key) { panic!("cannot emit rustc-check-cfg: invalid key {key:?}"); } if values.is_empty() { rustc_check_cfgs(&[key]); return; } let mut directive = format!("\"{}\"", values[0].escape_default()); for value in &values[1..] { write!(directive, ", \"{}\"", value.escape_default()) .expect("writing to string should be infallible"); } emit( "rustc-check-cfg", format_args!("cfg({key}, values({directive}))"), ); } /// The `rustc-env` instruction tells Cargo to set the given environment variable /// when compiling the package. /// /// The value can be then retrieved by the /// [`env!` macro][env!] in the compiled crate. This is useful for embedding /// additional metadata in crate’s code, such as the hash of git HEAD or the /// unique identifier of a continuous integration server. /// /// See also the [environment variables automatically included by Cargo][cargo-env]. /// /// [cargo-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates #[track_caller] pub fn rustc_env(key: &str, value: &str) { if key.contains(['=', '\n']) { panic!("cannot emit rustc-env: invalid key {key:?}"); } if value.contains('\n') { panic!("cannot emit rustc-env: invalid value {value:?}"); } emit("rustc-env", format_args!("{key}={value}")); } /// The `rustc-cdylib-link-arg` instruction tells Cargo to pass the /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building /// a `cdylib` library target. /// /// Its usage is highly platform specific. It is useful /// to set the shared library version or the runtime-path. /// /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg #[track_caller] pub fn rustc_cdylib_link_arg(flag: &str) { if flag.contains('\n') { panic!("cannot emit rustc-cdylib-link-arg: invalid flag {flag:?}"); } emit("rustc-cdylib-link-arg", flag); } /// The `warning` instruction tells Cargo to display a warning after the build /// script has finished running. /// /// Warnings are only shown for path dependencies /// (that is, those you’re working on locally), so for example warnings printed /// out in [crates.io] crates are not emitted by default. The `-vv` “very verbose” /// flag may be used to have Cargo display warnings for all crates. /// /// [crates.io]: https://crates.io/ #[track_caller] pub fn warning(message: &str) { if message.contains('\n') { panic!("cannot emit warning: message contains newline"); } emit("warning", message); } /// The `error` instruction tells Cargo to display an error after the build script has finished /// running, and then fail the build. /// ///
/// /// Build script libraries should carefully consider if they want to use [`error`] versus /// returning a `Result`. It may be better to return a `Result`, and allow the caller to decide if the /// error is fatal or not. The caller can then decide whether or not to display the `Err` variant /// using [`error`]. /// ///
#[doc = respected_msrv!("1.84")] #[track_caller] pub fn error(message: &str) { if message.contains('\n') { panic!("cannot emit error: message contains newline"); } emit("error", message); } /// Metadata, used by `links` scripts. #[track_caller] pub fn metadata(key: &str, val: &str) { if !is_ascii_ident(key) { panic!("cannot emit metadata: invalid key {key:?}"); } if val.contains('\n') { panic!("cannot emit metadata: invalid value {val:?}"); } emit("metadata", format_args!("{key}={val}")); } ================================================ FILE: crates/build-rs-test-lib/Cargo.toml ================================================ [package] name = "build-rs-test-lib" version = "0.0.0" edition.workspace = true publish = false [features] unstable = ["build-rs/unstable"] [build-dependencies] build-rs.workspace = true ================================================ FILE: crates/build-rs-test-lib/build.rs ================================================ fn main() { smoke_test_inputs(); build_rs::output::rerun_if_changed("build.rs"); build_rs::output::rustc_check_cfgs(&["did_run_build_script"]); build_rs::output::rustc_cfg("did_run_build_script"); } fn smoke_test_inputs() { use build_rs::input::*; dbg!(cargo()); dbg!(cargo_cfg_feature()); dbg!(cargo_cfg("careful")); dbg!(cargo_cfg_debug_assertions()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_fmt_debug()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_overflow_checks()); dbg!(cargo_cfg_panic()); dbg!(cargo_cfg_proc_macro()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_relocation_model()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_sanitize()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_sanitizer_cfi_generalize_pointers()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_sanitizer_cfi_normalize_integers()); dbg!(cargo_cfg_target_abi()); dbg!(cargo_cfg_target_arch()); dbg!(cargo_cfg_target_endian()); dbg!(cargo_cfg_target_env()); dbg!(cargo_cfg_target_feature()); dbg!(cargo_cfg_target_has_atomic()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_target_has_atomic_equal_alignment()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_target_has_atomic_load_store()); dbg!(cargo_cfg_target_os()); dbg!(cargo_cfg_target_pointer_width()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_target_thread_local()); dbg!(cargo_cfg_target_vendor()); #[cfg(feature = "unstable")] dbg!(cargo_cfg_ub_checks()); dbg!(cargo_cfg_unix()); dbg!(cargo_cfg_windows()); dbg!(cargo_encoded_rustflags()); dbg!(cargo_feature("unstable")); dbg!(cargo_manifest_dir()); dbg!(cargo_manifest_path()); dbg!(cargo_manifest_links()); dbg!(cargo_pkg_authors()); dbg!(cargo_pkg_description()); dbg!(cargo_pkg_homepage()); dbg!(cargo_pkg_license()); dbg!(cargo_pkg_license_file()); dbg!(cargo_pkg_name()); dbg!(cargo_pkg_readme()); dbg!(cargo_pkg_repository()); dbg!(cargo_pkg_rust_version()); dbg!(cargo_pkg_version()); dbg!(cargo_pkg_version_major()); dbg!(cargo_pkg_version_minor()); dbg!(cargo_pkg_version_patch()); dbg!(cargo_pkg_version_pre()); dbg!(debug()); dbg!(dep_metadata("z", "include")); dbg!(host()); dbg!(num_jobs()); dbg!(opt_level()); dbg!(out_dir()); dbg!(profile()); dbg!(rustc()); dbg!(rustc_linker()); dbg!(rustc_workspace_wrapper()); dbg!(rustc_wrapper()); dbg!(rustdoc()); dbg!(target()); } ================================================ FILE: crates/build-rs-test-lib/src/lib.rs ================================================ #[test] fn test() { const { assert!(cfg!(did_run_build_script)) }; } ================================================ FILE: crates/cargo-platform/Cargo.toml ================================================ [package] name = "cargo-platform" version = "0.3.3" edition.workspace = true license.workspace = true rust-version.workspace = true repository.workspace = true documentation = "https://docs.rs/cargo-platform" description = "Cargo's representation of a target platform." [dependencies] serde_core.workspace = true # serde v1.0.220 is the first version that released with `serde_core`. # This is required to avoid conflict with other `serde` users which may require an older version. [target.'cfg(any())'.dependencies] serde.workspace = true [lints] workspace = true ================================================ FILE: crates/cargo-platform/README.md ================================================ > This crate is maintained by the Cargo team for use by the wider > ecosystem. This crate follows semver compatibility for its APIs. ================================================ FILE: crates/cargo-platform/examples/matches.rs ================================================ //! This example demonstrates how to filter a Platform based on the current //! host target. #![allow(clippy::print_stdout)] use cargo_platform::{Cfg, Platform}; use std::process::Command; use std::str::FromStr; static EXAMPLES: &[&str] = &[ "cfg(windows)", "cfg(unix)", "cfg(target_os=\"macos\")", "cfg(target_os=\"linux\")", "cfg(any(target_arch=\"x86\", target_arch=\"x86_64\"))", ]; fn main() { let target = get_target(); let cfgs = get_cfgs(); println!("host target={} cfgs:", target); for cfg in &cfgs { println!(" {}", cfg); } let mut examples: Vec<&str> = EXAMPLES.iter().copied().collect(); examples.push(target.as_str()); for example in examples { let p = Platform::from_str(example).unwrap(); println!("{:?} matches: {:?}", example, p.matches(&target, &cfgs)); } } fn get_target() -> String { let output = Command::new("rustc") .arg("-Vv") .output() .expect("rustc failed to run"); let stdout = String::from_utf8(output.stdout).unwrap(); for line in stdout.lines() { if let Some(line) = line.strip_prefix("host: ") { return String::from(line); } } panic!("Failed to find host: {}", stdout); } fn get_cfgs() -> Vec { let output = Command::new("rustc") .arg("--print=cfg") .output() .expect("rustc failed to run"); let stdout = String::from_utf8(output.stdout).unwrap(); stdout .lines() .map(|line| Cfg::from_str(line).unwrap()) .collect() } ================================================ FILE: crates/cargo-platform/src/cfg.rs ================================================ use crate::error::{ParseError, ParseErrorKind::*}; use std::fmt; use std::hash::{Hash, Hasher}; use std::iter; use std::str::{self, FromStr}; /// A cfg expression. #[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] pub enum CfgExpr { Not(Box), All(Vec), Any(Vec), Value(Cfg), True, False, } /// A cfg value. #[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] pub enum Cfg { /// A named cfg value, like `unix`. Name(Ident), /// A key/value cfg pair, like `target_os = "linux"`. KeyPair(Ident, String), } /// A identifier #[derive(Eq, Ord, PartialOrd, Clone, Debug)] pub struct Ident { /// The identifier pub name: String, /// Is this a raw ident: `r#async` /// /// It's mainly used for display and doesn't take /// part in the hash or equality (`foo` == `r#foo`). pub raw: bool, } #[derive(PartialEq)] enum Token<'a> { LeftParen, RightParen, Ident(bool, &'a str), Comma, Equals, String(&'a str), } /// The list of keywords. /// /// We should consider all the keywords, but some are conditional on /// the edition so for now we just consider true/false. /// /// pub(crate) const KEYWORDS: &[&str; 2] = &["true", "false"]; #[derive(Clone)] struct Tokenizer<'a> { s: iter::Peekable>, orig: &'a str, } struct Parser<'a> { t: Tokenizer<'a>, } impl Ident { pub fn as_str(&self) -> &str { &self.name } } impl Hash for Ident { fn hash(&self, state: &mut H) { self.name.hash(state); } } impl PartialEq for Ident { fn eq(&self, other: &str) -> bool { self.name == other } } impl PartialEq<&str> for Ident { fn eq(&self, other: &&str) -> bool { self.name == *other } } impl PartialEq for Ident { fn eq(&self, other: &Ident) -> bool { self.name == other.name } } impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.raw { f.write_str("r#")?; } f.write_str(&*self.name) } } impl FromStr for Cfg { type Err = ParseError; fn from_str(s: &str) -> Result { let mut p = Parser::new(s); let e = p.cfg()?; if let Some(rest) = p.rest() { return Err(ParseError::new( p.t.orig, UnterminatedExpression(rest.to_string()), )); } Ok(e) } } impl fmt::Display for Cfg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Cfg::Name(ref s) => s.fmt(f), Cfg::KeyPair(ref k, ref v) => write!(f, "{} = \"{}\"", k, v), } } } impl CfgExpr { /// Utility function to check if the key, "cfg(..)" matches the `target_cfg` pub fn matches_key(key: &str, target_cfg: &[Cfg]) -> bool { if key.starts_with("cfg(") && key.ends_with(')') { let cfg = &key[4..key.len() - 1]; CfgExpr::from_str(cfg) .ok() .map(|ce| ce.matches(target_cfg)) .unwrap_or(false) } else { false } } pub fn matches(&self, cfg: &[Cfg]) -> bool { match *self { CfgExpr::Not(ref e) => !e.matches(cfg), CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg)), CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg)), CfgExpr::Value(ref e) => cfg.contains(e), CfgExpr::True => true, CfgExpr::False => false, } } } impl FromStr for CfgExpr { type Err = ParseError; fn from_str(s: &str) -> Result { let mut p = Parser::new(s); let e = p.expr()?; if let Some(rest) = p.rest() { return Err(ParseError::new( p.t.orig, UnterminatedExpression(rest.to_string()), )); } Ok(e) } } impl fmt::Display for CfgExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { CfgExpr::Not(ref e) => write!(f, "not({})", e), CfgExpr::All(ref e) => write!(f, "all({})", CommaSep(e)), CfgExpr::Any(ref e) => write!(f, "any({})", CommaSep(e)), CfgExpr::Value(ref e) => write!(f, "{}", e), CfgExpr::True => write!(f, "true"), CfgExpr::False => write!(f, "false"), } } } struct CommaSep<'a, T>(&'a [T]); impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, v) in self.0.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", v)?; } Ok(()) } } impl<'a> Parser<'a> { fn new(s: &'a str) -> Parser<'a> { Parser { t: Tokenizer { s: s.char_indices().peekable(), orig: s, }, } } fn expr(&mut self) -> Result { match self.peek() { Some(Ok(Token::Ident(false, op @ "all"))) | Some(Ok(Token::Ident(false, op @ "any"))) => { self.t.next(); let mut e = Vec::new(); self.eat(&Token::LeftParen)?; while !self.r#try(&Token::RightParen) { e.push(self.expr()?); if !self.r#try(&Token::Comma) { self.eat(&Token::RightParen)?; break; } } if op == "all" { Ok(CfgExpr::All(e)) } else { Ok(CfgExpr::Any(e)) } } Some(Ok(Token::Ident(false, "not"))) => { self.t.next(); self.eat(&Token::LeftParen)?; let e = self.expr()?; self.eat(&Token::RightParen)?; Ok(CfgExpr::Not(Box::new(e))) } Some(Ok(..)) => self.cfg().map(|v| match v { Cfg::Name(n) if n == "true" => CfgExpr::True, Cfg::Name(n) if n == "false" => CfgExpr::False, v => CfgExpr::Value(v), }), Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()), None => Err(ParseError::new( self.t.orig, IncompleteExpr("start of a cfg expression"), )), } } fn cfg(&mut self) -> Result { match self.t.next() { Some(Ok(Token::Ident(raw, name))) => { let e = if self.r#try(&Token::Equals) { let val = match self.t.next() { Some(Ok(Token::String(s))) => s, Some(Ok(t)) => { return Err(ParseError::new( self.t.orig, UnexpectedToken { expected: "a string", found: t.classify(), }, )); } Some(Err(e)) => return Err(e), None => { return Err(ParseError::new(self.t.orig, IncompleteExpr("a string"))); } }; Cfg::KeyPair( Ident { name: name.to_string(), raw, }, val.to_string(), ) } else { Cfg::Name(Ident { name: name.to_string(), raw, }) }; Ok(e) } Some(Ok(t)) => Err(ParseError::new( self.t.orig, UnexpectedToken { expected: "identifier", found: t.classify(), }, )), Some(Err(e)) => Err(e), None => Err(ParseError::new(self.t.orig, IncompleteExpr("identifier"))), } } fn peek(&mut self) -> Option, ParseError>> { self.t.clone().next() } fn r#try(&mut self, token: &Token<'a>) -> bool { match self.peek() { Some(Ok(ref t)) if token == t => {} _ => return false, } self.t.next(); true } fn eat(&mut self, token: &Token<'a>) -> Result<(), ParseError> { match self.t.next() { Some(Ok(ref t)) if token == t => Ok(()), Some(Ok(t)) => Err(ParseError::new( self.t.orig, UnexpectedToken { expected: token.classify(), found: t.classify(), }, )), Some(Err(e)) => Err(e), None => Err(ParseError::new( self.t.orig, IncompleteExpr(token.classify()), )), } } /// Returns the rest of the input from the current location. fn rest(&self) -> Option<&str> { let mut s = self.t.s.clone(); loop { match s.next() { Some((_, ' ')) => {} Some((start, _ch)) => return Some(&self.t.orig[start..]), None => return None, } } } } impl<'a> Iterator for Tokenizer<'a> { type Item = Result, ParseError>; fn next(&mut self) -> Option, ParseError>> { loop { match self.s.next() { Some((_, ' ')) => {} Some((_, '(')) => return Some(Ok(Token::LeftParen)), Some((_, ')')) => return Some(Ok(Token::RightParen)), Some((_, ',')) => return Some(Ok(Token::Comma)), Some((_, '=')) => return Some(Ok(Token::Equals)), Some((start, '"')) => { while let Some((end, ch)) = self.s.next() { if ch == '"' { return Some(Ok(Token::String(&self.orig[start + 1..end]))); } } return Some(Err(ParseError::new(self.orig, UnterminatedString))); } Some((start, ch)) if is_ident_start(ch) => { let (start, raw) = if ch == 'r' { if let Some(&(_pos, '#')) = self.s.peek() { // starts with `r#` is a raw ident self.s.next(); if let Some((start, ch)) = self.s.next() { if is_ident_start(ch) { (start, true) } else { // not a starting ident character return Some(Err(ParseError::new( self.orig, UnexpectedChar(ch), ))); } } else { // not followed by a ident, error out return Some(Err(ParseError::new( self.orig, IncompleteExpr("identifier"), ))); } } else { // starts with `r` but not does continue with `#` // cannot be a raw ident (start, false) } } else { // do not start with `r`, cannot be a raw ident (start, false) }; while let Some(&(end, ch)) = self.s.peek() { if !is_ident_rest(ch) { return Some(Ok(Token::Ident(raw, &self.orig[start..end]))); } else { self.s.next(); } } return Some(Ok(Token::Ident(raw, &self.orig[start..]))); } Some((_, ch)) => { return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch)))); } None => return None, } } } } fn is_ident_start(ch: char) -> bool { ch == '_' || ch.is_ascii_alphabetic() } fn is_ident_rest(ch: char) -> bool { is_ident_start(ch) || ch.is_ascii_digit() } impl<'a> Token<'a> { fn classify(&self) -> &'static str { match *self { Token::LeftParen => "`(`", Token::RightParen => "`)`", Token::Ident(..) => "an identifier", Token::Comma => "`,`", Token::Equals => "`=`", Token::String(..) => "a string", } } } ================================================ FILE: crates/cargo-platform/src/error.rs ================================================ use std::fmt; #[derive(Debug)] pub struct ParseError { kind: ParseErrorKind, orig: String, } #[non_exhaustive] #[derive(Debug)] pub enum ParseErrorKind { UnterminatedString, UnexpectedChar(char), UnexpectedToken { expected: &'static str, found: &'static str, }, IncompleteExpr(&'static str), UnterminatedExpression(String), InvalidTarget(String), } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "failed to parse `{}` as a cfg expression: {}", self.orig, self.kind ) } } impl fmt::Display for ParseErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use ParseErrorKind::*; match self { UnterminatedString => write!(f, "unterminated string in cfg"), UnexpectedChar(ch) => write!( f, "unexpected character `{}` in cfg, expected parens, a comma, \ an identifier, or a string", ch ), UnexpectedToken { expected, found } => { write!(f, "expected {}, found {}", expected, found) } IncompleteExpr(expected) => { write!(f, "expected {}, but cfg expression ended", expected) } UnterminatedExpression(s) => { write!(f, "unexpected content `{}` found after cfg expression", s) } InvalidTarget(s) => write!(f, "invalid target specifier: {}", s), } } } impl std::error::Error for ParseError {} impl ParseError { pub fn new(orig: &str, kind: ParseErrorKind) -> ParseError { ParseError { kind, orig: orig.to_string(), } } } ================================================ FILE: crates/cargo-platform/src/lib.rs ================================================ //! Platform definition used by Cargo. //! //! This defines a [`Platform`] type which is used in Cargo to specify a target platform. //! There are two kinds, a named target like `x86_64-apple-darwin`, and a "cfg expression" //! like `cfg(any(target_os = "macos", target_os = "ios"))`. //! //! See `examples/matches.rs` for an example of how to match against a `Platform`. //! //! > This crate is maintained by the Cargo team for use by the wider //! > ecosystem. This crate follows semver compatibility for its APIs. //! //! [`Platform`]: enum.Platform.html use std::str::FromStr; use std::{fmt, path::Path}; mod cfg; mod error; use cfg::KEYWORDS; pub use cfg::{Cfg, CfgExpr, Ident}; pub use error::{ParseError, ParseErrorKind}; /// Platform definition. #[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] pub enum Platform { /// A named platform, like `x86_64-apple-darwin`. Name(String), /// A cfg expression, like `cfg(windows)`. Cfg(CfgExpr), } impl Platform { /// Returns whether the Platform matches the given target and cfg. /// /// The named target and cfg values should be obtained from `rustc`. pub fn matches(&self, name: &str, cfg: &[Cfg]) -> bool { match *self { Platform::Name(ref p) => p == name, Platform::Cfg(ref p) => p.matches(cfg), } } fn validate_named_platform(name: &str) -> Result<(), ParseError> { if let Some(ch) = name .chars() .find(|&c| !(c.is_alphanumeric() || c == '_' || c == '-' || c == '.')) { if name.chars().any(|c| c == '(') { return Err(ParseError::new( name, ParseErrorKind::InvalidTarget( "unexpected `(` character, cfg expressions must start with `cfg(`" .to_string(), ), )); } return Err(ParseError::new( name, ParseErrorKind::InvalidTarget(format!( "unexpected character {} in target name", ch )), )); } Ok(()) } pub fn check_cfg_attributes(&self, warnings: &mut Vec) { fn check_cfg_expr(expr: &CfgExpr, warnings: &mut Vec) { match *expr { CfgExpr::Not(ref e) => check_cfg_expr(e, warnings), CfgExpr::All(ref e) | CfgExpr::Any(ref e) => { for e in e { check_cfg_expr(e, warnings); } } CfgExpr::Value(ref e) => match e { Cfg::Name(name) => match name.as_str() { "test" | "debug_assertions" | "proc_macro" => warnings.push(format!( "Found `{}` in `target.'cfg(...)'.dependencies`. \ This value is not supported for selecting dependencies \ and will not work as expected. \ To learn more visit \ https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies", name )), _ => (), }, Cfg::KeyPair(name, _) => if name.as_str() == "feature" { warnings.push(String::from( "Found `feature = ...` in `target.'cfg(...)'.dependencies`. \ This key is not supported for selecting dependencies \ and will not work as expected. \ Use the [features] section instead: \ https://doc.rust-lang.org/cargo/reference/features.html" )) }, } CfgExpr::True | CfgExpr::False => {}, } } if let Platform::Cfg(cfg) = self { check_cfg_expr(cfg, warnings); } } pub fn check_cfg_keywords(&self, warnings: &mut Vec, path: &Path) { fn check_cfg_expr(expr: &CfgExpr, warnings: &mut Vec, path: &Path) { match *expr { CfgExpr::Not(ref e) => check_cfg_expr(e, warnings, path), CfgExpr::All(ref e) | CfgExpr::Any(ref e) => { for e in e { check_cfg_expr(e, warnings, path); } } CfgExpr::True | CfgExpr::False => {} CfgExpr::Value(ref e) => match e { Cfg::Name(name) | Cfg::KeyPair(name, _) => { if !name.raw && KEYWORDS.contains(&name.as_str()) { warnings.push(format!( "[{}] future-incompatibility: `cfg({e})` is deprecated as `{name}` is a keyword \ and not an identifier and should not have have been accepted in this position.\n \ | this was previously accepted by Cargo but is being phased out; it will become a hard error in a future release!\n \ |\n \ | help: use raw-idents instead: `cfg(r#{name})`", path.display() )); } } }, } } if let Platform::Cfg(cfg) = self { check_cfg_expr(cfg, warnings, path); } } } impl serde_core::Serialize for Platform { fn serialize(&self, s: S) -> Result where S: serde_core::Serializer, { self.to_string().serialize(s) } } impl<'de> serde_core::Deserialize<'de> for Platform { fn deserialize(deserializer: D) -> Result where D: serde_core::Deserializer<'de>, { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(serde_core::de::Error::custom) } } impl FromStr for Platform { type Err = ParseError; fn from_str(s: &str) -> Result { if let Some(s) = s.strip_prefix("cfg(").and_then(|s| s.strip_suffix(')')) { s.parse().map(Platform::Cfg) } else { Platform::validate_named_platform(s)?; Ok(Platform::Name(s.to_string())) } } } impl fmt::Display for Platform { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Platform::Name(ref n) => n.fmt(f), Platform::Cfg(ref e) => write!(f, "cfg({})", e), } } } ================================================ FILE: crates/cargo-platform/tests/test_cfg.rs ================================================ use cargo_platform::{Cfg, CfgExpr, Ident, Platform}; use std::fmt; use std::str::FromStr; macro_rules! c { ($a:ident) => { Cfg::Name(Ident { name: stringify!($a).to_string(), raw: false, }) }; (r # $a:ident) => { Cfg::Name(Ident { name: stringify!($a).to_string(), raw: true, }) }; ($a:ident = $e:expr) => { Cfg::KeyPair( Ident { name: stringify!($a).to_string(), raw: false, }, $e.to_string(), ) }; (r # $a:ident = $e:expr) => { Cfg::KeyPair( Ident { name: stringify!($a).to_string(), raw: true, }, $e.to_string(), ) }; } macro_rules! e { (any($($t:tt),*)) => (CfgExpr::Any(vec![$(e!($t)),*])); (all($($t:tt),*)) => (CfgExpr::All(vec![$(e!($t)),*])); (not($($t:tt)*)) => (CfgExpr::Not(Box::new(e!($($t)*)))); (true) => (CfgExpr::True); (false) => (CfgExpr::False); (($($t:tt)*)) => (e!($($t)*)); ($($t:tt)*) => (CfgExpr::Value(c!($($t)*))); } fn good(s: &str, expected: T) where T: FromStr + PartialEq + fmt::Debug, T::Err: fmt::Display, { let c = match T::from_str(s) { Ok(c) => c, Err(e) => panic!("failed to parse `{}`: {}", s, e), }; assert_eq!(c, expected); } fn bad(s: &str, err: &str) where T: FromStr + fmt::Display, T::Err: fmt::Display, { let e = match T::from_str(s) { Ok(cfg) => panic!("expected `{}` to not parse but got {}", s, cfg), Err(e) => e.to_string(), }; assert!( e.contains(err), "when parsing `{}`,\n\"{}\" not contained \ inside: {}", s, err, e ); } #[test] fn cfg_syntax() { good("foo", c!(foo)); good("_bar", c!(_bar)); good(" foo", c!(foo)); good(" foo ", c!(foo)); good("r#foo", c!(r # foo)); good(" foo = \"bar\"", c!(foo = "bar")); good("foo=\"\"", c!(foo = "")); good("r#foo=\"\"", c!(r # foo = "")); good(" foo=\"3\" ", c!(foo = "3")); good("foo = \"3 e\"", c!(foo = "3 e")); good(" r#foo = \"3 e\"", c!(r # foo = "3 e")); } #[test] fn cfg_syntax_bad() { bad::("", "but cfg expression ended"); bad::(" ", "but cfg expression ended"); bad::("\t", "unexpected character"); bad::("7", "unexpected character"); bad::("=", "expected identifier"); bad::(",", "expected identifier"); bad::("(", "expected identifier"); bad::("foo (", "unexpected content `(` found after cfg expression"); bad::("bar =", "expected a string"); bad::("bar = \"", "unterminated string"); bad::( "foo, bar", "unexpected content `, bar` found after cfg expression", ); bad::("r# foo", "unexpected character"); bad::("r #foo", "unexpected content"); bad::("r#\"foo\"", "unexpected character"); bad::("foo = r#\"\"", "unexpected character"); } #[test] fn cfg_expr() { good("foo", e!(foo)); good("_bar", e!(_bar)); good(" foo", e!(foo)); good(" foo ", e!(foo)); good(" foo = \"bar\"", e!(foo = "bar")); good("foo=\"\"", e!(foo = "")); good(" foo=\"3\" ", e!(foo = "3")); good("foo = \"3 e\"", e!(foo = "3 e")); good("true", e!(true)); good("false", e!(false)); good("all()", e!(all())); good("all(a)", e!(all(a))); good("all(a, b)", e!(all(a, b))); good("all(a, )", e!(all(a))); good("not(a = \"b\")", e!(not(a = "b"))); good("not(all(a))", e!(not(all(a)))); } #[test] fn cfg_expr_bad() { bad::(" ", "but cfg expression ended"); bad::(" all", "expected `(`"); bad::("all(a", "expected `)`"); bad::("not", "expected `(`"); bad::("not(a", "expected `)`"); bad::("a = ", "expected a string"); bad::("all(not())", "expected identifier"); bad::( "foo(a)", "unexpected content `(a)` found after cfg expression", ); } #[test] fn cfg_matches() { assert!(e!(foo).matches(&[c!(bar), c!(foo), c!(baz)])); assert!(e!(any(foo)).matches(&[c!(bar), c!(foo), c!(baz)])); assert!(e!(any(foo, bar)).matches(&[c!(bar)])); assert!(e!(any(foo, bar)).matches(&[c!(foo)])); assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)])); assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)])); assert!(e!(not(foo)).matches(&[c!(bar)])); assert!(e!(not(foo)).matches(&[])); assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)])); assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)])); assert!(e!(foo).matches(&[c!(r # foo)])); assert!(e!(r # foo).matches(&[c!(foo)])); assert!(e!(r # foo).matches(&[c!(r # foo)])); assert!(!e!(foo).matches(&[])); assert!(!e!(foo).matches(&[c!(bar)])); assert!(!e!(foo).matches(&[c!(fo)])); assert!(!e!(any(foo)).matches(&[])); assert!(!e!(any(foo)).matches(&[c!(bar)])); assert!(!e!(any(foo)).matches(&[c!(bar), c!(baz)])); assert!(!e!(all(foo)).matches(&[c!(bar), c!(baz)])); assert!(!e!(all(foo, bar)).matches(&[c!(bar)])); assert!(!e!(all(foo, bar)).matches(&[c!(foo)])); assert!(!e!(all(foo, bar)).matches(&[])); assert!(!e!(not(bar)).matches(&[c!(bar)])); assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)])); assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)])); } #[test] fn bad_target_name() { bad::( "any(cfg(unix), cfg(windows))", "failed to parse `any(cfg(unix), cfg(windows))` as a cfg expression: \ invalid target specifier: unexpected `(` character, \ cfg expressions must start with `cfg(`", ); bad::( "!foo", "failed to parse `!foo` as a cfg expression: \ invalid target specifier: unexpected character ! in target name", ); } #[test] fn round_trip_platform() { fn rt(s: &str) { let p = Platform::from_str(s).unwrap(); let s2 = p.to_string(); let p2 = Platform::from_str(&s2).unwrap(); assert_eq!(p, p2); } rt("x86_64-apple-darwin"); rt("foo"); rt("cfg(windows)"); rt("cfg(target_os = \"windows\")"); rt( "cfg(any(all(any(target_os = \"android\", target_os = \"linux\"), \ any(target_arch = \"aarch64\", target_arch = \"arm\", target_arch = \"powerpc64\", \ target_arch = \"x86\", target_arch = \"x86_64\")), \ all(target_os = \"freebsd\", target_arch = \"x86_64\")))", ); } #[test] fn check_cfg_attributes() { fn ok(s: &str) { let p = Platform::Cfg(s.parse().unwrap()); let mut warnings = Vec::new(); p.check_cfg_attributes(&mut warnings); assert!( warnings.is_empty(), "Expected no warnings but got: {:?}", warnings, ); } fn warn(s: &str, names: &[&str]) { let p = Platform::Cfg(s.parse().unwrap()); let mut warnings = Vec::new(); p.check_cfg_attributes(&mut warnings); assert_eq!( warnings.len(), names.len(), "Expecter warnings about {:?} but got {:?}", names, warnings, ); for (name, warning) in names.iter().zip(warnings.iter()) { assert!( warning.contains(name), "Expected warning about '{}' but got: {}", name, warning, ); } } ok("unix"); ok("windows"); ok("any(not(unix), windows)"); ok("foo"); ok("true"); ok("false"); ok("target_arch = \"abc\""); ok("target_feature = \"abc\""); ok("target_os = \"abc\""); ok("target_family = \"abc\""); ok("target_env = \"abc\""); ok("target_endian = \"abc\""); ok("target_pointer_width = \"abc\""); ok("target_vendor = \"abc\""); ok("bar = \"def\""); warn("test", &["test"]); warn("debug_assertions", &["debug_assertions"]); warn("proc_macro", &["proc_macro"]); warn("feature = \"abc\"", &["feature"]); warn("any(not(debug_assertions), windows)", &["debug_assertions"]); warn( "any(not(feature = \"def\"), target_arch = \"abc\")", &["feature"], ); warn( "any(not(target_os = \"windows\"), proc_macro)", &["proc_macro"], ); warn( "any(not(feature = \"windows\"), proc_macro)", &["feature", "proc_macro"], ); warn( "all(not(debug_assertions), any(windows, proc_macro))", &["debug_assertions", "proc_macro"], ); } ================================================ FILE: crates/cargo-test-macro/Cargo.toml ================================================ [package] name = "cargo-test-macro" version = "0.4.11" edition.workspace = true rust-version = "1.94" # MSRV:1 license.workspace = true repository.workspace = true description = "Helper proc-macro for Cargo's testsuite." [lib] proc-macro = true [lints] workspace = true ================================================ FILE: crates/cargo-test-macro/README.md ================================================ > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use. This > crate may make major changes to its APIs or be deprecated without warning. ================================================ FILE: crates/cargo-test-macro/src/lib.rs ================================================ //! # Cargo test macro. //! //! This is meant to be consumed alongside `cargo-test-support`. See //! for a guide on writing tests. //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use. This //! > crate may make major changes to its APIs or be deprecated without warning. use proc_macro::*; use std::path::Path; use std::process::Command; use std::sync::LazyLock; /// Replacement for `#[test]` /// /// The `#[cargo_test]` attribute extends `#[test]` with some setup before starting the test. /// It will create a filesystem "sandbox" under the "cargo integration test" directory for each test, such as `/path/to/cargo/target/tmp/cit/t123/`. /// The sandbox will contain a `home` directory that will be used instead of your normal home directory. /// /// The `#[cargo_test]` attribute takes several options that will affect how the test is generated. /// They are listed in parentheses separated with commas, such as: /// /// ```rust,ignore /// #[cargo_test(nightly, reason = "-Zfoo is unstable")] /// ``` /// /// The options it supports are: /// /// * `>=1.64` --- This indicates that the test will only run with the given version of `rustc` or newer. /// This can be used when a new `rustc` feature has been stabilized that the test depends on. /// If this is specified, a `reason` is required to explain why it is being checked. /// * `nightly` --- This will cause the test to be ignored if not running on the nightly toolchain. /// This is useful for tests that use unstable options in `rustc` or `rustdoc`. /// These tests are run in Cargo's CI, but are disabled in rust-lang/rust's CI due to the difficulty of updating both repos simultaneously. /// A `reason` field is required to explain why it is nightly-only. /// * `requires = ""` --- This indicates a command that is required to be installed to be run. /// For example, `requires = "rustfmt"` means the test will only run if the executable `rustfmt` is installed. /// These tests are *always* run on CI. /// This is mainly used to avoid requiring contributors from having every dependency installed. /// * `build_std_real` --- This is a "real" `-Zbuild-std` test (in the `build_std` integration test). /// This only runs on nightly, and only if the environment variable `CARGO_RUN_BUILD_STD_TESTS` is set (these tests on run on Linux). /// * `build_std_mock` --- This is a "mock" `-Zbuild-std` test (which uses a mock standard library). /// This only runs on nightly, and is disabled for windows-gnu. /// * `public_network_test` --- This tests contacts the public internet. /// These tests are disabled unless the `CARGO_PUBLIC_NETWORK_TESTS` environment variable is set. /// Use of this should be *extremely rare*, please avoid using it if possible. /// The hosts it contacts should have a relatively high confidence that they are reliable and stable (such as github.com), especially in CI. /// The tests should be carefully considered for developer security and privacy as well. /// * `container_test` --- This indicates that it is a test that uses Docker. /// These tests are disabled unless the `CARGO_CONTAINER_TESTS` environment variable is set. /// This requires that you have Docker installed. /// The SSH tests also assume that you have OpenSSH installed. /// These should work on Linux, macOS, and Windows where possible. /// Unfortunately these tests are not run in CI for macOS or Windows (no Docker on macOS, and Windows does not support Linux images). /// See [`cargo-test-support::containers`](https://doc.rust-lang.org/nightly/nightly-rustc/cargo_test_support/containers) for more on writing these tests. /// * `ignore_windows="reason"` --- Indicates that the test should be ignored on windows for the given reason. #[proc_macro_attribute] pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream { // Ideally these options would be embedded in the test itself. However, I // find it very helpful to have the test clearly state whether or not it // is ignored. It would be nice to have some kind of runtime ignore // support (such as // https://internals.rust-lang.org/t/pre-rfc-skippable-tests/14611). // // Unfortunately a big drawback here is that if the environment changes // (such as the existence of the `git` CLI), this will not trigger a // rebuild and the test will still be ignored. In theory, something like // `tracked_env` or `tracked_path` // (https://github.com/rust-lang/rust/issues/99515) could help with this, // but they don't really handle the absence of files well. let mut ignore = false; let mut requires_reason = false; let mut explicit_reason = None; let mut implicit_reasons = Vec::new(); macro_rules! set_ignore { ($predicate:expr, $($arg:tt)*) => { let p = $predicate; ignore |= p; if p { implicit_reasons.push(std::fmt::format(format_args!($($arg)*))); } }; } let is_not_nightly = !version().1; for rule in split_rules(attr) { match rule.as_str() { "build_std_real" => { // Only run the "real" build-std tests on nightly and with an // explicit opt-in (these generally only work on linux, and // have some extra requirements, and are slow, and can pollute // the environment since it downloads dependencies). set_ignore!(is_not_nightly, "requires nightly"); set_ignore!( option_env!("CARGO_RUN_BUILD_STD_TESTS").is_none(), "CARGO_RUN_BUILD_STD_TESTS must be set" ); } "build_std_mock" => { // Only run the "mock" build-std tests on nightly and disable // for windows-gnu which is missing object files (see // https://github.com/rust-lang/wg-cargo-std-aware/issues/46). set_ignore!(is_not_nightly, "requires nightly"); set_ignore!( cfg!(all(target_os = "windows", target_env = "gnu")), "does not work on windows-gnu" ); } "container_test" => { // These tests must be opt-in because they require docker. set_ignore!( option_env!("CARGO_CONTAINER_TESTS").is_none(), "CARGO_CONTAINER_TESTS must be set" ); } "public_network_test" => { // These tests must be opt-in because they touch the public // network. The use of these should be **EXTREMELY RARE**, and // should only touch things which would nearly certainly work // in CI (like github.com). set_ignore!( option_env!("CARGO_PUBLIC_NETWORK_TESTS").is_none(), "CARGO_PUBLIC_NETWORK_TESTS must be set" ); } "nightly" => { requires_reason = true; set_ignore!(is_not_nightly, "requires nightly"); } "requires_rustup_stable" => { set_ignore!( !has_rustup_stable(), "rustup or stable toolchain not installed" ); } s if s.starts_with("requires=") => { let command = &s[9..]; let Ok(literal) = command.parse::() else { panic!("expect a string literal, found: {command}"); }; let literal = literal.to_string(); let Some(command) = literal .strip_prefix('"') .and_then(|lit| lit.strip_suffix('"')) else { panic!("expect a quoted string literal, found: {literal}"); }; set_ignore!(!has_command(command), "{command} not installed"); } s if s.starts_with(">=1.") => { requires_reason = true; let min_minor = s[4..].parse().unwrap(); let minor = version().0; set_ignore!(minor < min_minor, "requires rustc 1.{minor} or newer"); } s if s.starts_with("reason=") => { explicit_reason = Some(s[7..].parse().unwrap()); } s if s.starts_with("ignore_windows=") => { set_ignore!(cfg!(windows), "{}", &s[16..s.len() - 1]); } _ => panic!("unknown rule {:?}", rule), } } if requires_reason && explicit_reason.is_none() { panic!( "#[cargo_test] with a rule also requires a reason, \ such as #[cargo_test(nightly, reason = \"needs -Z unstable-thing\")]" ); } // Construct the appropriate attributes. let span = Span::call_site(); let mut ret = TokenStream::new(); let add_attr = |ret: &mut TokenStream, attr_name, attr_input| { ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone)))); let attr = TokenTree::from(Ident::new(attr_name, span)); let mut attr_stream: TokenStream = attr.into(); if let Some(input) = attr_input { attr_stream.extend(input); } ret.extend(Some(TokenTree::from(Group::new( Delimiter::Bracket, attr_stream, )))); }; add_attr(&mut ret, "test", None); if ignore { let reason = explicit_reason .or_else(|| { (!implicit_reasons.is_empty()) .then(|| TokenTree::from(Literal::string(&implicit_reasons.join(", "))).into()) }) .map(|reason: TokenStream| { let mut stream = TokenStream::new(); stream.extend(Some(TokenTree::from(Punct::new('=', Spacing::Alone)))); stream.extend(Some(reason)); stream }); add_attr(&mut ret, "ignore", reason); } let mut test_name = None; let mut num = 0; // Find where the function body starts, and add the boilerplate at the start. for token in item { let group = match token { TokenTree::Group(g) => { if g.delimiter() == Delimiter::Brace { g } else { ret.extend(Some(TokenTree::Group(g))); continue; } } TokenTree::Ident(i) => { // The first time through it will be `fn` the second time is the // name of the test. if test_name.is_none() && num == 1 { test_name = Some(i.to_string()) } else { num += 1; } ret.extend(Some(TokenTree::Ident(i))); continue; } other => { ret.extend(Some(other)); continue; } }; let name = &test_name .clone() .map(|n| n.split("::").next().unwrap().to_string()) .unwrap(); let mut new_body = to_token_stream(&format!( r#"let _test_guard = {{ let tmp_dir = env!("CARGO_TARGET_TMPDIR"); let test_dir = cargo_test_support::paths::test_dir(std::file!(), "{name}"); cargo_test_support::paths::init_root(tmp_dir, test_dir) }};"# )); new_body.extend(group.stream()); ret.extend(Some(TokenTree::from(Group::new( group.delimiter(), new_body, )))); } ret } fn split_rules(t: TokenStream) -> Vec { let tts: Vec<_> = t.into_iter().collect(); tts.split(|tt| match tt { TokenTree::Punct(p) => p.as_char() == ',', _ => false, }) .filter(|parts| !parts.is_empty()) .map(|parts| { parts .into_iter() .map(|part| part.to_string()) .collect::() }) .collect() } fn to_token_stream(code: &str) -> TokenStream { code.parse().unwrap() } static VERSION: std::sync::LazyLock<(u32, bool)> = LazyLock::new(|| { let output = Command::new("rustc") .arg("-V") .output() .expect("rustc should run"); let stdout = std::str::from_utf8(&output.stdout).expect("utf8"); let vers = stdout.split_whitespace().skip(1).next().unwrap(); let is_nightly = option_env!("CARGO_TEST_DISABLE_NIGHTLY").is_none() && (vers.contains("-nightly") || vers.contains("-dev")); let minor = vers.split('.').skip(1).next().unwrap().parse().unwrap(); (minor, is_nightly) }); fn version() -> (u32, bool) { LazyLock::force(&VERSION).clone() } fn check_command(command_path: &Path, args: &[&str]) -> bool { let mut command = Command::new(command_path); let command_name = command.get_program().to_str().unwrap().to_owned(); command.args(args); let output = match command.output() { Ok(output) => output, Err(e) => { // * hg is not installed on GitHub macOS or certain constrained // environments like Docker. Consider installing it if Cargo // gains more hg support, but otherwise it isn't critical. // * lldb is not pre-installed on Ubuntu and Windows, so skip. if is_ci() && !matches!(command_name.as_str(), "hg" | "lldb") { panic!("expected command `{command_name}` to be somewhere in PATH: {e}",); } return false; } }; if !output.status.success() { panic!( "expected command `{command_name}` to be runnable, got error {}:\n\ stderr:{}\n\ stdout:{}\n", output.status, String::from_utf8_lossy(&output.stderr), String::from_utf8_lossy(&output.stdout) ); } true } fn has_command(command: &str) -> bool { use std::env::consts::EXE_EXTENSION; // ALLOWED: For testing cargo itself only. #[allow(clippy::disallowed_methods)] let Some(paths) = std::env::var_os("PATH") else { return false; }; std::env::split_paths(&paths) .flat_map(|path| { let candidate = path.join(&command); let with_exe = if EXE_EXTENSION.is_empty() { None } else { Some(candidate.with_extension(EXE_EXTENSION)) }; std::iter::once(candidate).chain(with_exe) }) .find(|p| is_executable(p)) .is_some() } #[cfg(unix)] fn is_executable>(path: P) -> bool { use std::os::unix::prelude::*; std::fs::metadata(path) .map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0) .unwrap_or(false) } #[cfg(windows)] fn is_executable>(path: P) -> bool { path.as_ref().is_file() } fn has_rustup_stable() -> bool { if option_env!("CARGO_TEST_DISABLE_NIGHTLY").is_some() { // This cannot run on rust-lang/rust CI due to the lack of rustup. return false; } // Cargo mucks with PATH on Windows, adding sysroot host libdir, which is // "bin", which circumvents the rustup wrapper. Use the path directly from // CARGO_HOME. let home = match option_env!("CARGO_HOME") { Some(home) => home, None if is_ci() => panic!("expected to run under rustup"), None => return false, }; let cargo = Path::new(home).join("bin/cargo"); check_command(&cargo, &["+stable", "--version"]) } /// Whether or not this running in a Continuous Integration environment. fn is_ci() -> bool { // Consider using `tracked_env` instead of option_env! when it is stabilized. // `tracked_env` will handle changes, but not require rebuilding the macro // itself like option_env does. option_env!("CI").is_some() || option_env!("TF_BUILD").is_some() } ================================================ FILE: crates/cargo-test-support/Cargo.toml ================================================ [package] name = "cargo-test-support" version = "0.11.1" edition.workspace = true rust-version = "1.94" # MSRV:1 license.workspace = true repository.workspace = true description = "Testing framework for Cargo's testsuite." [dependencies] anstream.workspace = true anstyle.workspace = true anyhow.workspace = true cargo-test-macro.workspace = true cargo-util.workspace = true crates-io.workspace = true filetime.workspace = true flate2.workspace = true git2.workspace = true glob.workspace = true itertools.workspace = true pasetors.workspace = true regex.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true snapbox.workspace = true tar.workspace = true time.workspace = true toml = { workspace = true, features = ["display", "serde"] } url.workspace = true walkdir.workspace = true [target.'cfg(windows)'.dependencies] windows-sys = { workspace = true, features = ["Win32_Storage_FileSystem"] } [lints] workspace = true ================================================ FILE: crates/cargo-test-support/README.md ================================================ > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use. This > crate may make major changes to its APIs or be deprecated without warning. ================================================ FILE: crates/cargo-test-support/build.rs ================================================ #![allow(clippy::disallowed_methods)] fn main() { println!("cargo:rustc-check-cfg=cfg(emulate_second_only_system)"); println!( "cargo:rustc-env=NATIVE_ARCH={}", std::env::var("TARGET").unwrap() ); println!("cargo:rerun-if-changed=build.rs"); } ================================================ FILE: crates/cargo-test-support/containers/apache/Dockerfile ================================================ FROM httpd:2.4-alpine RUN apk add --no-cache git git-daemon openssl COPY bar /repos/bar WORKDIR /repos/bar RUN git config --global user.email "testuser@example.com" &&\ git config --global user.name "Test User" &&\ git config --system --add safe.directory '*' &&\ git init -b master . &&\ git add Cargo.toml src &&\ git commit -m "Initial commit" &&\ cd .. &&\ git clone --bare bar bar.git &&\ rm -rf bar WORKDIR / EXPOSE 443 WORKDIR /usr/local/apache2/conf COPY httpd-cargo.conf . RUN cat httpd-cargo.conf >> httpd.conf RUN openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout server.key -out server.crt \ -subj "/emailAddress=webmaster@example.com/C=US/ST=California/L=San Francisco/O=Rust/OU=Cargo/CN=127.0.0.1" WORKDIR / ================================================ FILE: crates/cargo-test-support/containers/apache/bar/Cargo.toml ================================================ [package] name = "bar" version = "1.0.0" edition = "2021" ================================================ FILE: crates/cargo-test-support/containers/apache/bar/src/lib.rs ================================================ // Intentionally blank. ================================================ FILE: crates/cargo-test-support/containers/apache/httpd-cargo.conf ================================================ SetEnv GIT_PROJECT_ROOT /repos SetEnv GIT_HTTP_EXPORT_ALL ScriptAlias /repos /usr/libexec/git-core/git-http-backend/ LoadModule cgid_module modules/mod_cgid.so Require all granted Include conf/extra/httpd-ssl.conf LoadModule ssl_module modules/mod_ssl.so LoadModule socache_shmcb_module modules/mod_socache_shmcb.so ================================================ FILE: crates/cargo-test-support/containers/sshd/Dockerfile ================================================ FROM alpine:3.23 RUN apk add --no-cache openssh git RUN apk upgrade --no-cache libcrypto3 RUN ssh-keygen -A RUN addgroup -S testuser && adduser -S testuser -G testuser -s /bin/ash # NOTE: Ideally the password should be set to *, but I am uncertain how to do # that in alpine. It shouldn't matter since PermitEmptyPasswords is "no". RUN passwd -u testuser RUN mkdir /repos && chown testuser /repos COPY --chown=testuser:testuser bar /repos/bar USER testuser WORKDIR /repos/bar RUN git config --global user.email "testuser@example.com" &&\ git config --global user.name "Test User" &&\ git init -b master . &&\ git add Cargo.toml src &&\ git commit -m "Initial commit" &&\ cd .. &&\ git clone --bare bar bar.git &&\ rm -rf bar WORKDIR / USER root EXPOSE 22 ENTRYPOINT ["/usr/sbin/sshd", "-D", "-E", "/var/log/auth.log"] ================================================ FILE: crates/cargo-test-support/containers/sshd/bar/Cargo.toml ================================================ [package] name = "bar" version = "1.0.0" edition = "2021" ================================================ FILE: crates/cargo-test-support/containers/sshd/bar/src/lib.rs ================================================ // Intentionally blank. ================================================ FILE: crates/cargo-test-support/src/compare.rs ================================================ //! Routines for comparing and diffing output. //! //! # Deprecated comparisons //! //! Cargo's tests are in transition from internal-only pattern and normalization routines used in //! asserts like [`crate::Execs::with_stdout_contains`] to [`assert_e2e`] and [`assert_ui`]. //! //! ## Patterns //! //! Many of these functions support special markup to assist with comparing //! text that may vary or is otherwise uninteresting for the test at hand. The //! supported patterns are: //! //! - `[..]` is a wildcard that matches 0 or more characters on the same line //! (similar to `.*` in a regex). It is non-greedy. //! - `[EXE]` optionally adds `.exe` on Windows (empty string on other //! platforms). //! - `[ROOT]` is the path to the test directory's root. //! - `[CWD]` is the working directory of the process that was run. //! - There is a wide range of substitutions (such as `[COMPILING]` or //! `[WARNING]`) to match cargo's "status" output and allows you to ignore //! the alignment. See the source of `substitute_macros` for a complete list //! of substitutions. //! - `[DIRTY-MSVC]` (only when the line starts with it) would be replaced by //! `[DIRTY]` when `cfg(target_env = "msvc")` or the line will be ignored otherwise. //! Tests that work around [issue 7358](https://github.com/rust-lang/cargo/issues/7358) //! can use this to avoid duplicating the `with_stderr` call like: //! `if cfg!(target_env = "msvc") {e.with_stderr("...[DIRTY]...");} else {e.with_stderr("...");}`. //! //! ## Normalization //! //! In addition to the patterns described above, the strings are normalized //! in such a way to avoid unwanted differences. The normalizations are: //! //! - Raw tab characters are converted to the string ``. This is helpful //! so that raw tabs do not need to be written in the expected string, and //! to avoid confusion of tabs vs spaces. //! - Backslashes are converted to forward slashes to deal with Windows paths. //! This helps so that all tests can be written assuming forward slashes. //! Other heuristics are applied to try to ensure Windows-style paths aren't //! a problem. //! - Carriage returns are removed, which can help when running on Windows. use crate::cross_compile::try_alternate; use crate::paths; use crate::rustc_host; use anyhow::{Result, bail}; use snapbox::Data; use snapbox::IntoData; use std::fmt; use std::path::Path; use std::path::PathBuf; use std::str; /// This makes it easier to write regex replacements that are guaranteed to only /// get compiled once macro_rules! regex { ($re:literal $(,)?) => {{ static RE: std::sync::OnceLock = std::sync::OnceLock::new(); RE.get_or_init(|| regex::Regex::new($re).unwrap()) }}; } /// Assertion policy for UI tests /// /// This emphasizes showing as much content as possible at the cost of more brittleness /// /// # Snapshots /// /// Updating of snapshots is controlled with the `SNAPSHOTS` environment variable: /// /// - `skip`: do not run the tests /// - `ignore`: run the tests but ignore their failure /// - `verify`: run the tests /// - `overwrite`: update the snapshots based on the output of the tests /// /// # Patterns /// /// - `[..]` is a character wildcard, stopping at line breaks /// - `\n...\n` is a multi-line wildcard /// - `[EXE]` matches the exe suffix for the current platform /// - `[ROOT]` matches [`paths::root()`][crate::paths::root] /// - `[ROOTURL]` matches [`paths::root()`][crate::paths::root] as a URL /// /// # Normalization /// /// In addition to the patterns described above, text is normalized /// in such a way to avoid unwanted differences. The normalizations are: /// /// - Backslashes are converted to forward slashes to deal with Windows paths. /// This helps so that all tests can be written assuming forward slashes. /// Other heuristics are applied to try to ensure Windows-style paths aren't /// a problem. /// - Carriage returns are removed, which can help when running on Windows. /// /// # Example /// /// ```no_run /// # use cargo_test_support::compare::assert_e2e; /// # use cargo_test_support::file; /// # let p = cargo_test_support::project().build(); /// # let stdout = ""; /// assert_e2e().eq(stdout, file!["stderr.term.svg"]); /// ``` /// ```console /// $ SNAPSHOTS=overwrite cargo test /// ``` pub fn assert_ui() -> snapbox::Assert { let mut subs = snapbox::Redactions::new(); subs.extend(MIN_LITERAL_REDACTIONS.into_iter().cloned()) .unwrap(); add_test_support_redactions(&mut subs); add_regex_redactions(&mut subs); snapbox::Assert::new() .action_env(snapbox::assert::DEFAULT_ACTION_ENV) .redact_with(subs) } /// Assertion policy for functional end-to-end tests /// /// This emphasizes showing as much content as possible at the cost of more brittleness /// /// # Snapshots /// /// Updating of snapshots is controlled with the `SNAPSHOTS` environment variable: /// /// - `skip`: do not run the tests /// - `ignore`: run the tests but ignore their failure /// - `verify`: run the tests /// - `overwrite`: update the snapshots based on the output of the tests /// /// # Patterns /// /// - `[..]` is a character wildcard, stopping at line breaks /// - `\n...\n` is a multi-line wildcard /// - `[EXE]` matches the exe suffix for the current platform /// - `[ROOT]` matches [`paths::root()`][crate::paths::root] /// - `[ROOTURL]` matches [`paths::root()`][crate::paths::root] as a URL /// /// # Normalization /// /// In addition to the patterns described above, text is normalized /// in such a way to avoid unwanted differences. The normalizations are: /// /// - Backslashes are converted to forward slashes to deal with Windows paths. /// This helps so that all tests can be written assuming forward slashes. /// Other heuristics are applied to try to ensure Windows-style paths aren't /// a problem. /// - Carriage returns are removed, which can help when running on Windows. /// /// # Example /// /// ```no_run /// # use cargo_test_support::compare::assert_e2e; /// # use cargo_test_support::str; /// # let p = cargo_test_support::project().build(); /// assert_e2e().eq(p.read_lockfile(), str![]); /// ``` /// ```console /// $ SNAPSHOTS=overwrite cargo test /// ``` pub fn assert_e2e() -> snapbox::Assert { let mut subs = snapbox::Redactions::new(); subs.extend(MIN_LITERAL_REDACTIONS.into_iter().cloned()) .unwrap(); subs.extend(E2E_LITERAL_REDACTIONS.into_iter().cloned()) .unwrap(); add_test_support_redactions(&mut subs); add_regex_redactions(&mut subs); snapbox::Assert::new() .action_env(snapbox::assert::DEFAULT_ACTION_ENV) .redact_with(subs) } fn add_test_support_redactions(subs: &mut snapbox::Redactions) { let root = paths::root(); // Use `from_file_path` instead of `from_dir_path` so the trailing slash is // put in the users output, rather than hidden in the variable let root_url = url::Url::from_file_path(&root).unwrap().to_string(); subs.insert("[ROOT]", root).unwrap(); subs.insert("[ROOTURL]", root_url).unwrap(); subs.insert("[HOST_TARGET]", rustc_host()).unwrap(); if let Some(alt_target) = try_alternate() { subs.insert("[ALT_TARGET]", alt_target).unwrap(); } } fn add_regex_redactions(subs: &mut snapbox::Redactions) { // For e2e tests subs.insert( "[ELAPSED]", regex!(r"\[FINISHED\].*in (?[0-9]+(\.[0-9]+)?(m [0-9]+)?)s"), ) .unwrap(); // for UI tests subs.insert( "[ELAPSED]", regex!(r"Finished.*in (?[0-9]+(\.[0-9]+)?(m [0-9]+)?)s"), ) .unwrap(); // output from libtest subs.insert( "[ELAPSED]", regex!(r"; finished in (?[0-9]+(\.[0-9]+)?(m [0-9]+)?)s"), ) .unwrap(); subs.insert( "[FILE_NUM]", regex!(r"\[(REMOVED|SUMMARY)\] (?[1-9][0-9]*) files"), ) .unwrap(); subs.insert( "[FILE_SIZE]", regex!(r"(?[0-9]+(\.[0-9]+)?([a-zA-Z]i)?)B\s"), ) .unwrap(); subs.insert( "[HASH]", regex!(r"home/\.cargo/registry/(cache|index|src)/-(?[a-z0-9]+)"), ) .unwrap(); subs.insert( "[HASH]", regex!(r"\.cargo/target/(?[0-9a-f]{2}/[0-9a-f]{14})"), ) .unwrap(); subs.insert("[HASH]", regex!(r"/[a-z0-9\-_]+-(?[0-9a-f]{16})")) .unwrap(); // Match multi-part hashes like `06/b451d0d6f88b1d` used in directory paths subs.insert("[HASH]", regex!(r"/(?[a-f0-9]{2}\/[0-9a-f]{14})")) .unwrap(); // Match file name hashes like `foo-06b451d0d6f88b1d` subs.insert("[HASH]", regex!(r"[a-z0-9]+-(?[a-f0-9]{16})")) .unwrap(); // Match path hashes like `../06b451d0d6f88b1d/..` used in directory paths subs.insert("[HASH]", regex!(r"\/(?[0-9a-f]{16})\/")) .unwrap(); subs.insert( "[AVG_ELAPSED]", regex!(r"(?[0-9]+(\.[0-9]+)?) ns/iter"), ) .unwrap(); subs.insert( "[JITTER]", regex!(r"ns/iter \(\+/- (?[0-9]+(\.[0-9]+)?)\)"), ) .unwrap(); // Following 3 subs redact: // "1719325877.527949100s, 61549498ns after last build at 1719325877.466399602s" // "1719503592.218193216s, 1h 1s after last build at 1719499991.982681034s" // into "[DIRTY_REASON_NEW_TIME], [DIRTY_REASON_DIFF] after last build at [DIRTY_REASON_OLD_TIME]" subs.insert( "[TIME_DIFF_AFTER_LAST_BUILD]", regex!(r"(?[0-9]+(\.[0-9]+)?s, (\s?[0-9]+(\.[0-9]+)?(s|ns|h))+ after last build at [0-9]+(\.[0-9]+)?s)"), ) .unwrap(); } static MIN_LITERAL_REDACTIONS: &[(&str, &str)] = &[ ("[EXE]", std::env::consts::EXE_SUFFIX), ("[BROKEN_PIPE]", "Broken pipe (os error 32)"), ("[BROKEN_PIPE]", "The pipe is being closed. (os error 232)"), // Unix message for an entity was not found ("[NOT_FOUND]", "No such file or directory (os error 2)"), // Windows message for an entity was not found ( "[NOT_FOUND]", "The system cannot find the file specified. (os error 2)", ), ( "[NOT_FOUND]", "The system cannot find the path specified. (os error 3)", ), ("[NOT_FOUND]", "Access is denied. (os error 5)"), ("[NOT_FOUND]", "program not found"), // Unix message for exit status ("[EXIT_STATUS]", "exit status"), // Windows message for exit status ("[EXIT_STATUS]", "exit code"), ]; static E2E_LITERAL_REDACTIONS: &[(&str, &str)] = &[ ("[RUNNING]", " Running"), ("[COMPILING]", " Compiling"), ("[CHECKING]", " Checking"), ("[COMPLETED]", " Completed"), ("[CREATED]", " Created"), ("[CREATING]", " Creating"), ("[CREDENTIAL]", " Credential"), ("[DOWNGRADING]", " Downgrading"), ("[FINISHED]", " Finished"), ("[ERROR]", "error:"), ("[WARNING]", "warning:"), ("[NOTE]", "note:"), ("[HELP]", "help:"), ("[DOCUMENTING]", " Documenting"), ("[SCRAPING]", " Scraping"), ("[FRESH]", " Fresh"), ("[DIRTY]", " Dirty"), ("[LOCKING]", " Locking"), ("[UPDATING]", " Updating"), ("[UPGRADING]", " Upgrading"), ("[ADDING]", " Adding"), ("[REMOVING]", " Removing"), ("[REMOVED]", " Removed"), ("[UNCHANGED]", " Unchanged"), ("[DOCTEST]", " Doc-tests"), ("[PACKAGING]", " Packaging"), ("[PACKAGED]", " Packaged"), ("[DOWNLOADING]", " Downloading"), ("[DOWNLOADED]", " Downloaded"), ("[UPLOADING]", " Uploading"), ("[UPLOADED]", " Uploaded"), ("[VERIFYING]", " Verifying"), ("[ARCHIVING]", " Archiving"), ("[INSTALLING]", " Installing"), ("[REPLACING]", " Replacing"), ("[UNPACKING]", " Unpacking"), ("[SUMMARY]", " Summary"), ("[FIXED]", " Fixed"), ("[FIXING]", " Fixing"), ("[IGNORED]", " Ignored"), ("[INSTALLED]", " Installed"), ("[REPLACED]", " Replaced"), ("[BUILDING]", " Building"), ("[LOGIN]", " Login"), ("[LOGOUT]", " Logout"), ("[YANK]", " Yank"), ("[OWNER]", " Owner"), ("[MIGRATING]", " Migrating"), ("[EXECUTABLE]", " Executable"), ("[SKIPPING]", " Skipping"), ("[WAITING]", " Waiting"), ("[PUBLISHED]", " Published"), ("[BLOCKING]", " Blocking"), ("[GENERATED]", " Generated"), ("[OPENING]", " Opening"), ("[MERGING]", " Merging"), ]; /// Checks that the given string contains the given contiguous lines /// somewhere. /// /// See [Patterns](index.html#patterns) for more information on pattern matching. pub(crate) fn match_contains( expected: &str, actual: &str, redactions: &snapbox::Redactions, ) -> Result<()> { let expected = normalize_expected(expected, redactions); let actual = normalize_actual(actual, redactions); let e: Vec<_> = expected.lines().map(|line| WildStr::new(line)).collect(); let a: Vec<_> = actual.lines().collect(); if e.len() == 0 { bail!("expected length must not be zero"); } for window in a.windows(e.len()) { if e == window { return Ok(()); } } bail!( "expected to find:\n\ {}\n\n\ did not find in output:\n\ {}", expected, actual ); } /// Checks that the given string does not contain the given contiguous lines /// anywhere. /// /// See [Patterns](index.html#patterns) for more information on pattern matching. pub(crate) fn match_does_not_contain( expected: &str, actual: &str, redactions: &snapbox::Redactions, ) -> Result<()> { if match_contains(expected, actual, redactions).is_ok() { bail!( "expected not to find:\n\ {}\n\n\ but found in output:\n\ {}", expected, actual ); } else { Ok(()) } } /// Checks that the given string has a line that contains the given patterns, /// and that line also does not contain the `without` patterns. /// /// See [Patterns](index.html#patterns) for more information on pattern matching. /// /// See [`crate::Execs::with_stderr_line_without`] for an example and cautions /// against using. pub(crate) fn match_with_without( actual: &str, with: &[String], without: &[String], redactions: &snapbox::Redactions, ) -> Result<()> { let actual = normalize_actual(actual, redactions); let norm = |s: &String| format!("[..]{}[..]", normalize_expected(s, redactions)); let with: Vec<_> = with.iter().map(norm).collect(); let without: Vec<_> = without.iter().map(norm).collect(); let with_wild: Vec<_> = with.iter().map(|w| WildStr::new(w)).collect(); let without_wild: Vec<_> = without.iter().map(|w| WildStr::new(w)).collect(); let matches: Vec<_> = actual .lines() .filter(|line| with_wild.iter().all(|with| with == line)) .filter(|line| !without_wild.iter().any(|without| without == line)) .collect(); match matches.len() { 0 => bail!( "Could not find expected line in output.\n\ With contents: {:?}\n\ Without contents: {:?}\n\ Actual stderr:\n\ {}\n", with, without, actual ), 1 => Ok(()), _ => bail!( "Found multiple matching lines, but only expected one.\n\ With contents: {:?}\n\ Without contents: {:?}\n\ Matching lines:\n\ {}\n", with, without, itertools::join(matches, "\n") ), } } /// Normalizes the output so that it can be compared against the expected value. fn normalize_actual(content: &str, redactions: &snapbox::Redactions) -> String { use snapbox::filter::Filter as _; let content = snapbox::filter::FilterPaths.filter(content.into_data()); let content = snapbox::filter::FilterNewlines.filter(content); let content = content.render().expect("came in as a String"); let content = redactions.redact(&content); content } /// Normalizes the expected string so that it can be compared against the actual output. fn normalize_expected(content: &str, redactions: &snapbox::Redactions) -> String { use snapbox::filter::Filter as _; let content = snapbox::filter::FilterPaths.filter(content.into_data()); let content = snapbox::filter::FilterNewlines.filter(content); // Remove any conditionally absent redactions like `[EXE]` let content = content.render().expect("came in as a String"); let content = redactions.clear_unused(&content); content.into_owned() } /// A single line string that supports `[..]` wildcard matching. struct WildStr<'a> { has_meta: bool, line: &'a str, } impl<'a> WildStr<'a> { fn new(line: &'a str) -> WildStr<'a> { WildStr { has_meta: line.contains("[..]"), line, } } } impl PartialEq<&str> for WildStr<'_> { fn eq(&self, other: &&str) -> bool { if self.has_meta { meta_cmp(self.line, other) } else { self.line == *other } } } fn meta_cmp(a: &str, mut b: &str) -> bool { for (i, part) in a.split("[..]").enumerate() { match b.find(part) { Some(j) => { if i == 0 && j != 0 { return false; } b = &b[j + part.len()..]; } None => return false, } } b.is_empty() || a.ends_with("[..]") } impl fmt::Display for WildStr<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.line) } } impl fmt::Debug for WildStr<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.line) } } pub struct InMemoryDir { files: Vec<(PathBuf, Data)>, } impl InMemoryDir { pub fn paths(&self) -> impl Iterator { self.files.iter().map(|(p, _)| p.as_path()) } #[track_caller] pub fn assert_contains(&self, expected: &Self) { use std::fmt::Write as _; let assert = assert_e2e(); let mut errs = String::new(); for (path, expected_data) in &expected.files { let actual_data = self .files .iter() .find_map(|(p, d)| (path == p).then(|| d.clone())) .unwrap_or_else(|| Data::new()); if let Err(err) = assert.try_eq(Some(&path.display()), actual_data, expected_data.clone()) { let _ = write!(&mut errs, "{err}"); } } if !errs.is_empty() { panic!("{errs}") } } } impl FromIterator<(P, D)> for InMemoryDir where P: Into, D: IntoData, { fn from_iter>(files: I) -> Self { let files = files .into_iter() .map(|(p, d)| (p.into(), d.into_data())) .collect(); Self { files } } } impl From<[(P, D); N]> for InMemoryDir where P: Into, D: IntoData, { fn from(files: [(P, D); N]) -> Self { let files = files .into_iter() .map(|(p, d)| (p.into(), d.into_data())) .collect(); Self { files } } } impl From> for InMemoryDir where P: Into, D: IntoData, { fn from(files: std::collections::HashMap) -> Self { let files = files .into_iter() .map(|(p, d)| (p.into(), d.into_data())) .collect(); Self { files } } } impl From> for InMemoryDir where P: Into, D: IntoData, { fn from(files: std::collections::BTreeMap) -> Self { let files = files .into_iter() .map(|(p, d)| (p.into(), d.into_data())) .collect(); Self { files } } } impl From<()> for InMemoryDir { fn from(_files: ()) -> Self { let files = Vec::new(); Self { files } } } /// Create an `impl _ for InMemoryDir` for a generic tuple /// /// Must pass in names for each tuple parameter for /// - internal variable name /// - `Path` type /// - `Data` type macro_rules! impl_from_tuple_for_inmemorydir { ($($var:ident $path:ident $data:ident),+) => { impl<$($path: Into, $data: IntoData),+> From<($(($path, $data)),+ ,)> for InMemoryDir { fn from(files: ($(($path, $data)),+,)) -> Self { let ($($var),+ ,) = files; let files = [$(($var.0.into(), $var.1.into_data())),+]; files.into() } } }; } /// Extend `impl_from_tuple_for_inmemorydir` to generate for the specified tuple and all smaller /// tuples macro_rules! impl_from_tuples_for_inmemorydir { ($var1:ident $path1:ident $data1:ident, $($var:ident $path:ident $data:ident),+) => { impl_from_tuples_for_inmemorydir!(__impl $var1 $path1 $data1; $($var $path $data),+); }; (__impl $($var:ident $path:ident $data:ident),+; $var1:ident $path1:ident $data1:ident $(,$var2:ident $path2:ident $data2:ident)*) => { impl_from_tuple_for_inmemorydir!($($var $path $data),+); impl_from_tuples_for_inmemorydir!(__impl $($var $path $data),+, $var1 $path1 $data1; $($var2 $path2 $data2),*); }; (__impl $($var:ident $path:ident $data:ident),+;) => { impl_from_tuple_for_inmemorydir!($($var $path $data),+); } } // Generate for tuples of size `1..=7` impl_from_tuples_for_inmemorydir!( s1 P1 D1, s2 P2 D2, s3 P3 D3, s4 P4 D4, s5 P5 D5, s6 P6 D6, s7 P7 D7 ); #[cfg(test)] mod test { use snapbox::assert_data_eq; use snapbox::prelude::*; use snapbox::str; use super::*; #[test] fn wild_str_cmp() { for (a, b) in &[ ("a b", "a b"), ("a[..]b", "a b"), ("a[..]", "a b"), ("[..]", "a b"), ("[..]b", "a b"), ] { assert_eq!(WildStr::new(a), b); } for (a, b) in &[("[..]b", "c"), ("b", "c"), ("b", "cb")] { assert_ne!(WildStr::new(a), b); } } #[test] fn redact_elapsed_time() { let mut subs = snapbox::Redactions::new(); add_regex_redactions(&mut subs); assert_data_eq!( subs.redact("[FINISHED] `release` profile [optimized] target(s) in 5.5s"), str!["[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s"].raw() ); assert_data_eq!( subs.redact("[FINISHED] `release` profile [optimized] target(s) in 1m 05s"), str!["[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s"].raw() ); } } ================================================ FILE: crates/cargo-test-support/src/containers.rs ================================================ //! Support for testing using Docker containers. //! //! The [`Container`] type is a builder for configuring a container to run. //! After you call `launch`, you can use the [`ContainerHandle`] to interact //! with the running container. //! //! Tests using containers must use `#[cargo_test(container_test)]` to disable //! them unless the `CARGO_CONTAINER_TESTS` environment variable is set. use cargo_util::ProcessBuilder; use std::collections::HashMap; use std::io::Read; use std::path::PathBuf; use std::process::Command; use std::sync::Mutex; use std::sync::atomic::{AtomicUsize, Ordering}; use tar::Header; /// A builder for configuring a container to run. pub struct Container { /// The host directory that forms the basis of the Docker image. build_context: PathBuf, /// Files to copy over to the image. files: Vec, } /// A handle to a running container. /// /// You can use this to interact with the container. pub struct ContainerHandle { /// The name of the container. name: String, /// Port mappings of `container_port` to `host_port` for ports exposed via EXPOSE. pub port_mappings: HashMap, } impl Container { pub fn new(context_dir: &str) -> Container { assert!(std::env::var_os("CARGO_CONTAINER_TESTS").is_some()); let mut build_context = PathBuf::from(env!("CARGO_MANIFEST_DIR")); build_context.push("containers"); build_context.push(context_dir); Container { build_context, files: Vec::new(), } } /// Adds a file to be copied into the container. pub fn file(mut self, file: MkFile) -> Self { self.files.push(file); self } /// Starts the container. pub fn launch(mut self) -> ContainerHandle { static NEXT_ID: AtomicUsize = AtomicUsize::new(0); let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); let name = format!("cargo_test_{id}"); remove_if_exists(&name); self.create_container(&name); self.copy_files(&name); self.start_container(&name); let info = self.container_inspect(&name); let port_mappings = self.port_mappings(&info); self.wait_till_ready(&port_mappings); ContainerHandle { name, port_mappings, } } fn create_container(&self, name: &str) { static BUILD_LOCK: Mutex<()> = Mutex::new(()); let image_base = self.build_context.file_name().unwrap(); let image_name = format!("cargo-test-{}", image_base.to_str().unwrap()); let _lock = BUILD_LOCK .lock() .map_err(|_| panic!("previous docker build failed, unable to run test")); ProcessBuilder::new("docker") .args(&["build", "--tag", image_name.as_str()]) .arg(&self.build_context) .exec_with_output() .unwrap(); ProcessBuilder::new("docker") .args(&[ "container", "create", "--publish-all", "--rm", "--name", name, ]) .arg(image_name) .exec_with_output() .unwrap(); } fn copy_files(&mut self, name: &str) { if self.files.is_empty() { return; } let mut ar = tar::Builder::new(Vec::new()); ar.sparse(false); let files = std::mem::replace(&mut self.files, Vec::new()); for mut file in files { ar.append_data(&mut file.header, &file.path, file.contents.as_slice()) .unwrap(); } let ar = ar.into_inner().unwrap(); ProcessBuilder::new("docker") .args(&["cp", "-"]) .arg(format!("{name}:/")) .stdin(ar) .exec_with_output() .unwrap(); } fn start_container(&self, name: &str) { ProcessBuilder::new("docker") .args(&["container", "start"]) .arg(name) .exec_with_output() .unwrap(); } fn container_inspect(&self, name: &str) -> serde_json::Value { let output = ProcessBuilder::new("docker") .args(&["inspect", name]) .exec_with_output() .unwrap(); serde_json::from_slice(&output.stdout).unwrap() } /// Returns the mapping of container_port->host_port for ports that were /// exposed with EXPOSE. fn port_mappings(&self, info: &serde_json::Value) -> HashMap { info[0]["NetworkSettings"]["Ports"] .as_object() .unwrap() .iter() .map(|(key, value)| { let key = key .strip_suffix("/tcp") .expect("expected TCP only ports") .parse() .unwrap(); let values = value.as_array().unwrap(); let value = values .iter() .find(|value| value["HostIp"].as_str().unwrap() == "0.0.0.0") .expect("expected localhost IP"); let host_port = value["HostPort"].as_str().unwrap().parse().unwrap(); (key, host_port) }) .collect() } fn wait_till_ready(&self, port_mappings: &HashMap) { for port in port_mappings.values() { let mut ok = false; for _ in 0..30 { match std::net::TcpStream::connect(format!("127.0.0.1:{port}")) { Ok(_) => { ok = true; break; } Err(e) => { if e.kind() != std::io::ErrorKind::ConnectionRefused { panic!("unexpected localhost connection error: {e:?}"); } std::thread::sleep(std::time::Duration::new(1, 0)); } } } if !ok { panic!("no listener on localhost port {port}"); } } } } impl ContainerHandle { /// Executes a program inside a running container. pub fn exec(&self, args: &[&str]) -> std::process::Output { ProcessBuilder::new("docker") .args(&["container", "exec", &self.name]) .args(args) .exec_with_output() .unwrap() } /// Returns the contents of a file inside the container. pub fn read_file(&self, path: &str) -> String { let output = ProcessBuilder::new("docker") .args(&["cp", &format!("{}:{}", self.name, path), "-"]) .exec_with_output() .unwrap(); let mut ar = tar::Archive::new(output.stdout.as_slice()); let mut entry = ar.entries().unwrap().next().unwrap().unwrap(); let mut contents = String::new(); entry.read_to_string(&mut contents).unwrap(); contents } } impl Drop for ContainerHandle { fn drop(&mut self) { // To help with debugging, this will keep the container alive. if std::env::var_os("CARGO_CONTAINER_TEST_KEEP").is_some() { return; } remove_if_exists(&self.name); } } fn remove_if_exists(name: &str) { if let Err(e) = Command::new("docker") .args(&["container", "rm", "--force", name]) .output() { panic!("failed to run docker: {e}"); } } /// Builder for configuring a file to copy into a container. pub struct MkFile { path: String, contents: Vec, header: Header, } impl MkFile { /// Defines a file to add to the container. /// /// This should be passed to `Container::file`. /// /// The path is the path inside the container to create the file. pub fn path(path: &str) -> MkFile { MkFile { path: path.to_string(), contents: Vec::new(), header: Header::new_gnu(), } } pub fn contents(mut self, contents: impl Into>) -> Self { self.contents = contents.into(); self.header.set_size(self.contents.len() as u64); self } pub fn mode(mut self, mode: u32) -> Self { self.header.set_mode(mode); self } pub fn uid(mut self, uid: u64) -> Self { self.header.set_uid(uid); self } pub fn gid(mut self, gid: u64) -> Self { self.header.set_gid(gid); self } } ================================================ FILE: crates/cargo-test-support/src/cross_compile.rs ================================================ //! Support for cross-compile tests with the `--target` flag. //! //! Note that cross-testing is very limited. You need to install the //! "alternate" target to the host (32-bit for 64-bit hosts or vice-versa). //! //! Set `CFG_DISABLE_CROSS_TESTS=1` environment variable to disable these tests //! if you are unable to use the alternate target. Unfortunately 32-bit //! support on macOS is going away, so macOS users are out of luck. //! //! These tests are all disabled on rust-lang/rust's CI, but run in Cargo's CI. use std::env; /// The arch triple of the test-running host. pub fn native() -> &'static str { env!("NATIVE_ARCH") } pub fn native_arch() -> &'static str { match native() .split("-") .next() .expect("Target triple has unexpected format") { "x86_64" => "x86_64", "aarch64" => "aarch64", "i686" => "x86", _ => panic!("This test should be gated on cross_compile::disabled."), } } /// The alternate target-triple to build with. /// /// Only use this function on tests that check `cross_compile::disabled`. pub fn alternate() -> &'static str { try_alternate().expect("This test should be gated on cross_compile::disabled.") } /// A possible alternate target-triple to build with. pub(crate) fn try_alternate() -> Option<&'static str> { if cfg!(target_os = "macos") { Some("x86_64-apple-darwin") } else if cfg!(target_os = "linux") { Some("i686-unknown-linux-gnu") } else if cfg!(all(target_os = "windows", target_env = "msvc")) { Some("i686-pc-windows-msvc") } else if cfg!(all(target_os = "windows", target_env = "gnu")) { Some("i686-pc-windows-gnu") } else { None } } pub fn alternate_arch() -> &'static str { if cfg!(target_os = "macos") { "x86_64" } else { "x86" } } /// A target-triple that is neither the host nor the target. /// /// Rustc may not work with it and it's alright, apart from being a /// valid target triple it is supposed to be used only as a /// placeholder for targets that should not be considered. pub fn unused() -> &'static str { "wasm32-unknown-unknown" } /// Check if the given target has been installed. /// /// Generally `testsuite::utils::cross_compile::disabled` should be used to check if cross-compilation is allowed. /// And [`alternate`] to get the cross target. /// /// You should only use this as a last resort to skip tests, /// because it doesn't report skipped tests as ignored. pub fn requires_target_installed(target: &str) -> bool { let has_target = std::process::Command::new("rustup") .args(["target", "list", "--installed"]) .output() .ok() .map(|output| { String::from_utf8(output.stdout) .map(|stdout| stdout.contains(target)) .unwrap_or_default() }) .unwrap_or_default(); if !has_target { let msg = format!("to run this test, run `rustup target add {target} --toolchain `",); if cargo_util::is_ci() { panic!("{msg}"); } else { eprintln!("{msg}"); } } has_target } ================================================ FILE: crates/cargo-test-support/src/git.rs ================================================ //! # Git Testing Support //! //! ## Creating a git dependency //! [`new()`] is an easy way to create a new git repository containing a //! project that you can then use as a dependency. It will automatically add all //! the files you specify in the project and commit them to the repository. //! //! ### Example: //! //! ```no_run //! # use cargo_test_support::project; //! # use cargo_test_support::basic_manifest; //! # use cargo_test_support::git; //! let git_project = git::new("dep1", |project| { //! project //! .file("Cargo.toml", &basic_manifest("dep1", "1.0.0")) //! .file("src/lib.rs", r#"pub fn f() { println!("hi!"); } "#) //! }); //! //! // Use the `url()` method to get the file url to the new repository. //! let p = project() //! .file("Cargo.toml", &format!(r#" //! [package] //! name = "a" //! version = "1.0.0" //! //! [dependencies] //! dep1 = {{ git = '{}' }} //! "#, git_project.url())) //! .file("src/lib.rs", "extern crate dep1;") //! .build(); //! ``` //! //! ## Manually creating repositories //! //! [`repo()`] can be used to create a [`RepoBuilder`] which provides a way of //! adding files to a blank repository and committing them. //! //! If you want to then manipulate the repository (such as adding new files or //! tags), you can use `git2::Repository::open()` to open the repository and then //! use some of the helper functions in this file to interact with the repository. use crate::{Project, ProjectBuilder, SymlinkBuilder, paths::CargoPathExt, project}; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Once; use url::Url; /// Manually construct a [`Repository`] /// /// See also [`new`], [`repo`] #[must_use] pub struct RepoBuilder { repo: git2::Repository, files: Vec, } /// See [`new`] pub struct Repository(git2::Repository); /// Create a [`RepoBuilder`] to build a new git repository. /// /// Call [`RepoBuilder::build()`] to finalize and create the repository. pub fn repo(p: &Path) -> RepoBuilder { RepoBuilder::init(p) } impl RepoBuilder { pub fn init(p: &Path) -> RepoBuilder { t!(fs::create_dir_all(p.parent().unwrap())); let repo = init(p); RepoBuilder { repo, files: Vec::new(), } } /// Add a file to the repository. pub fn file(self, path: &str, contents: &str) -> RepoBuilder { let mut me = self.nocommit_file(path, contents); me.files.push(PathBuf::from(path)); me } /// Create a symlink to a directory pub fn nocommit_symlink_dir>(self, dst: T, src: T) -> Self { let workdir = self.repo.workdir().unwrap(); SymlinkBuilder::new_dir(workdir.join(dst), workdir.join(src)).mk(); self } /// Add a file that will be left in the working directory, but not added /// to the repository. pub fn nocommit_file(self, path: &str, contents: &str) -> RepoBuilder { let dst = self.repo.workdir().unwrap().join(path); t!(fs::create_dir_all(dst.parent().unwrap())); t!(fs::write(&dst, contents)); self } /// Create the repository and commit the new files. pub fn build(self) -> Repository { { let mut index = t!(self.repo.index()); for file in self.files.iter() { t!(index.add_path(file)); } t!(index.write()); let id = t!(index.write_tree()); let tree = t!(self.repo.find_tree(id)); let sig = t!(self.repo.signature()); t!(self .repo .commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])); } let RepoBuilder { repo, .. } = self; Repository(repo) } } impl Repository { pub fn root(&self) -> &Path { self.0.workdir().unwrap() } pub fn url(&self) -> Url { self.0.workdir().unwrap().to_url() } pub fn revparse_head(&self) -> String { self.0 .revparse_single("HEAD") .expect("revparse HEAD") .id() .to_string() } } /// *(`git2`)* Initialize a new repository at the given path. pub fn init(path: &Path) -> git2::Repository { default_search_path(); let repo = t!(git2::Repository::init(path)); default_repo_cfg(&repo); repo } fn default_search_path() { use crate::paths::global_root; use git2::{ConfigLevel, opts::set_search_path}; static INIT: Once = Once::new(); INIT.call_once(|| unsafe { let path = global_root().join("blank_git_search_path"); t!(set_search_path(ConfigLevel::System, &path)); t!(set_search_path(ConfigLevel::Global, &path)); t!(set_search_path(ConfigLevel::XDG, &path)); t!(set_search_path(ConfigLevel::ProgramData, &path)); }) } fn default_repo_cfg(repo: &git2::Repository) { let mut cfg = t!(repo.config()); t!(cfg.set_str("user.email", "foo@bar.com")); t!(cfg.set_str("user.name", "Foo Bar")); } /// Create a new [`Project`] in a git [`Repository`] pub fn new(name: &str, callback: F) -> Project where F: FnOnce(ProjectBuilder) -> ProjectBuilder, { new_repo(name, callback).0 } /// Create a new [`Project`] with access to the [`Repository`] pub fn new_repo(name: &str, callback: F) -> (Project, git2::Repository) where F: FnOnce(ProjectBuilder) -> ProjectBuilder, { let mut git_project = project().at(name); git_project = callback(git_project); let git_project = git_project.build(); let repo = init(&git_project.root()); add(&repo); commit(&repo); (git_project, repo) } /// *(`git2`)* Add all files in the working directory to the git index pub fn add(repo: &git2::Repository) { let mut index = t!(repo.index()); t!(index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)); t!(index.write()); } /// *(`git2`)* Add a git submodule to the repository pub fn add_submodule<'a>( repo: &'a git2::Repository, url: &str, path: &Path, ) -> git2::Submodule<'a> { let path = path.to_str().unwrap().replace(r"\", "/"); let mut s = t!(repo.submodule(url, Path::new(&path), false)); let subrepo = t!(s.open()); default_repo_cfg(&subrepo); t!(subrepo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*")); let mut origin = t!(subrepo.find_remote("origin")); t!(origin.fetch(&Vec::::new(), None, None)); t!(subrepo.checkout_head(None)); t!(s.add_finalize()); s } /// *(`git2`)* Commit changes to the git repository pub fn commit(repo: &git2::Repository) -> git2::Oid { let tree_id = t!(t!(repo.index()).write_tree()); let sig = t!(repo.signature()); let mut parents = Vec::new(); if let Some(parent) = repo.head().ok().map(|h| h.target().unwrap()) { parents.push(t!(repo.find_commit(parent))) } let parents = parents.iter().collect::>(); t!(repo.commit( Some("HEAD"), &sig, &sig, "test", &t!(repo.find_tree(tree_id)), &parents )) } /// *(`git2`)* Create a new tag in the git repository pub fn tag(repo: &git2::Repository, name: &str) { let head = repo.head().unwrap().target().unwrap(); t!(repo.tag( name, &t!(repo.find_object(head, None)), &t!(repo.signature()), "make a new tag", false )); } /// Returns true if gitoxide is globally activated. /// /// That way, tests that normally use `git2` can transparently use `gitoxide`. pub fn cargo_uses_gitoxide() -> bool { std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1") } ================================================ FILE: crates/cargo-test-support/src/install.rs ================================================ //! Helpers for testing `cargo install` use std::env::consts::EXE_SUFFIX; use std::path::Path; /// Used by `cargo install` tests to assert an executable binary /// has been installed. Example usage: /// ```no_run /// use cargo_test_support::install::assert_has_installed_exe; /// use cargo_test_support::paths; /// /// assert_has_installed_exe(paths::cargo_home(), "foo"); /// ``` #[track_caller] pub fn assert_has_installed_exe>(path: P, name: &'static str) { assert!(check_has_installed_exe(path, name)); } #[track_caller] pub fn assert_has_not_installed_exe>(path: P, name: &'static str) { assert!(!check_has_installed_exe(path, name)); } fn check_has_installed_exe>(path: P, name: &'static str) -> bool { path.as_ref().join("bin").join(exe(name)).is_file() } /// `$name$EXE` pub fn exe(name: &str) -> String { format!("{}{}", name, EXE_SUFFIX) } ================================================ FILE: crates/cargo-test-support/src/lib.rs ================================================ //! # Cargo test support. //! //! See for a guide on writing tests. //! //! There are two places you can find API documentation //! //! - : //! targeted at external tool developers testing cargo-related code //! - Released with every rustc release //! - : //! targeted at cargo contributors //! - Updated on each update of the `cargo` submodule in `rust-lang/rust` //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use. This //! > crate may make major changes to its APIs or be deprecated without warning. //! //! # Example //! //! ```rust,no_run //! use cargo_test_support::prelude::*; //! use cargo_test_support::str; //! use cargo_test_support::project; //! //! #[cargo_test] //! fn some_test() { //! let p = project() //! .file("src/main.rs", r#"fn main() { println!("hi!"); }"#) //! .build(); //! //! p.cargo("run --bin foo") //! .with_stderr_data(str![[r#" //! [COMPILING] foo [..] //! [FINISHED] [..] //! [RUNNING] `target/debug/foo` //! "#]]) //! .with_stdout_data(str![["hi!"]]) //! .run(); //! } //! ``` #![allow(clippy::disallowed_methods)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] use std::env; use std::ffi::OsStr; use std::fmt::Write; use std::fs; use std::os; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::sync::LazyLock; use std::sync::OnceLock; use std::thread::JoinHandle; use std::time::{self, Duration}; use anyhow::{Result, bail}; use cargo_util::{ProcessError, is_ci}; use snapbox::IntoData as _; use url::Url; use self::paths::CargoPathExt; /// Unwrap a `Result` with a useful panic message /// /// # Example /// /// ```rust /// use cargo_test_support::t; /// t!(std::fs::read_to_string("Cargo.toml")); /// ``` #[macro_export] macro_rules! t { ($e:expr) => { match $e { Ok(e) => e, Err(e) => $crate::panic_error(&format!("failed running {}", stringify!($e)), e), } }; } pub use cargo_util::ProcessBuilder; pub use snapbox::file; pub use snapbox::str; pub use snapbox::utils::current_dir; /// `panic!`, reporting the specified error , see also [`t!`] #[track_caller] pub fn panic_error(what: &str, err: impl Into) -> ! { let err = err.into(); pe(what, err); #[track_caller] fn pe(what: &str, err: anyhow::Error) -> ! { let mut result = format!("{}\nerror: {}", what, err); for cause in err.chain().skip(1) { let _ = writeln!(result, "\nCaused by:"); let _ = write!(result, "{}", cause); } panic!("\n{}", result); } } pub use cargo_test_macro::cargo_test; pub mod compare; pub mod containers; pub mod cross_compile; pub mod git; pub mod install; pub mod paths; pub mod publish; pub mod registry; pub mod prelude { pub use crate::ArgLineCommandExt; pub use crate::ChannelChangerCommandExt; pub use crate::TestEnvCommandExt; pub use crate::cargo_test; pub use crate::paths::CargoPathExt; pub use snapbox::IntoData; } /* * * ===== Builders ===== * */ #[derive(PartialEq, Clone)] struct FileBuilder { path: PathBuf, body: String, executable: bool, } impl FileBuilder { pub fn new(path: PathBuf, body: &str, executable: bool) -> FileBuilder { FileBuilder { path, body: body.to_string(), executable: executable, } } fn mk(&mut self) { if self.executable { let mut path = self.path.clone().into_os_string(); write!(path, "{}", env::consts::EXE_SUFFIX).unwrap(); self.path = path.into(); } self.dirname().mkdir_p(); fs::write(&self.path, &self.body) .unwrap_or_else(|e| panic!("could not create file {}: {}", self.path.display(), e)); #[cfg(unix)] if self.executable { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(&self.path).unwrap().permissions(); let mode = perms.mode(); perms.set_mode(mode | 0o111); fs::set_permissions(&self.path, perms).unwrap(); } } fn dirname(&self) -> &Path { self.path.parent().unwrap() } } #[derive(PartialEq, Clone)] struct SymlinkBuilder { dst: PathBuf, src: PathBuf, src_is_dir: bool, } impl SymlinkBuilder { pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder { SymlinkBuilder { dst, src, src_is_dir: false, } } pub fn new_dir(dst: PathBuf, src: PathBuf) -> SymlinkBuilder { SymlinkBuilder { dst, src, src_is_dir: true, } } #[cfg(unix)] fn mk(&self) { self.dirname().mkdir_p(); t!(os::unix::fs::symlink(&self.dst, &self.src)); } #[cfg(windows)] fn mk(&mut self) { self.dirname().mkdir_p(); if self.src_is_dir { t!(os::windows::fs::symlink_dir(&self.dst, &self.src)); } else { if let Some(ext) = self.dst.extension() { if ext == env::consts::EXE_EXTENSION { self.src.set_extension(ext); } } t!(os::windows::fs::symlink_file(&self.dst, &self.src)); } } fn dirname(&self) -> &Path { self.src.parent().unwrap() } } /// A cargo project to run tests against. /// /// See [`ProjectBuilder`] or [`Project::from_template`] to get started. pub struct Project { root: PathBuf, } /// Create a project to run tests against /// /// - Creates a [`basic_manifest`] if one isn't supplied /// /// To get started, see: /// - [`project`] /// - [`project_in`] /// - [`project_in_home`] /// - [`Project::from_template`] #[must_use] pub struct ProjectBuilder { root: Project, files: Vec, symlinks: Vec, no_manifest: bool, } impl ProjectBuilder { /// Root of the project /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo` pub fn root(&self) -> PathBuf { self.root.root() } /// Project's debug dir /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug` pub fn target_debug_dir(&self) -> PathBuf { self.root.target_debug_dir() } /// Create project in `root` pub fn new(root: PathBuf) -> ProjectBuilder { ProjectBuilder { root: Project { root }, files: vec![], symlinks: vec![], no_manifest: false, } } /// Create project, relative to [`paths::root`] pub fn at>(mut self, path: P) -> Self { self.root = Project { root: paths::root().join(path), }; self } /// Adds a file to the project. pub fn file>(mut self, path: B, body: &str) -> Self { self._file(path.as_ref(), body, false); self } /// Adds an executable file to the project. pub fn executable>(mut self, path: B, body: &str) -> Self { self._file(path.as_ref(), body, true); self } fn _file(&mut self, path: &Path, body: &str, executable: bool) { self.files.push(FileBuilder::new( self.root.root().join(path), body, executable, )); } /// Adds a symlink to a file to the project. pub fn symlink(mut self, dst: impl AsRef, src: impl AsRef) -> Self { self.symlinks.push(SymlinkBuilder::new( self.root.root().join(dst), self.root.root().join(src), )); self } /// Create a symlink to a directory pub fn symlink_dir(mut self, dst: impl AsRef, src: impl AsRef) -> Self { self.symlinks.push(SymlinkBuilder::new_dir( self.root.root().join(dst), self.root.root().join(src), )); self } pub fn no_manifest(mut self) -> Self { self.no_manifest = true; self } /// Creates the project. pub fn build(mut self) -> Project { // First, clean the directory if it already exists self.rm_root(); // Create the empty directory self.root.root().mkdir_p(); let manifest_path = self.root.root().join("Cargo.toml"); if !self.no_manifest && self.files.iter().all(|fb| fb.path != manifest_path) { self._file( Path::new("Cargo.toml"), &basic_manifest("foo", "0.0.1"), false, ) } let past = time::SystemTime::now() - Duration::new(1, 0); let ftime = filetime::FileTime::from_system_time(past); for file in self.files.iter_mut() { file.mk(); if is_coarse_mtime() { // Place the entire project 1 second in the past to ensure // that if cargo is called multiple times, the 2nd call will // see targets as "fresh". Without this, if cargo finishes in // under 1 second, the second call will see the mtime of // source == mtime of output and consider it dirty. filetime::set_file_times(&file.path, ftime, ftime).unwrap(); } } for symlink in self.symlinks.iter_mut() { symlink.mk(); } let ProjectBuilder { root, .. } = self; root } fn rm_root(&self) { self.root.root().rm_rf() } } impl Project { /// Copy the test project from a fixed state pub fn from_template(template_path: impl AsRef) -> Self { let root = paths::root(); let project_root = root.join("case"); snapbox::dir::copy_template(template_path.as_ref(), &project_root).unwrap(); Self { root: project_root } } /// Root of the project /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo` pub fn root(&self) -> PathBuf { self.root.clone() } /// Project's target dir /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target` pub fn build_dir(&self) -> PathBuf { self.root().join("target") } /// Project's debug dir /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug` pub fn target_debug_dir(&self) -> PathBuf { self.build_dir().join("debug") } /// File url for root /// /// ex: `file://$CARGO_TARGET_TMPDIR/cit/t0/foo` pub fn url(&self) -> Url { use paths::CargoPathExt; self.root().to_url() } /// Path to an example built as a library. /// /// `kind` should be one of: "lib", "rlib", "staticlib", "dylib", "proc-macro" /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug/examples/libex.rlib` pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf { self.target_debug_dir() .join("examples") .join(paths::get_lib_filename(name, kind)) } /// Path to a dynamic library. /// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/examples/libex.dylib` pub fn dylib(&self, name: &str) -> PathBuf { self.target_debug_dir().join(format!( "{}{name}{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX )) } /// Path to a debug binary. /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug/foo` pub fn bin(&self, b: &str) -> PathBuf { self.build_dir() .join("debug") .join(&format!("{}{}", b, env::consts::EXE_SUFFIX)) } /// Path to a release binary. /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/release/foo` pub fn release_bin(&self, b: &str) -> PathBuf { self.build_dir() .join("release") .join(&format!("{}{}", b, env::consts::EXE_SUFFIX)) } /// Path to a debug binary for a specific target triple. /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/i686-apple-darwin/debug/foo` pub fn target_bin(&self, target: &str, b: &str) -> PathBuf { self.build_dir().join(target).join("debug").join(&format!( "{}{}", b, env::consts::EXE_SUFFIX )) } /// Returns an iterator of paths within [`Project::root`] matching the glob pattern pub fn glob>(&self, pattern: P) -> glob::Paths { let pattern = self.root().join(pattern); glob::glob(pattern.to_str().expect("failed to convert pattern to str")) .expect("failed to glob") } /// Overwrite a file with new content /// // # Example: /// /// ```no_run /// # let p = cargo_test_support::project().build(); /// p.change_file("src/lib.rs", "fn new_fn() {}"); /// ``` pub fn change_file(&self, path: impl AsRef, body: &str) { FileBuilder::new(self.root().join(path), body, false).mk() } /// Creates a `ProcessBuilder` to run a program in the project /// and wrap it in an Execs to assert on the execution. /// /// # Example: /// /// ```no_run /// # use cargo_test_support::str; /// # let p = cargo_test_support::project().build(); /// p.process(&p.bin("foo")) /// .with_stdout_data(str!["bar\n"]) /// .run(); /// ``` pub fn process>(&self, program: T) -> Execs { let mut p = process(program); p.cwd(self.root()); execs().with_process_builder(p) } /// Safely run a process after `cargo build`. /// /// Windows has a problem where a process cannot be reliably /// be replaced, removed, or renamed immediately after executing it. /// The action may fail (with errors like Access is denied), or /// it may succeed, but future attempts to use the same filename /// will fail with "Already Exists". /// /// If you have a test that needs to do `cargo run` multiple /// times, you should instead use `cargo build` and use this /// method to run the executable. Each time you call this, /// use a new name for `dst`. /// See rust-lang/cargo#5481. pub fn rename_run(&self, src: &str, dst: &str) -> Execs { let src = self.bin(src); let dst = self.bin(dst); fs::rename(&src, &dst) .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e)); self.process(dst) } /// Returns the contents of `Cargo.lock`. pub fn read_lockfile(&self) -> String { self.read_file("Cargo.lock") } /// Returns the contents of a path in the project root pub fn read_file(&self, path: impl AsRef) -> String { let full = self.root().join(path); fs::read_to_string(&full) .unwrap_or_else(|e| panic!("could not read file {}: {}", full.display(), e)) } /// Modifies `Cargo.toml` to remove all commented lines. pub fn uncomment_root_manifest(&self) { let contents = self.read_file("Cargo.toml").replace("#", ""); fs::write(self.root().join("Cargo.toml"), contents).unwrap(); } pub fn symlink(&self, src: impl AsRef, dst: impl AsRef) { let src = self.root().join(src.as_ref()); let dst = self.root().join(dst.as_ref()); #[cfg(unix)] { if let Err(e) = os::unix::fs::symlink(&src, &dst) { panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e); } } #[cfg(windows)] { if src.is_dir() { if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) { panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e); } } else { if let Err(e) = os::windows::fs::symlink_file(&src, &dst) { panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e); } } } } } /// Generates a project layout, see [`ProjectBuilder`] pub fn project() -> ProjectBuilder { ProjectBuilder::new(paths::root().join("foo")) } /// Generates a project layout in given directory, see [`ProjectBuilder`] pub fn project_in(dir: impl AsRef) -> ProjectBuilder { ProjectBuilder::new(paths::root().join(dir).join("foo")) } /// Generates a project layout inside our fake home dir, see [`ProjectBuilder`] pub fn project_in_home(name: impl AsRef) -> ProjectBuilder { ProjectBuilder::new(paths::home().join(name)) } // === Helpers === /// Generate a `main.rs` printing the specified text /// /// ```rust /// # use cargo_test_support::main_file; /// # mod dep { /// # fn bar() -> &'static str { /// # "world" /// # } /// # } /// main_file( /// r#""hello {}", dep::bar()"#, /// &[] /// ); /// ``` pub fn main_file(println: &str, externed_deps: &[&str]) -> String { let mut buf = String::new(); for dep in externed_deps.iter() { buf.push_str(&format!("extern crate {};\n", dep)); } buf.push_str("fn main() { println!("); buf.push_str(println); buf.push_str("); }\n"); buf } /// This is the raw output from the process. /// /// This is similar to `std::process::Output`, however the `status` is /// translated to the raw `code`. This is necessary because `ProcessError` /// does not have access to the raw `ExitStatus` because `ProcessError` needs /// to be serializable (for the Rustc cache), and `ExitStatus` does not /// provide a constructor. pub struct RawOutput { pub code: Option, pub stdout: Vec, pub stderr: Vec, } /// Run and verify a [`ProcessBuilder`] /// /// Construct with /// - [`execs`] /// - [`Project`] methods /// - `cargo_process` in testsuite #[must_use] #[derive(Clone)] pub struct Execs { ran: bool, process_builder: Option, expect_stdin: Option, expect_exit_code: Option, expect_stdout_data: Option, expect_stderr_data: Option, expect_stdout_contains: Vec, expect_stderr_contains: Vec, expect_stdout_not_contains: Vec, expect_stderr_not_contains: Vec, expect_stderr_with_without: Vec<(Vec, Vec)>, stream_output: bool, assert: snapbox::Assert, } impl Execs { pub fn with_process_builder(mut self, p: ProcessBuilder) -> Execs { self.process_builder = Some(p); self } } /// # Configure assertions impl Execs { /// Verifies that stdout is equal to the given lines. /// /// See [`compare::assert_e2e`] for assertion details. /// ///
/// /// Prefer passing in [`str!`] for `expected` to get snapshot updating. /// /// If `format!` is needed for content that changes from run to run that you don't care about, /// consider whether you could have [`compare::assert_e2e`] redact the content. /// If nothing else, a wildcard (`[..]`, `...`) may be useful. /// /// However, `""` may be preferred for intentionally empty output so people don't accidentally /// bless a change. /// ///
/// /// # Examples /// /// ```no_run /// use cargo_test_support::prelude::*; /// use cargo_test_support::str; /// use cargo_test_support::execs; /// /// execs().with_stdout_data(str![r#" /// Hello world! /// "#]); /// ``` /// /// Non-deterministic compiler output /// ```no_run /// use cargo_test_support::prelude::*; /// use cargo_test_support::str; /// use cargo_test_support::execs; /// /// execs().with_stdout_data(str![r#" /// [COMPILING] foo /// [COMPILING] bar /// "#].unordered()); /// ``` /// /// jsonlines /// ```no_run /// use cargo_test_support::prelude::*; /// use cargo_test_support::str; /// use cargo_test_support::execs; /// /// execs().with_stdout_data(str![r#" /// [ /// {}, /// {} /// ] /// "#].is_json().against_jsonlines()); /// ``` pub fn with_stdout_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self { self.expect_stdout_data = Some(expected.into_data()); self } /// Verifies that stderr is equal to the given lines. /// /// See [`compare::assert_e2e`] for assertion details. /// ///
/// /// Prefer passing in [`str!`] for `expected` to get snapshot updating. /// /// If `format!` is needed for content that changes from run to run that you don't care about, /// consider whether you could have [`compare::assert_e2e`] redact the content. /// If nothing else, a wildcard (`[..]`, `...`) may be useful. /// /// However, `""` may be preferred for intentionally empty output so people don't accidentally /// bless a change. /// ///
/// /// # Examples /// /// ```no_run /// use cargo_test_support::prelude::*; /// use cargo_test_support::str; /// use cargo_test_support::execs; /// /// execs().with_stderr_data(str![r#" /// Hello world! /// "#]); /// ``` /// /// Non-deterministic compiler output /// ```no_run /// use cargo_test_support::prelude::*; /// use cargo_test_support::str; /// use cargo_test_support::execs; /// /// execs().with_stderr_data(str![r#" /// [COMPILING] foo /// [COMPILING] bar /// "#].unordered()); /// ``` /// /// jsonlines /// ```no_run /// use cargo_test_support::prelude::*; /// use cargo_test_support::str; /// use cargo_test_support::execs; /// /// execs().with_stderr_data(str![r#" /// [ /// {}, /// {} /// ] /// "#].is_json().against_jsonlines()); /// ``` pub fn with_stderr_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self { self.expect_stderr_data = Some(expected.into_data()); self } /// Writes the given lines to stdin. pub fn with_stdin(&mut self, expected: S) -> &mut Self { self.expect_stdin = Some(expected.to_string()); self } /// Verifies the exit code from the process. /// /// This is not necessary if the expected exit code is `0`. pub fn with_status(&mut self, expected: i32) -> &mut Self { self.expect_exit_code = Some(expected); self } /// Removes exit code check for the process. /// /// By default, the expected exit code is `0`. pub fn without_status(&mut self) -> &mut Self { self.expect_exit_code = None; self } /// Verifies that stdout contains the given contiguous lines somewhere in /// its output. /// /// See [`compare`] for supported patterns. /// ///
/// /// Prefer [`Execs::with_stdout_data`] where possible. /// - `expected` cannot be snapshotted /// - `expected` can end up being ambiguous, causing the assertion to succeed when it should fail /// ///
pub fn with_stdout_contains(&mut self, expected: S) -> &mut Self { self.expect_stdout_contains.push(expected.to_string()); self } /// Verifies that stderr contains the given contiguous lines somewhere in /// its output. /// /// See [`compare`] for supported patterns. /// ///
/// /// Prefer [`Execs::with_stderr_data`] where possible. /// - `expected` cannot be snapshotted /// - `expected` can end up being ambiguous, causing the assertion to succeed when it should fail /// ///
pub fn with_stderr_contains(&mut self, expected: S) -> &mut Self { self.expect_stderr_contains.push(expected.to_string()); self } /// Verifies that stdout does not contain the given contiguous lines. /// /// See [`compare`] for supported patterns. /// /// See note on [`Self::with_stderr_does_not_contain`]. /// ///
/// /// Prefer [`Execs::with_stdout_data`] where possible. /// - `expected` cannot be snapshotted /// - The absence of `expected` can either mean success or that the string being looked for /// changed. /// /// To mitigate this, consider matching this up with /// [`Execs::with_stdout_contains`]. /// ///
pub fn with_stdout_does_not_contain(&mut self, expected: S) -> &mut Self { self.expect_stdout_not_contains.push(expected.to_string()); self } /// Verifies that stderr does not contain the given contiguous lines. /// /// See [`compare`] for supported patterns. /// ///
/// /// Prefer [`Execs::with_stdout_data`] where possible. /// - `expected` cannot be snapshotted /// - The absence of `expected` can either mean success or that the string being looked for /// changed. /// /// To mitigate this, consider either matching this up with /// [`Execs::with_stdout_contains`] or replace it /// with [`Execs::with_stderr_line_without`]. /// ///
pub fn with_stderr_does_not_contain(&mut self, expected: S) -> &mut Self { self.expect_stderr_not_contains.push(expected.to_string()); self } /// Verify that a particular line appears in stderr with and without the /// given substrings. Exactly one line must match. /// /// The substrings are matched as `contains`. /// ///
/// /// Prefer [`Execs::with_stdout_data`] where possible. /// - `with` cannot be snapshotted /// - The absence of `without` can either mean success or that the string being looked for /// changed. /// ///
/// /// # Example /// /// ```no_run /// use cargo_test_support::execs; /// /// execs().with_stderr_line_without( /// &[ /// "[RUNNING] `rustc --crate-name build_script_build", /// "-C opt-level=3", /// ], /// &["-C debuginfo", "-C incremental"], /// ); /// ``` /// /// This will check that a build line includes `-C opt-level=3` but does /// not contain `-C debuginfo` or `-C incremental`. /// pub fn with_stderr_line_without( &mut self, with: &[S], without: &[S], ) -> &mut Self { let with = with.iter().map(|s| s.to_string()).collect(); let without = without.iter().map(|s| s.to_string()).collect(); self.expect_stderr_with_without.push((with, without)); self } } /// # Configure the process impl Execs { /// Forward subordinate process stdout/stderr to the terminal. /// Useful for printf debugging of the tests. /// CAUTION: CI will fail if you leave this in your test! #[allow(unused)] pub fn stream(&mut self) -> &mut Self { self.stream_output = true; self } pub fn arg>(&mut self, arg: T) -> &mut Self { if let Some(ref mut p) = self.process_builder { p.arg(arg); } self } pub fn args>(&mut self, args: &[T]) -> &mut Self { if let Some(ref mut p) = self.process_builder { p.args(args); } self } pub fn cwd>(&mut self, path: T) -> &mut Self { if let Some(ref mut p) = self.process_builder { if let Some(cwd) = p.get_cwd() { let new_path = cwd.join(path.as_ref()); p.cwd(new_path); } else { p.cwd(path); } } self } pub fn env>(&mut self, key: &str, val: T) -> &mut Self { if let Some(ref mut p) = self.process_builder { p.env(key, val); } self } pub fn env_remove(&mut self, key: &str) -> &mut Self { if let Some(ref mut p) = self.process_builder { p.env_remove(key); } self } /// Enables nightly features for testing /// /// The list of reasons should be why nightly cargo is needed. If it is /// because of an unstable feature put the name of the feature as the reason, /// e.g. `&["print-im-a-teapot"]` pub fn masquerade_as_nightly_cargo(&mut self, reasons: &[&str]) -> &mut Self { if let Some(ref mut p) = self.process_builder { p.masquerade_as_nightly_cargo(reasons); } self } /// Overrides the crates.io URL for testing. /// /// Can be used for testing crates-io functionality where alt registries /// cannot be used. pub fn replace_crates_io(&mut self, url: &Url) -> &mut Self { if let Some(ref mut p) = self.process_builder { p.env("__CARGO_TEST_CRATES_IO_URL_DO_NOT_USE_THIS", url.as_str()); } self } pub fn overlay_registry(&mut self, url: &Url, path: &str) -> &mut Self { if let Some(ref mut p) = self.process_builder { let env_value = format!("{}={}", url, path); p.env( "__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS", env_value, ); } self } pub fn enable_split_debuginfo_packed(&mut self) -> &mut Self { self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed") .env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed") .env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed") .env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed"); self } pub fn enable_mac_dsym(&mut self) -> &mut Self { if cfg!(target_os = "macos") { return self.enable_split_debuginfo_packed(); } self } } /// # Run and verify the process impl Execs { pub fn exec_with_output(&mut self) -> Result { self.ran = true; // TODO avoid unwrap let p = (&self.process_builder).clone().unwrap(); p.exec_with_output() } pub fn build_command(&mut self) -> Command { self.ran = true; // TODO avoid unwrap let p = (&self.process_builder).clone().unwrap(); p.build_command() } #[track_caller] pub fn run(&mut self) -> RawOutput { self.ran = true; let mut p = (&self.process_builder).clone().unwrap(); if let Some(stdin) = self.expect_stdin.take() { p.stdin(stdin); } match self.match_process(&p) { Err(e) => panic_error(&format!("test failed running {}", p), e), Ok(output) => output, } } /// Runs the process, checks the expected output, and returns the first /// JSON object on stdout. #[track_caller] pub fn run_json(&mut self) -> serde_json::Value { let output = self.run(); serde_json::from_slice(&output.stdout).unwrap_or_else(|e| { panic!( "\nfailed to parse JSON: {}\n\ output was:\n{}\n", e, String::from_utf8_lossy(&output.stdout) ); }) } #[track_caller] pub fn run_output(&mut self, output: &Output) { self.ran = true; if let Err(e) = self.match_output(output.status.code(), &output.stdout, &output.stderr) { panic_error("process did not return the expected result", e) } } #[track_caller] fn verify_checks_output(&self, stdout: &[u8], stderr: &[u8]) { if self.expect_exit_code.unwrap_or(0) != 0 && self.expect_stdin.is_none() && self.expect_stdout_data.is_none() && self.expect_stderr_data.is_none() && self.expect_stdout_contains.is_empty() && self.expect_stderr_contains.is_empty() && self.expect_stdout_not_contains.is_empty() && self.expect_stderr_not_contains.is_empty() && self.expect_stderr_with_without.is_empty() { panic!( "`with_status()` is used, but no output is checked.\n\ The test must check the output to ensure the correct error is triggered.\n\ --- stdout\n{}\n--- stderr\n{}", String::from_utf8_lossy(stdout), String::from_utf8_lossy(stderr), ); } } #[track_caller] fn match_process(&self, process: &ProcessBuilder) -> Result { println!("running {}", process); let res = if self.stream_output { if is_ci() { panic!("`.stream()` is for local debugging") } process.exec_with_streaming( &mut |out| { println!("{}", out); Ok(()) }, &mut |err| { eprintln!("{}", err); Ok(()) }, true, ) } else { process.exec_with_output() }; match res { Ok(out) => { self.match_output(out.status.code(), &out.stdout, &out.stderr)?; return Ok(RawOutput { stdout: out.stdout, stderr: out.stderr, code: out.status.code(), }); } Err(e) => { if let Some(ProcessError { stdout: Some(stdout), stderr: Some(stderr), code, .. }) = e.downcast_ref::() { self.match_output(*code, stdout, stderr)?; return Ok(RawOutput { stdout: stdout.to_vec(), stderr: stderr.to_vec(), code: *code, }); } bail!("could not exec process {}: {:?}", process, e) } } } #[track_caller] fn match_output(&self, code: Option, stdout: &[u8], stderr: &[u8]) -> Result<()> { self.verify_checks_output(stdout, stderr); let stdout = std::str::from_utf8(stdout).expect("stdout is not utf8"); let stderr = std::str::from_utf8(stderr).expect("stderr is not utf8"); match self.expect_exit_code { None => {} Some(expected) if code == Some(expected) => {} Some(expected) => bail!( "process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}", code.unwrap_or(-1), expected, stdout, stderr ), } if let Some(expect_stdout_data) = &self.expect_stdout_data { if let Err(err) = self.assert.try_eq( Some(&"stdout"), stdout.into_data(), expect_stdout_data.clone(), ) { panic!("{err}") } } if let Some(expect_stderr_data) = &self.expect_stderr_data { if let Err(err) = self.assert.try_eq( Some(&"stderr"), stderr.into_data(), expect_stderr_data.clone(), ) { panic!("{err}") } } for expect in self.expect_stdout_contains.iter() { compare::match_contains(expect, stdout, self.assert.redactions())?; } for expect in self.expect_stderr_contains.iter() { compare::match_contains(expect, stderr, self.assert.redactions())?; } for expect in self.expect_stdout_not_contains.iter() { compare::match_does_not_contain(expect, stdout, self.assert.redactions())?; } for expect in self.expect_stderr_not_contains.iter() { compare::match_does_not_contain(expect, stderr, self.assert.redactions())?; } for (with, without) in self.expect_stderr_with_without.iter() { compare::match_with_without(stderr, with, without, self.assert.redactions())?; } Ok(()) } } impl Drop for Execs { fn drop(&mut self) { if !self.ran && !std::thread::panicking() { panic!("forgot to run this command"); } } } /// Run and verify a process, see [`Execs`] pub fn execs() -> Execs { Execs { ran: false, process_builder: None, expect_stdin: None, expect_exit_code: Some(0), expect_stdout_data: None, expect_stderr_data: None, expect_stdout_contains: Vec::new(), expect_stderr_contains: Vec::new(), expect_stdout_not_contains: Vec::new(), expect_stderr_not_contains: Vec::new(), expect_stderr_with_without: Vec::new(), stream_output: false, assert: compare::assert_e2e(), } } /// Generate a basic `Cargo.toml` pub fn basic_manifest(name: &str, version: &str) -> String { format!( r#" [package] name = "{}" version = "{}" authors = [] edition = "2015" "#, name, version ) } /// Generate a `Cargo.toml` with the specified `bin.name` pub fn basic_bin_manifest(name: &str) -> String { format!( r#" [package] name = "{}" version = "0.5.0" authors = ["wycats@example.com"] edition = "2015" [[bin]] name = "{}" "#, name, name ) } /// Generate a `Cargo.toml` with the specified `lib.name` pub fn basic_lib_manifest(name: &str) -> String { format!( r#" [package] name = "{}" version = "0.5.0" authors = ["wycats@example.com"] edition = "2015" [lib] name = "{}" "#, name, name ) } /// Gets a valid target spec JSON from rustc. /// /// To avoid any hardcoded value, this fetches `x86_64-unknown-none` target /// spec JSON directly from `rustc`, as Cargo shouldn't know the JSON schema. pub fn target_spec_json() -> &'static str { static TARGET_SPEC_JSON: LazyLock = LazyLock::new(|| { let json = std::process::Command::new("rustc") .env("RUSTC_BOOTSTRAP", "1") .arg("--print") .arg("target-spec-json") .arg("-Zunstable-options") .arg("--target") .arg("x86_64-unknown-none") .output() .expect("rustc --print target-spec-json") .stdout; String::from_utf8(json).expect("utf8 target spec json") }); TARGET_SPEC_JSON.as_str() } struct RustcInfo { verbose_version: String, host: String, } impl RustcInfo { fn new() -> RustcInfo { let output = ProcessBuilder::new("rustc") .arg("-vV") .exec_with_output() .expect("rustc should exec"); let verbose_version = String::from_utf8(output.stdout).expect("utf8 output"); let host = verbose_version .lines() .filter_map(|line| line.strip_prefix("host: ")) .next() .expect("verbose version has host: field") .to_string(); RustcInfo { verbose_version, host, } } } fn rustc_info() -> &'static RustcInfo { static RUSTC_INFO: OnceLock = OnceLock::new(); RUSTC_INFO.get_or_init(RustcInfo::new) } /// The rustc host such as `x86_64-unknown-linux-gnu`. pub fn rustc_host() -> &'static str { &rustc_info().host } /// The host triple suitable for use in a cargo environment variable (uppercased). pub fn rustc_host_env() -> String { rustc_host().to_uppercase().replace('-', "_") } pub fn is_nightly() -> bool { let vv = &rustc_info().verbose_version; // CARGO_TEST_DISABLE_NIGHTLY is set in rust-lang/rust's CI so that all // nightly-only tests are disabled there. Otherwise, it could make it // difficult to land changes which would need to be made simultaneously in // rust-lang/cargo and rust-lan/rust, which isn't possible. env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err() && (vv.contains("-nightly") || vv.contains("-dev")) } /// Run `$bin` in the test's environment, see [`ProcessBuilder`] /// /// For more on the test environment, see /// - [`paths::root`] /// - [`TestEnvCommandExt`] pub fn process>(bin: T) -> ProcessBuilder { _process(bin.as_ref()) } fn _process(t: &OsStr) -> ProcessBuilder { let mut p = ProcessBuilder::new(t); p.cwd(&paths::root()).test_env(); p } /// Enable nightly features for testing pub trait ChannelChangerCommandExt { /// The list of reasons should be why nightly cargo is needed. If it is /// because of an unstable feature put the name of the feature as the reason, /// e.g. `&["print-im-a-teapot"]`. fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self; } impl ChannelChangerCommandExt for &mut ProcessBuilder { fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self { self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly") } } impl ChannelChangerCommandExt for snapbox::cmd::Command { fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self { self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly") } } /// Establish a process's test environment pub trait TestEnvCommandExt: Sized { fn test_env(mut self) -> Self { // In general just clear out all cargo-specific configuration already in the // environment. Our tests all assume a "default configuration" unless // specified otherwise. for (k, _v) in env::vars() { if k.starts_with("CARGO_") { self = self.env_remove(&k); } } if env::var_os("RUSTUP_TOOLCHAIN").is_some() { // Override the PATH to avoid executing the rustup wrapper thousands // of times. This makes the testsuite run substantially faster. static RUSTC_DIR: OnceLock = OnceLock::new(); let rustc_dir = RUSTC_DIR.get_or_init(|| { match ProcessBuilder::new("rustup") .args(&["which", "rustc"]) .exec_with_output() { Ok(output) => { let s = std::str::from_utf8(&output.stdout).expect("utf8").trim(); let mut p = PathBuf::from(s); p.pop(); p } Err(e) => { panic!("RUSTUP_TOOLCHAIN was set, but could not run rustup: {}", e); } } }); let path = env::var_os("PATH").unwrap_or_default(); let paths = env::split_paths(&path); let new_path = env::join_paths(std::iter::once(rustc_dir.clone()).chain(paths)).unwrap(); self = self.env("PATH", new_path); } self = self .current_dir(&paths::root()) .env("HOME", paths::home()) .env("CARGO_HOME", paths::cargo_home()) .env("__CARGO_TEST_ROOT", paths::global_root()) // Force Cargo to think it's on the stable channel for all tests, this // should hopefully not surprise us as we add cargo features over time and // cargo rides the trains. .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable") // Keeps cargo within its sandbox. .env("__CARGO_TEST_DISABLE_GLOBAL_KNOWN_HOST", "1") // Set retry sleep to 1 millisecond. .env("__CARGO_TEST_FIXED_RETRY_SLEEP_MS", "1") // Setting this to a large number helps avoid problems with long // paths getting trimmed in snapshot tests. // // When updating this value, keep in mind that the `CARGO_TARGET_DIR` // that gets set when Cargo's tests get run in `rust-lang/rust` can // easily cause path lengths to exceed 200 characters. .env("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS", "400") // Incremental generates a huge amount of data per test, which we // don't particularly need. Tests that specifically need to check // the incremental behavior should turn this back on. .env("CARGO_INCREMENTAL", "0") // Don't read the system git config which is out of our control. .env("GIT_CONFIG_NOSYSTEM", "1") .env_remove("__CARGO_DEFAULT_LIB_METADATA") .env_remove("ALL_PROXY") .env_remove("EMAIL") .env_remove("GIT_AUTHOR_EMAIL") .env_remove("GIT_AUTHOR_NAME") .env_remove("GIT_COMMITTER_EMAIL") .env_remove("GIT_COMMITTER_NAME") .env_remove("http_proxy") .env_remove("HTTPS_PROXY") .env_remove("https_proxy") .env_remove("MAKEFLAGS") .env_remove("MFLAGS") .env_remove("MSYSTEM") // assume cmd.exe everywhere on windows .env_remove("RUSTC") .env_remove("RUST_BACKTRACE") .env_remove("RUSTC_WORKSPACE_WRAPPER") .env_remove("RUSTC_WRAPPER") .env_remove("RUSTDOC") .env_remove("RUSTDOCFLAGS") .env_remove("RUSTFLAGS") .env_remove("SSH_AUTH_SOCK") // ensure an outer agent is never contacted .env_remove("USER") // not set on some rust-lang docker images .env_remove("XDG_CONFIG_HOME") // see #2345 .env_remove("OUT_DIR"); // see #13204 if cfg!(windows) { self = self.env("USERPROFILE", paths::home()); } self } fn current_dir>(self, path: S) -> Self; fn env>(self, key: &str, value: S) -> Self; fn env_remove(self, key: &str) -> Self; } impl TestEnvCommandExt for &mut ProcessBuilder { fn current_dir>(self, path: S) -> Self { let path = path.as_ref(); self.cwd(path) } fn env>(self, key: &str, value: S) -> Self { self.env(key, value) } fn env_remove(self, key: &str) -> Self { self.env_remove(key) } } impl TestEnvCommandExt for snapbox::cmd::Command { fn current_dir>(self, path: S) -> Self { self.current_dir(path) } fn env>(self, key: &str, value: S) -> Self { self.env(key, value) } fn env_remove(self, key: &str) -> Self { self.env_remove(key) } } /// Add a list of arguments as a line pub trait ArgLineCommandExt: Sized { fn arg_line(mut self, s: &str) -> Self { for mut arg in s.split_whitespace() { if (arg.starts_with('"') && arg.ends_with('"')) || (arg.starts_with('\'') && arg.ends_with('\'')) { arg = &arg[1..(arg.len() - 1).max(1)]; } else if arg.contains(&['"', '\''][..]) { panic!("shell-style argument parsing is not supported") } self = self.arg(arg); } self } fn arg>(self, s: S) -> Self; } impl ArgLineCommandExt for &mut ProcessBuilder { fn arg>(self, s: S) -> Self { self.arg(s) } } impl ArgLineCommandExt for &mut Execs { fn arg>(self, s: S) -> Self { self.arg(s) } } impl ArgLineCommandExt for snapbox::cmd::Command { fn arg>(self, s: S) -> Self { self.arg(s) } } /// Run `git $arg_line`, see [`ProcessBuilder`] pub fn git_process(arg_line: &str) -> ProcessBuilder { let mut p = process("git"); p.arg_line(arg_line); p } pub fn sleep_ms(ms: u64) { ::std::thread::sleep(Duration::from_millis(ms)); } /// Returns `true` if the local filesystem has low-resolution mtimes. pub fn is_coarse_mtime() -> bool { // If the filetime crate is being used to emulate HFS then // return `true`, without looking at the actual hardware. cfg!(emulate_second_only_system) || // This should actually be a test that `$CARGO_TARGET_DIR` is on an HFS // filesystem, (or any filesystem with low-resolution mtimes). However, // that's tricky to detect, so for now just deal with CI. cfg!(target_os = "macos") && is_ci() } /// A way for to increase the cut off for all the time based test. /// /// Some CI setups are much slower then the equipment used by Cargo itself. /// Architectures that do not have a modern processor, hardware emulation, etc. pub fn slow_cpu_multiplier(main: u64) -> Duration { static SLOW_CPU_MULTIPLIER: OnceLock = OnceLock::new(); let slow_cpu_multiplier = SLOW_CPU_MULTIPLIER.get_or_init(|| { env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER") .ok() .and_then(|m| m.parse().ok()) .unwrap_or(1) }); Duration::from_secs(slow_cpu_multiplier * main) } #[cfg(windows)] pub fn symlink_supported() -> bool { if is_ci() { // We want to be absolutely sure this runs on CI. return true; } let src = paths::root().join("symlink_src"); fs::write(&src, "").unwrap(); let dst = paths::root().join("symlink_dst"); let result = match os::windows::fs::symlink_file(&src, &dst) { Ok(_) => { fs::remove_file(&dst).unwrap(); true } Err(e) => { eprintln!( "symlinks not supported: {:?}\n\ Windows 10 users should enable developer mode.", e ); false } }; fs::remove_file(&src).unwrap(); return result; } #[cfg(not(windows))] pub fn symlink_supported() -> bool { true } /// The error message for ENOENT. pub fn no_such_file_err_msg() -> String { std::io::Error::from_raw_os_error(2).to_string() } /// Helper to retry a function `n` times. /// /// The function should return `Some` when it is ready. #[track_caller] pub fn retry(n: u32, mut f: F) -> R where F: FnMut() -> Option, { let mut count = 0; let start = std::time::Instant::now(); loop { if let Some(r) = f() { return r; } count += 1; if count > n { panic!( "test did not finish within {n} attempts ({:?} total)", start.elapsed() ); } sleep_ms(100); } } #[test] #[should_panic(expected = "test did not finish")] fn retry_fails() { retry(2, || None::<()>); } /// Helper that waits for a thread to finish, up to `n` tenths of a second. #[track_caller] pub fn thread_wait_timeout(n: u32, thread: JoinHandle) -> T { retry(n, || thread.is_finished().then_some(())); thread.join().unwrap() } /// Helper that runs some function, and waits up to `n` tenths of a second for /// it to finish. #[track_caller] pub fn threaded_timeout(n: u32, f: F) -> R where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { let thread = std::thread::spawn(|| f()); thread_wait_timeout(n, thread) } // Helper for testing dep-info files in the fingerprint dir. #[track_caller] pub fn assert_deps(project: &Project, fingerprint: &str, test_cb: impl Fn(&Path, &[(u8, &str)])) { let mut files = project .glob(fingerprint) .map(|f| f.expect("unwrap glob result")) // Filter out `.json` entries. .filter(|f| f.extension().is_none()); let info_path = files .next() .unwrap_or_else(|| panic!("expected 1 dep-info file at {}, found 0", fingerprint)); assert!(files.next().is_none(), "expected only 1 dep-info file"); let dep_info = fs::read(&info_path).unwrap(); let dep_info = &mut &dep_info[..]; // Consume the magic marker and version. Here they don't really matter. read_usize(dep_info); read_u8(dep_info); read_u8(dep_info); let deps = (0..read_usize(dep_info)) .map(|_| { let ty = read_u8(dep_info); let path = std::str::from_utf8(read_bytes(dep_info)).unwrap(); let checksum_present = read_bool(dep_info); if checksum_present { // Read out the checksum info without using it let _file_len = read_u64(dep_info); let _checksum = read_bytes(dep_info); } (ty, path) }) .collect::>(); test_cb(&info_path, &deps); fn read_usize(bytes: &mut &[u8]) -> usize { let ret = &bytes[..4]; *bytes = &bytes[4..]; u32::from_le_bytes(ret.try_into().unwrap()) as usize } fn read_u8(bytes: &mut &[u8]) -> u8 { let ret = bytes[0]; *bytes = &bytes[1..]; ret } fn read_bool(bytes: &mut &[u8]) -> bool { read_u8(bytes) != 0 } fn read_u64(bytes: &mut &[u8]) -> u64 { let ret = &bytes[..8]; *bytes = &bytes[8..]; u64::from_le_bytes(ret.try_into().unwrap()) } fn read_bytes<'a>(bytes: &mut &'a [u8]) -> &'a [u8] { let n = read_usize(bytes); let ret = &bytes[..n]; *bytes = &bytes[n..]; ret } } #[track_caller] pub fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u8, &str)]) { assert_deps(project, fingerprint, |info_path, entries| { for (e_kind, e_path) in expected { let pattern = glob::Pattern::new(e_path).unwrap(); let count = entries .iter() .filter(|(kind, path)| kind == e_kind && pattern.matches(path)) .count(); if count != 1 { panic!( "Expected 1 match of {} {} in {:?}, got {}:\n{:#?}", e_kind, e_path, info_path, count, entries ); } } }) } #[track_caller] pub fn assert_deterministic_mtime(path: impl AsRef) { // Hardcoded value be removed once alexcrichton/tar-rs#420 is merged and released. // See also rust-lang/cargo#16237 const DETERMINISTIC_TIMESTAMP: u64 = 1153704088; let path = path.as_ref(); let mtime = path.metadata().unwrap().modified().unwrap(); let timestamp = mtime .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); assert_eq!( timestamp, DETERMINISTIC_TIMESTAMP, "expected deterministic mtime for {path:?}, got {timestamp}" ); } ================================================ FILE: crates/cargo-test-support/src/paths.rs ================================================ //! Access common paths and manipulate the filesystem use filetime::FileTime; use itertools::Itertools; use walkdir::WalkDir; use std::cell::RefCell; use std::fs; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::Mutex; use std::sync::OnceLock; use std::sync::atomic::{AtomicUsize, Ordering}; use crate::compare::assert_e2e; use crate::compare::match_contains; static CARGO_INTEGRATION_TEST_DIR: &str = "cit"; static GLOBAL_ROOT: OnceLock>> = OnceLock::new(); fn set_global_root(tmp_dir: &'static str) { let mut lock = GLOBAL_ROOT .get_or_init(|| Default::default()) .lock() .unwrap(); if lock.is_none() { let mut root = PathBuf::from(tmp_dir); root.push(CARGO_INTEGRATION_TEST_DIR); *lock = Some(root); } } /// Path to the parent directory of all test [`root`]s /// /// ex: `$CARGO_TARGET_TMPDIR/cit` pub fn global_root() -> PathBuf { let lock = GLOBAL_ROOT .get_or_init(|| Default::default()) .lock() .unwrap(); match lock.as_ref() { Some(p) => p.clone(), None => unreachable!("GLOBAL_ROOT not set yet"), } } // We need to give each test a unique id. The test name serve this // purpose. We are able to get the test name by having the `cargo-test-macro` // crate automatically insert an init function for each test that sets the // test name in a thread local variable. thread_local! { static TEST_ID: RefCell> = const { RefCell::new(None) }; static TEST_DIR: RefCell> = const { RefCell::new(None) }; } /// See [`init_root`] pub struct TestIdGuard { _private: (), } /// For test harnesses like [`crate::cargo_test`] pub fn init_root(tmp_dir: &'static str, test_dir: PathBuf) -> TestIdGuard { static NEXT_ID: AtomicUsize = AtomicUsize::new(0); let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); TEST_ID.with(|n| *n.borrow_mut() = Some(id)); if cfg!(windows) { // Due to path-length limits, Windows doesn't use the full test name. TEST_DIR.with(|n| *n.borrow_mut() = Some(PathBuf::from(format!("t{id}")))); } else { TEST_DIR.with(|n| *n.borrow_mut() = Some(test_dir)); } let guard = TestIdGuard { _private: () }; set_global_root(tmp_dir); let r = root(); r.rm_rf(); r.mkdir_p(); #[cfg(not(windows))] if id == 0 { // Create a symlink from `t0` to the first test to make it easier to // find and reuse when running a single test. use crate::SymlinkBuilder; let mut alias = global_root(); alias.push("t0"); alias.rm_rf(); SymlinkBuilder::new_dir(r, alias).mk(); } guard } impl Drop for TestIdGuard { fn drop(&mut self) { TEST_ID.with(|n| *n.borrow_mut() = None); TEST_DIR.with(|n| *n.borrow_mut() = None); } } /// Path to the test's filesystem scratchpad /// /// ex: `$CARGO_TARGET_TMPDIR/cit////` /// or `$CARGO_TARGET_TMPDIR/cit/t0` on Windows pub fn root() -> PathBuf { let test_dir = TEST_DIR.with(|n| { n.borrow().clone().expect( "Tests must use the `#[cargo_test]` attribute in \ order to be able to use the crate root.", ) }); let mut root = global_root(); root.push(&test_dir); root } /// Path to the current test's `$HOME` /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home` pub fn home() -> PathBuf { let mut path = root(); path.push("home"); path.mkdir_p(); path } /// Path to the current test's `$CARGO_HOME` /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home/.cargo` pub fn cargo_home() -> PathBuf { home().join(".cargo") } /// Path to the current test's `$CARGO_LOG` /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home/.cargo/log` pub fn log_dir() -> PathBuf { cargo_home().join("log") } /// Path to the current test's `$CARGO_LOG` file /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home/.cargo/log/.jsonl` /// /// This also asserts the number of log files is exactly the same as `idx + 1`. pub fn log_file(idx: usize) -> PathBuf { let log_dir = log_dir(); let entries = std::fs::read_dir(&log_dir).unwrap(); let mut log_files: Vec<_> = entries .filter_map(Result::ok) .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("jsonl")) .collect(); // Sort them to get chronological order log_files.sort_unstable_by(|a, b| a.file_name().to_str().cmp(&b.file_name().to_str())); assert_eq!( idx + 1, log_files.len(), "unexpected number of log files: {}, expected {}", log_files.len(), idx + 1 ); log_files[idx].path() } /// Common path and file operations pub trait CargoPathExt { fn to_url(&self) -> url::Url; fn rm_rf(&self); fn mkdir_p(&self); /// Returns a list of all files and directories underneath the given /// directory, recursively, including the starting path. fn ls_r(&self) -> Vec; fn move_into_the_past(&self) { self.move_in_time(|sec, nsec| (sec - 3600, nsec)) } fn move_into_the_future(&self) { self.move_in_time(|sec, nsec| (sec + 3600, nsec)) } fn move_in_time(&self, travel_amount: F) where F: Fn(i64, u32) -> (i64, u32); fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData); fn assert_dir_layout(&self, expected: impl snapbox::IntoData, ignored_path_patterns: &[String]); } impl CargoPathExt for Path { fn to_url(&self) -> url::Url { url::Url::from_file_path(self).ok().unwrap() } fn rm_rf(&self) { let meta = match self.symlink_metadata() { Ok(meta) => meta, Err(e) => { if e.kind() == ErrorKind::NotFound { return; } panic!("failed to remove {:?}, could not read: {:?}", self, e); } }; // There is a race condition between fetching the metadata and // actually performing the removal, but we don't care all that much // for our tests. if meta.is_dir() { if let Err(e) = fs::remove_dir_all(self) { panic!("failed to remove {:?}: {:?}", self, e) } } else if let Err(e) = fs::remove_file(self) { panic!("failed to remove {:?}: {:?}", self, e) } } fn mkdir_p(&self) { fs::create_dir_all(self) .unwrap_or_else(|e| panic!("failed to mkdir_p {}: {}", self.display(), e)) } fn ls_r(&self) -> Vec { walkdir::WalkDir::new(self) .sort_by_file_name() .into_iter() .filter_map(|e| e.map(|e| e.path().to_owned()).ok()) .collect() } fn move_in_time(&self, travel_amount: F) where F: Fn(i64, u32) -> (i64, u32), { if self.is_file() { time_travel(self, &travel_amount); } else { recurse(self, &self.join("target"), &travel_amount); } fn recurse(p: &Path, bad: &Path, travel_amount: &F) where F: Fn(i64, u32) -> (i64, u32), { if p.is_file() { time_travel(p, travel_amount) } else if !p.starts_with(bad) { for f in t!(fs::read_dir(p)) { let f = t!(f).path(); recurse(&f, bad, travel_amount); } } } fn time_travel(path: &Path, travel_amount: &F) where F: Fn(i64, u32) -> (i64, u32), { let stat = t!(path.symlink_metadata()); let mtime = FileTime::from_last_modification_time(&stat); let (sec, nsec) = travel_amount(mtime.unix_seconds(), mtime.nanoseconds()); let newtime = FileTime::from_unix_time(sec, nsec); // Sadly change_file_times has a failure mode where a readonly file // cannot have its times changed on windows. do_op(path, "set file times", |path| { filetime::set_file_times(path, newtime, newtime) }); } } #[track_caller] fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData) { // We call `unordered()` here to because the build-dir has some scenarios that make // consistent ordering not possible. // Notably: // 1. Binaries with `.exe` on Windows causing the ordering to change with the dep-info `.d` // file. // 2. Directories with hashes are often reordered differently by platform. self.assert_dir_layout(expected.unordered(), &build_dir_ignored_path_patterns()); } #[track_caller] fn assert_dir_layout( &self, expected: impl snapbox::IntoData, ignored_path_patterns: &[String], ) { let assert = assert_e2e(); let actual = WalkDir::new(self) .sort_by_file_name() .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.file_type().is_file()) .map(|e| e.path().to_string_lossy().into_owned()) .filter(|file| { for ignored in ignored_path_patterns { if match_contains(&ignored, file, &assert.redactions()).is_ok() { return false; } } return true; }) .join("\n"); assert.eq(format!("{actual}\n"), expected); } } impl CargoPathExt for PathBuf { fn to_url(&self) -> url::Url { self.as_path().to_url() } fn rm_rf(&self) { self.as_path().rm_rf() } fn mkdir_p(&self) { self.as_path().mkdir_p() } fn ls_r(&self) -> Vec { self.as_path().ls_r() } fn move_in_time(&self, travel_amount: F) where F: Fn(i64, u32) -> (i64, u32), { self.as_path().move_in_time(travel_amount) } #[track_caller] fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData) { self.as_path().assert_build_dir_layout(expected); } #[track_caller] fn assert_dir_layout( &self, expected: impl snapbox::IntoData, ignored_path_patterns: &[String], ) { self.as_path() .assert_dir_layout(expected, ignored_path_patterns); } } fn do_op(path: &Path, desc: &str, mut f: F) where F: FnMut(&Path) -> io::Result<()>, { match f(path) { Ok(()) => {} Err(ref e) if e.kind() == ErrorKind::PermissionDenied => { let mut p = t!(path.metadata()).permissions(); p.set_readonly(false); t!(fs::set_permissions(path, p)); // Unix also requires the parent to not be readonly for example when // removing files let parent = path.parent().unwrap(); let mut p = t!(parent.metadata()).permissions(); p.set_readonly(false); t!(fs::set_permissions(parent, p)); f(path).unwrap_or_else(|e| { panic!("failed to {} {}: {}", desc, path.display(), e); }) } Err(e) => { panic!("failed to {} {}: {}", desc, path.display(), e); } } } /// The paths to ignore when [`CargoPathExt::assert_build_dir_layout`] is called fn build_dir_ignored_path_patterns() -> Vec { vec![ // Ignore MacOS debug symbols as there are many files/directories that would clutter up // tests few not a lot of benefit. "[..].dSYM/[..]", // Ignore Windows debug symbols files (.pdb) "[..].pdb", ] .into_iter() .map(ToString::to_string) .collect() } /// Get the filename for a library. /// /// `kind` should be one of: /// - `lib` /// - `rlib` /// - `staticlib` /// - `dylib` /// - `proc-macro` /// /// # Examples /// ``` /// # use cargo_test_support::paths::get_lib_filename; /// get_lib_filename("foo", "dylib"); /// ``` /// would return: /// - macOS: `"libfoo.dylib"` /// - Windows: `"foo.dll"` /// - Unix: `"libfoo.so"` pub fn get_lib_filename(name: &str, kind: &str) -> String { let prefix = get_lib_prefix(kind); let extension = get_lib_extension(kind); format!("{}{}.{}", prefix, name, extension) } /// See [`get_lib_filename`] for more details pub fn get_lib_prefix(kind: &str) -> &str { match kind { "lib" | "rlib" => "lib", "staticlib" | "dylib" | "proc-macro" => { if cfg!(windows) { "" } else { "lib" } } _ => unreachable!(), } } /// See [`get_lib_filename`] for more details pub fn get_lib_extension(kind: &str) -> &str { match kind { "lib" | "rlib" => "rlib", "staticlib" => { if cfg!(windows) { "lib" } else { "a" } } "dylib" | "proc-macro" => { if cfg!(windows) { "dll" } else if cfg!(target_os = "macos") { "dylib" } else { "so" } } _ => unreachable!(), } } /// Path to `rustc`s sysroot pub fn sysroot() -> String { let output = Command::new("rustc") .arg("--print=sysroot") .output() .expect("rustc to run"); assert!(output.status.success()); let sysroot = String::from_utf8(output.stdout).unwrap(); sysroot.trim().to_string() } /// Returns true if names such as aux.* are allowed. /// /// Traditionally, Windows did not allow a set of file names (see `is_windows_reserved` /// for a list). More recent versions of Windows have relaxed this restriction. This test /// determines whether we are running in a mode that allows Windows reserved names. #[cfg(windows)] pub fn windows_reserved_names_are_allowed() -> bool { use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::ptr; use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW; let test_file_name: Vec<_> = OsStr::new("aux.rs").encode_wide().chain([0]).collect(); let buffer_length = unsafe { GetFullPathNameW(test_file_name.as_ptr(), 0, ptr::null_mut(), ptr::null_mut()) }; if buffer_length == 0 { // This means the call failed, so we'll conservatively assume reserved names are not allowed. return false; } let mut buffer = vec![0u16; buffer_length as usize]; let result = unsafe { GetFullPathNameW( test_file_name.as_ptr(), buffer_length, buffer.as_mut_ptr(), ptr::null_mut(), ) }; if result == 0 { // Once again, conservatively assume reserved names are not allowed if the // GetFullPathNameW call failed. return false; } // Under the old rules, a file name like aux.rs would get converted into \\.\aux, so // we detect this case by checking if the string starts with \\.\ // // Otherwise, the filename will be something like C:\Users\Foo\Documents\aux.rs let prefix: Vec<_> = OsStr::new("\\\\.\\").encode_wide().collect(); if buffer.starts_with(&prefix) { false } else { true } } /// This takes the test location (std::file!() should be passed) and the test name /// and outputs the location the test should be places in, inside of `target/tmp/cit` /// /// `path: tests/testsuite/workspaces.rs` /// `name: `workspace_in_git /// `output: "testsuite/workspaces/workspace_in_git` pub fn test_dir(path: &str, name: &str) -> std::path::PathBuf { let test_dir: std::path::PathBuf = std::path::PathBuf::from(path) .components() // Trim .rs from any files .map(|c| c.as_os_str().to_str().unwrap().trim_end_matches(".rs")) // We only want to take once we have reached `tests` or `src`. This helps when in a // workspace: `workspace/more/src/...` would result in `src/...` .skip_while(|c| c != &"tests" && c != &"src") // We want to skip "tests" since it is taken in `skip_while`. // "src" is fine since you could have test in "src" named the same as one in "tests" // Skip "mod" since `snapbox` tests have a folder per test not a file and the files // are named "mod.rs" .filter(|c| c != &"tests" && c != &"mod") .collect(); test_dir.join(name) } ================================================ FILE: crates/cargo-test-support/src/publish.rs ================================================ //! Helpers for testing `cargo package` / `cargo publish` //! //! # Example //! //! ```no_run //! # use cargo_test_support::registry::RegistryBuilder; //! # use cargo_test_support::publish::validate_upload; //! # use cargo_test_support::project; //! validate_upload( //! r#" //! { //! "authors": [], //! "badges": {}, //! "categories": [], //! "deps": [], //! "description": "foo", //! "documentation": null, //! "features": {}, //! "homepage": null, //! "keywords": [], //! "license": "MIT", //! "license_file": null, //! "links": null, //! "name": "foo", //! "readme": null, //! "readme_file": null, //! "repository": null, //! "rust_version": null, //! "vers": "0.0.1" //! } //! "#, //! "foo-0.0.1.crate", //! &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], //! ); //! ``` use crate::compare::InMemoryDir; use crate::registry::{self, FeatureMap, alt_api_path}; use flate2::read::GzDecoder; use snapbox::prelude::*; use std::collections::HashSet; use std::fs; use std::fs::File; use std::io::{self, SeekFrom, prelude::*}; use std::path::Path; use tar::Archive; fn read_le_u32(mut reader: R) -> io::Result where R: Read, { let mut buf = [0; 4]; reader.read_exact(&mut buf)?; Ok(u32::from_le_bytes(buf)) } /// Check the `cargo publish` API call #[track_caller] pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_files: &[&str]) { let new_path = registry::api_path().join("api/v1/crates/new"); _validate_upload( &new_path, expected_json, expected_crate_name, expected_files, (), ); } /// Check the `cargo publish` API call, with file contents #[track_caller] pub fn validate_upload_with_contents( expected_json: &str, expected_crate_name: &str, expected_files: &[&str], expected_contents: impl Into, ) { let new_path = registry::api_path().join("api/v1/crates/new"); _validate_upload( &new_path, expected_json, expected_crate_name, expected_files, expected_contents, ); } /// Check the `cargo publish` API call to the alternative test registry #[track_caller] pub fn validate_alt_upload( expected_json: &str, expected_crate_name: &str, expected_files: &[&str], ) { let new_path = alt_api_path().join("api/v1/crates/new"); _validate_upload( &new_path, expected_json, expected_crate_name, expected_files, (), ); } #[track_caller] fn _validate_upload( new_path: &Path, expected_json: &str, expected_crate_name: &str, expected_files: &[&str], expected_contents: impl Into, ) { let (actual_json, krate_bytes) = read_new_post(new_path); snapbox::assert_data_eq!(actual_json, expected_json.is_json()); // Verify the tarball. validate_crate_contents( &krate_bytes[..], expected_crate_name, expected_files, expected_contents, ); } #[track_caller] fn read_new_post(new_path: &Path) -> (Vec, Vec) { let mut f = File::open(new_path).unwrap(); // 32-bit little-endian integer of length of JSON data. let json_sz = read_le_u32(&mut f).expect("read json length"); let mut json_bytes = vec![0; json_sz as usize]; f.read_exact(&mut json_bytes).expect("read JSON data"); // 32-bit little-endian integer of length of crate file. let crate_sz = read_le_u32(&mut f).expect("read crate length"); let mut krate_bytes = vec![0; crate_sz as usize]; f.read_exact(&mut krate_bytes).expect("read crate data"); // Check at end. let current = f.seek(SeekFrom::Current(0)).unwrap(); assert_eq!(f.seek(SeekFrom::End(0)).unwrap(), current); (json_bytes, krate_bytes) } /// Checks the contents of a `.crate` file. /// /// - `expected_crate_name` should be something like `foo-0.0.1.crate`. /// - `expected_files` should be a complete list of files in the crate /// (relative to `expected_crate_name`). /// - `expected_contents` should be a list of `(file_name, contents)` tuples /// to validate the contents of the given file. Only the listed files will /// be checked (others will be ignored). #[track_caller] pub fn validate_crate_contents( reader: impl Read, expected_crate_name: &str, expected_files: &[&str], expected_contents: impl Into, ) { let expected_contents = expected_contents.into(); validate_crate_contents_( reader, expected_crate_name, expected_files, expected_contents, ) } #[track_caller] fn validate_crate_contents_( reader: impl Read, expected_crate_name: &str, expected_files: &[&str], expected_contents: InMemoryDir, ) { let mut rdr = GzDecoder::new(reader); snapbox::assert_data_eq!(rdr.header().unwrap().filename().unwrap(), { let expected: snapbox::Data = expected_crate_name.into(); expected.raw() }); let mut contents = Vec::new(); rdr.read_to_end(&mut contents).unwrap(); let mut ar = Archive::new(&contents[..]); let base_crate_name = Path::new( expected_crate_name .strip_suffix(".crate") .expect("must end with .crate"), ); let actual_contents: InMemoryDir = ar .entries() .unwrap() .map(|entry| { let mut entry = entry.unwrap(); let name = entry .path() .unwrap() .strip_prefix(base_crate_name) .unwrap() .to_owned(); let mut contents = String::new(); entry.read_to_string(&mut contents).unwrap(); (name, contents) }) .collect(); let actual_files: HashSet<&Path> = actual_contents.paths().collect(); let expected_files: HashSet<&Path> = expected_files.iter().map(|name| Path::new(name)).collect(); let missing: Vec<&&Path> = expected_files.difference(&actual_files).collect(); let extra: Vec<&&Path> = actual_files.difference(&expected_files).collect(); if !missing.is_empty() || !extra.is_empty() { panic!( "uploaded archive does not match.\nMissing: {:?}\nExtra: {:?}\n", missing, extra ); } actual_contents.assert_contains(&expected_contents); } pub(crate) fn create_index_line( name: serde_json::Value, vers: &str, deps: Vec, cksum: &str, features: crate::registry::FeatureMap, yanked: bool, links: Option, rust_version: Option<&str>, pubtime: Option<&str>, v: Option, ) -> String { // This emulates what crates.io does to retain backwards compatibility. let (features, features2) = split_index_features(features.clone()); let mut json = serde_json::json!({ "name": name, "vers": vers, "deps": deps, "cksum": cksum, "features": features, "yanked": yanked, "links": links, }); if let Some(f2) = &features2 { json["features2"] = serde_json::json!(f2); json["v"] = serde_json::json!(2); } if let Some(v) = v { json["v"] = serde_json::json!(v); } if let Some(rust_version) = rust_version { json["rust_version"] = serde_json::json!(rust_version); } if let Some(pubtime) = pubtime { json["pubtime"] = serde_json::json!(pubtime); } json.to_string() } pub(crate) fn write_to_index(registry_path: &Path, name: &str, line: String, local: bool) { let file = cargo_util::registry::make_dep_path(name, false); // Write file/line in the index. let dst = if local { registry_path.join("index").join(&file) } else { registry_path.join(&file) }; let prev = fs::read_to_string(&dst).unwrap_or_default(); t!(fs::create_dir_all(dst.parent().unwrap())); t!(fs::write(&dst, prev + &line[..] + "\n")); // Add the new file to the index. if !local { let repo = t!(git2::Repository::open(®istry_path)); let mut index = t!(repo.index()); t!(index.add_path(Path::new(&file))); t!(index.write()); let id = t!(index.write_tree()); // Commit this change. let tree = t!(repo.find_tree(id)); let sig = t!(repo.signature()); let parent = t!(repo.refname_to_id("refs/heads/master")); let parent = t!(repo.find_commit(parent)); t!(repo.commit( Some("HEAD"), &sig, &sig, "Another commit", &tree, &[&parent] )); } } fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option) { let mut features2 = FeatureMap::new(); for (feat, values) in features.iter_mut() { if values .iter() .any(|value| value.starts_with("dep:") || value.contains("?/")) { let new_values = std::mem::take(values); features2.insert(feat.clone(), new_values); } } if features2.is_empty() { (features, None) } else { (features, Some(features2)) } } ================================================ FILE: crates/cargo-test-support/src/registry.rs ================================================ //! Interact with the [`TestRegistry`] //! //! # Example //! //! ```no_run //! use cargo_test_support::registry::Package; //! use cargo_test_support::project; //! use cargo_test_support::str; //! //! // Publish package "a" depending on "b". //! Package::new("a", "1.0.0") //! .dep("b", "1.0.0") //! .file("src/lib.rs", r#" //! extern crate b; //! pub fn f() -> i32 { b::f() * 2 } //! "#) //! .publish(); //! //! // Publish package "b". //! Package::new("b", "1.0.0") //! .file("src/lib.rs", r#" //! pub fn f() -> i32 { 12 } //! "#) //! .publish(); //! //! // Create a project that uses package "a". //! let p = project() //! .file("Cargo.toml", r#" //! [package] //! name = "foo" //! version = "0.0.1" //! //! [dependencies] //! a = "1.0" //! "#) //! .file("src/main.rs", r#" //! extern crate a; //! fn main() { println!("{}", a::f()); } //! "#) //! .build(); //! //! // p.cargo("run").with_stdout_data(str!["24"]).run(); //! ``` use crate::git::repo; use crate::paths; use crate::publish::{create_index_line, write_to_index}; use cargo_util::Sha256; use cargo_util::paths::append; use flate2::Compression; use flate2::write::GzEncoder; use pasetors::keys::{AsymmetricPublicKey, AsymmetricSecretKey}; use pasetors::paserk::FormatAsPaserk; use pasetors::token::UntrustedToken; use std::collections::{BTreeMap, HashMap}; use std::fmt; use std::fs::{self, File}; use std::io::{BufRead, BufReader, Read, Write}; use std::net::{SocketAddr, TcpListener, TcpStream}; use std::path::{Path, PathBuf}; use std::thread::{self, JoinHandle}; use tar::{Builder, Header}; use time::format_description::well_known::Rfc3339; use time::{Duration, OffsetDateTime}; use url::Url; /// Path to the local index for pseudo-crates.io. /// /// This is a Git repo /// initialized with a `config.json` file pointing to `dl_path` for downloads /// and `api_path` for uploads. /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/registry` pub fn registry_path() -> PathBuf { generate_path("registry") } /// Path to the local web API uploads /// /// Cargo will place the contents of a web API /// request here. For example, `api/v1/crates/new` is the result of publishing a crate. /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/api` pub fn api_path() -> PathBuf { generate_path("api") } /// Path to download `.crate` files using the web API endpoint. /// /// Crates /// should be organized as `{name}/{version}/download` to match the web API /// endpoint. This is rarely used and must be manually set up. /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/dl` pub fn dl_path() -> PathBuf { generate_path("dl") } /// Path to the alternative-registry version of [`registry_path`] /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-registry` pub fn alt_registry_path() -> PathBuf { generate_path("alternative-registry") } /// URL to the alternative-registry version of `registry_url` fn alt_registry_url() -> Url { generate_url("alternative-registry") } /// Path to the alternative-registry version of [`dl_path`] /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-dl` pub fn alt_dl_path() -> PathBuf { generate_path("alternative-dl") } /// Path to the alternative-registry version of [`api_path`] /// /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-api` pub fn alt_api_path() -> PathBuf { generate_path("alternative-api") } fn generate_path(name: &str) -> PathBuf { paths::root().join(name) } fn generate_url(name: &str) -> Url { Url::from_file_path(generate_path(name)).ok().unwrap() } /// Auth-token for publishing, see [`RegistryBuilder::token`] #[derive(Clone)] pub enum Token { Plaintext(String), Keys(String, Option), } impl Token { /// This is a valid PASETO secret key. /// /// This one is already publicly available as part of the text of the RFC so is safe to use for tests. pub fn rfc_key() -> Token { Token::Keys( "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36" .to_string(), Some("sub".to_string()), ) } } type RequestCallback = Box Response>; /// Prepare a local [`TestRegistry`] fixture /// /// See also [`init`] and [`alt_init`] pub struct RegistryBuilder { /// If set, configures an alternate registry with the given name. alternative: Option, /// The authorization token for the registry. token: Option, /// If set, the registry requires authorization for all operations. auth_required: bool, /// If set, serves the index over http. http_index: bool, /// If set, serves the API over http. http_api: bool, /// If set, config.json includes 'api' api: bool, /// Write the token in the configuration. configure_token: bool, /// Write the registry in configuration. configure_registry: bool, /// API responders. custom_responders: HashMap, /// Handler for 404 responses. not_found_handler: RequestCallback, /// If nonzero, the git index update to be delayed by the given number of seconds. delayed_index_update: usize, /// Credential provider in configuration credential_provider: Option, } /// A local registry fixture /// /// Most tests won't need to call this directly but instead interact with [`Package`] pub struct TestRegistry { server: Option, index_url: Url, path: PathBuf, api_url: Url, dl_url: Url, token: Token, } impl TestRegistry { pub fn index_url(&self) -> &Url { &self.index_url } pub fn api_url(&self) -> &Url { &self.api_url } pub fn token(&self) -> &str { match &self.token { Token::Plaintext(s) => s, Token::Keys(_, _) => panic!("registry was not configured with a plaintext token"), } } pub fn key(&self) -> &str { match &self.token { Token::Plaintext(_) => panic!("registry was not configured with a secret key"), Token::Keys(s, _) => s, } } /// Shutdown the server thread and wait for it to stop. /// `Drop` automatically stops the server, but this additionally /// waits for the thread to stop. pub fn join(self) { if let Some(mut server) = self.server { server.stop(); let handle = server.handle.take().unwrap(); handle.join().unwrap(); } } } impl RegistryBuilder { #[must_use] pub fn new() -> RegistryBuilder { let not_found = |_req: &Request, _server: &HttpServer| -> Response { Response { code: 404, headers: vec![], body: b"not found".to_vec(), } }; RegistryBuilder { alternative: None, token: None, auth_required: false, http_api: false, http_index: false, api: true, configure_registry: true, configure_token: true, custom_responders: HashMap::new(), not_found_handler: Box::new(not_found), delayed_index_update: 0, credential_provider: None, } } /// Adds a custom HTTP response for a specific url #[must_use] pub fn add_responder Response>( mut self, url: impl Into, responder: R, ) -> Self { self.custom_responders .insert(url.into(), Box::new(responder)); self } #[must_use] pub fn not_found_handler Response>( mut self, responder: R, ) -> Self { self.not_found_handler = Box::new(responder); self } /// Configures the git index update to be delayed by the given number of seconds. #[must_use] pub fn delayed_index_update(mut self, delay: usize) -> Self { self.delayed_index_update = delay; self } /// Initializes as an alternative registry with the given name. #[must_use] pub fn alternative_named(mut self, alt: &str) -> Self { self.alternative = Some(alt.to_string()); self } /// Initializes as an alternative registry named "alternative". #[must_use] pub fn alternative(self) -> Self { self.alternative_named("alternative") } /// Prevents placing a token in the configuration #[must_use] pub fn no_configure_token(mut self) -> Self { self.configure_token = false; self } /// Prevents adding the registry to the configuration. #[must_use] pub fn no_configure_registry(mut self) -> Self { self.configure_registry = false; self } /// Sets the token value #[must_use] pub fn token(mut self, token: Token) -> Self { self.token = Some(token); self } /// Sets this registry to require the authentication token for /// all operations. #[must_use] pub fn auth_required(mut self) -> Self { self.auth_required = true; self } /// Operate the index over http #[must_use] pub fn http_index(mut self) -> Self { self.http_index = true; self } /// Operate the api over http #[must_use] pub fn http_api(mut self) -> Self { self.http_api = true; self } /// The registry has no api. #[must_use] pub fn no_api(mut self) -> Self { self.api = false; self } /// The credential provider to configure for this registry. #[must_use] pub fn credential_provider(mut self, provider: &[&str]) -> Self { self.credential_provider = Some(format!("['{}']", provider.join("','"))); self } /// Initializes the registry. #[must_use] pub fn build(self) -> TestRegistry { let config_path = paths::cargo_home().join("config.toml"); t!(fs::create_dir_all(config_path.parent().unwrap())); let prefix = if let Some(alternative) = &self.alternative { format!("{alternative}-") } else { String::new() }; let registry_path = generate_path(&format!("{prefix}registry")); let index_url = generate_url(&format!("{prefix}registry")); let api_url = generate_url(&format!("{prefix}api")); let dl_url = generate_url(&format!("{prefix}dl")); let dl_path = generate_path(&format!("{prefix}dl")); let api_path = generate_path(&format!("{prefix}api")); let token = self .token .unwrap_or_else(|| Token::Plaintext(format!("{prefix}sekrit"))); let (server, index_url, api_url, dl_url) = if !self.http_index && !self.http_api { // No need to start the HTTP server. (None, index_url, api_url, dl_url) } else { let server = HttpServer::new( registry_path.clone(), dl_path, api_path.clone(), token.clone(), self.auth_required, self.custom_responders, self.not_found_handler, self.delayed_index_update, ); let index_url = if self.http_index { server.index_url() } else { index_url }; let api_url = if self.http_api { server.api_url() } else { api_url }; let dl_url = server.dl_url(); (Some(server), index_url, api_url, dl_url) }; let registry = TestRegistry { api_url, index_url, server, dl_url, path: registry_path, token, }; if self.configure_registry { if let Some(alternative) = &self.alternative { append( &config_path, format!( " [registries.{alternative}] index = '{}'", registry.index_url ) .as_bytes(), ) .unwrap(); if let Some(p) = &self.credential_provider { append( &config_path, &format!( " credential-provider = {p} " ) .as_bytes(), ) .unwrap() } } else { append( &config_path, format!( " [source.crates-io] replace-with = 'dummy-registry' [registries.dummy-registry] index = '{}'", registry.index_url ) .as_bytes(), ) .unwrap(); if let Some(p) = &self.credential_provider { append( &config_path, &format!( " [registry] credential-provider = {p} " ) .as_bytes(), ) .unwrap() } } } if self.configure_token { let credentials = paths::cargo_home().join("credentials.toml"); match ®istry.token { Token::Plaintext(token) => { if let Some(alternative) = &self.alternative { append( &credentials, format!( r#" [registries.{alternative}] token = "{token}" "# ) .as_bytes(), ) .unwrap(); } else { append( &credentials, format!( r#" [registry] token = "{token}" "# ) .as_bytes(), ) .unwrap(); } } Token::Keys(key, subject) => { let mut out = if let Some(alternative) = &self.alternative { format!("\n[registries.{alternative}]\n") } else { format!("\n[registry]\n") }; out += &format!("secret-key = \"{key}\"\n"); if let Some(subject) = subject { out += &format!("secret-key-subject = \"{subject}\"\n"); } append(&credentials, out.as_bytes()).unwrap(); } } } let auth = if self.auth_required { r#","auth-required":true"# } else { "" }; let api = if self.api { format!(r#","api":"{}""#, registry.api_url) } else { String::new() }; // Initialize a new registry. repo(®istry.path) .file( "config.json", &format!(r#"{{"dl":"{}"{api}{auth}}}"#, registry.dl_url), ) .build(); fs::create_dir_all(api_path.join("api/v1/crates")).unwrap(); registry } } /// Published package builder for [`TestRegistry`] /// /// This uses "source replacement" using an automatically generated /// `.cargo/config` file to ensure that dependencies will use these packages /// instead of contacting crates.io. See `source-replacement.md` for more /// details on how source replacement works. /// /// Call [`Package::publish`] to finalize and create the package. /// /// If no files are specified, an empty `lib.rs` file is automatically created. /// /// The `Cargo.toml` file is automatically generated based on the methods /// called on `Package` (for example, calling [`Package::dep()`] will add to the /// `[dependencies]` automatically). You may also specify a `Cargo.toml` file /// to override the generated one. /// /// This supports different registry types: /// - Regular source replacement that replaces `crates.io` (the default). /// - A "local registry" which is a subset for vendoring (see /// [`Package::local`]). /// - An "alternative registry" which requires specifying the registry name /// (see [`Package::alternative`]). /// /// This does not support "directory sources". See `directory.rs` for /// `VendorPackage` which implements directory sources. #[must_use] pub struct Package { name: String, vers: String, deps: Vec, files: Vec, yanked: bool, features: FeatureMap, local: bool, alternative: bool, invalid_index_line: bool, index_line: Option, edition: Option, resolver: Option, proc_macro: bool, links: Option, rust_version: Option, cargo_features: Vec, pubtime: Option, v: Option, } pub(crate) type FeatureMap = BTreeMap>; /// Published package dependency builder, see [`Package::add_dep`] #[derive(Clone)] pub struct Dependency { name: String, vers: String, kind: String, artifact: Option, bindep_target: Option, lib: bool, target: Option, features: Vec, registry: Option, package: Option, optional: bool, default_features: bool, public: bool, } /// Entry with data that corresponds to [`tar::EntryType`]. #[non_exhaustive] enum EntryData { Regular(String), Symlink(PathBuf), Directory, } /// A file to be created in a package. struct PackageFile { path: String, contents: EntryData, /// The Unix mode for the file. Note that when extracted on Windows, this /// is mostly ignored since it doesn't have the same style of permissions. mode: u32, /// If `true`, the file is created in the root of the tarfile, used for /// testing invalid packages. extra: bool, } const DEFAULT_MODE: u32 = 0o644; /// Setup a local pseudo-crates.io [`TestRegistry`] /// /// This is implicitly called by [`Package::new`]. /// /// When calling `cargo publish`, see instead [`crate::publish`]. pub fn init() -> TestRegistry { RegistryBuilder::new().build() } /// Setup a local "alternative" [`TestRegistry`] /// /// When calling `cargo publish`, see instead [`crate::publish`]. pub fn alt_init() -> TestRegistry { init(); RegistryBuilder::new().alternative().build() } pub struct HttpServerHandle { addr: SocketAddr, handle: Option>, } impl HttpServerHandle { pub fn index_url(&self) -> Url { Url::parse(&format!("sparse+http://{}/index/", self.addr)).unwrap() } pub fn api_url(&self) -> Url { Url::parse(&format!("http://{}/", self.addr)).unwrap() } pub fn dl_url(&self) -> Url { Url::parse(&format!("http://{}/dl", self.addr)).unwrap() } fn stop(&self) { if let Ok(mut stream) = TcpStream::connect(self.addr) { // shutdown the server let _ = stream.write_all(b"stop"); let _ = stream.flush(); } } } impl Drop for HttpServerHandle { fn drop(&mut self) { self.stop(); } } /// Request to the test http server #[derive(Clone)] pub struct Request { pub url: Url, pub method: String, pub body: Option>, pub authorization: Option, pub if_modified_since: Option, pub if_none_match: Option, } impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // body is not included as it can produce long debug outputs f.debug_struct("Request") .field("url", &self.url) .field("method", &self.method) .field("authorization", &self.authorization) .field("if_modified_since", &self.if_modified_since) .field("if_none_match", &self.if_none_match) .finish() } } /// Response from the test http server pub struct Response { pub code: u32, pub headers: Vec, pub body: Vec, } pub struct HttpServer { listener: TcpListener, registry_path: PathBuf, dl_path: PathBuf, api_path: PathBuf, addr: SocketAddr, token: Token, auth_required: bool, custom_responders: HashMap, not_found_handler: RequestCallback, delayed_index_update: usize, } /// A helper struct that collects the arguments for [`HttpServer::check_authorized`]. /// Based on looking at the request, these are the fields that the authentication header should attest to. struct Mutation<'a> { mutation: &'a str, name: Option<&'a str>, vers: Option<&'a str>, cksum: Option<&'a str>, } impl HttpServer { pub fn new( registry_path: PathBuf, dl_path: PathBuf, api_path: PathBuf, token: Token, auth_required: bool, custom_responders: HashMap, not_found_handler: RequestCallback, delayed_index_update: usize, ) -> HttpServerHandle { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); let server = HttpServer { listener, registry_path, dl_path, api_path, addr, token, auth_required, custom_responders, not_found_handler, delayed_index_update, }; let handle = Some(thread::spawn(move || server.start())); HttpServerHandle { addr, handle } } fn start(&self) { let mut line = String::new(); 'server: loop { let (socket, _) = self.listener.accept().unwrap(); let mut buf = BufReader::new(socket); line.clear(); if buf.read_line(&mut line).unwrap() == 0 { // Connection terminated. continue; } // Read the "GET path HTTP/1.1" line. let mut parts = line.split_ascii_whitespace(); let method = parts.next().unwrap().to_ascii_lowercase(); if method == "stop" { // Shutdown the server. return; } let addr = self.listener.local_addr().unwrap(); let url = format!( "http://{}/{}", addr, parts.next().unwrap().trim_start_matches('/') ); let url = Url::parse(&url).unwrap(); // Grab headers we care about. let mut if_modified_since = None; let mut if_none_match = None; let mut authorization = None; let mut content_len = None; loop { line.clear(); if buf.read_line(&mut line).unwrap() == 0 { continue 'server; } if line == "\r\n" { // End of headers. line.clear(); break; } let (name, value) = line.split_once(':').unwrap(); let name = name.trim().to_ascii_lowercase(); let value = value.trim().to_string(); match name.as_str() { "if-modified-since" => if_modified_since = Some(value), "if-none-match" => if_none_match = Some(value), "authorization" => authorization = Some(value), "content-length" => content_len = Some(value), _ => {} } } let mut body = None; if let Some(con_len) = content_len { let len = con_len.parse::().unwrap(); let mut content = vec![0u8; len as usize]; buf.read_exact(&mut content).unwrap(); body = Some(content) } let req = Request { authorization, if_modified_since, if_none_match, method, url, body, }; let response = self.route(&req); let buf = buf.get_mut(); write!(buf, "HTTP/1.1 {}\r\n", response.code).unwrap(); write!(buf, "Content-Length: {}\r\n", response.body.len()).unwrap(); write!(buf, "Connection: close\r\n").unwrap(); for header in response.headers { write!(buf, "{}\r\n", header).unwrap(); } write!(buf, "\r\n").unwrap(); buf.write_all(&response.body).unwrap(); buf.flush().unwrap(); } } fn check_authorized(&self, req: &Request, mutation: Option>) -> bool { let (private_key, private_key_subject) = if mutation.is_some() || self.auth_required { match &self.token { Token::Plaintext(token) => return Some(token) == req.authorization.as_ref(), Token::Keys(private_key, private_key_subject) => { (private_key.as_str(), private_key_subject) } } } else { assert!(req.authorization.is_none(), "unexpected token"); return true; }; macro_rules! t { ($e:expr) => { match $e { Some(e) => e, None => return false, } }; } let secret: AsymmetricSecretKey = private_key.try_into().unwrap(); let public: AsymmetricPublicKey = (&secret).try_into().unwrap(); let pub_key_id: pasetors::paserk::Id = (&public).into(); let mut paserk_pub_key_id = String::new(); FormatAsPaserk::fmt(&pub_key_id, &mut paserk_pub_key_id).unwrap(); // https://github.com/rust-lang/rfcs/blob/master/text/3231-cargo-asymmetric-tokens.md#how-the-registry-server-will-validate-an-asymmetric-token // - The PASETO is in v3.public format. let authorization = t!(&req.authorization); let untrusted_token = t!( UntrustedToken::::try_from(authorization) .ok() ); // - The PASETO validates using the public key it looked up based on the key ID. #[derive(serde::Deserialize, Debug)] struct Footer<'a> { url: &'a str, kip: &'a str, } let footer: Footer<'_> = t!(serde_json::from_slice(untrusted_token.untrusted_footer()).ok()); if footer.kip != paserk_pub_key_id { return false; } let trusted_token = t!( pasetors::version3::PublicToken::verify(&public, &untrusted_token, None, None,) .ok() ); // - The URL matches the registry base URL if footer.url != "https://github.com/rust-lang/crates.io-index" && footer.url != &format!("sparse+http://{}/index/", self.addr) { return false; } // - The PASETO is still within its valid time period. #[derive(serde::Deserialize)] struct Message<'a> { iat: &'a str, sub: Option<&'a str>, mutation: Option<&'a str>, name: Option<&'a str>, vers: Option<&'a str>, cksum: Option<&'a str>, _challenge: Option<&'a str>, // todo: PASETO with challenges v: Option, } let message: Message<'_> = t!(serde_json::from_str(trusted_token.payload()).ok()); let token_time = t!(OffsetDateTime::parse(message.iat, &Rfc3339).ok()); let now = OffsetDateTime::now_utc(); if (now - token_time) > Duration::MINUTE { return false; } if private_key_subject.as_deref() != message.sub { return false; } // - If the claim v is set, that it has the value of 1. if let Some(v) = message.v { if v != 1 { return false; } } // - If the server issues challenges, that the challenge has not yet been answered. // todo: PASETO with challenges // - If the operation is a mutation: if let Some(mutation) = mutation { // - That the operation matches the mutation field and is one of publish, yank, or unyank. if message.mutation != Some(mutation.mutation) { return false; } // - That the package, and version match the request. if message.name != mutation.name { return false; } if message.vers != mutation.vers { return false; } // - If the mutation is publish, that the version has not already been published, and that the hash matches the request. if mutation.mutation == "publish" { if message.cksum != mutation.cksum { return false; } } } else { // - If the operation is a read, that the mutation field is not set. if message.mutation.is_some() || message.name.is_some() || message.vers.is_some() || message.cksum.is_some() { return false; } } true } /// Route the request fn route(&self, req: &Request) -> Response { // Check for custom responder if let Some(responder) = self.custom_responders.get(req.url.path()) { return responder(&req, self); } let path: Vec<_> = req.url.path()[1..].split('/').collect(); match (req.method.as_str(), path.as_slice()) { ("get", ["index", ..]) => { if !self.check_authorized(req, None) { self.unauthorized(req) } else { self.index(&req) } } ("get", ["dl", ..]) => { if !self.check_authorized(req, None) { self.unauthorized(req) } else { self.dl(&req) } } // publish ("put", ["api", "v1", "crates", "new"]) => self.check_authorized_publish(req), // The remainder of the operators in the test framework do nothing other than responding 'ok'. // // Note: We don't need to support anything real here because there are no tests that // currently require anything other than publishing via the http api. // yank / unyank ("delete" | "put", ["api", "v1", "crates", crate_name, version, mutation]) => { if !self.check_authorized( req, Some(Mutation { mutation, name: Some(crate_name), vers: Some(version), cksum: None, }), ) { self.unauthorized(req) } else { self.ok(&req) } } // owners ("get" | "put" | "delete", ["api", "v1", "crates", crate_name, "owners"]) => { if !self.check_authorized( req, Some(Mutation { mutation: "owners", name: Some(crate_name), vers: None, cksum: None, }), ) { self.unauthorized(req) } else { self.ok(&req) } } _ => self.not_found(&req), } } /// Unauthorized response pub fn unauthorized(&self, _req: &Request) -> Response { Response { code: 401, headers: vec![ r#"www-authenticate: Cargo login_url="https://test-registry-login/me""#.to_string(), ], body: b"Unauthorized message from server.".to_vec(), } } /// Not found response pub fn not_found(&self, req: &Request) -> Response { (self.not_found_handler)(req, self) } /// Respond OK without doing anything pub fn ok(&self, _req: &Request) -> Response { Response { code: 200, headers: vec![], body: br#"{"ok": true, "msg": "completed!"}"#.to_vec(), } } /// Return an internal server error (HTTP 500) pub fn internal_server_error(&self, _req: &Request) -> Response { Response { code: 500, headers: vec![], body: br#"internal server error"#.to_vec(), } } /// Return too many requests (HTTP 429) pub fn too_many_requests(&self, _req: &Request, delay: std::time::Duration) -> Response { Response { code: 429, headers: vec![format!("Retry-After: {}", delay.as_secs())], body: format!( "too many requests, try again in {} seconds", delay.as_secs() ) .into_bytes(), } } /// Serve the download endpoint pub fn dl(&self, req: &Request) -> Response { let file = self .dl_path .join(req.url.path().strip_prefix("/dl/").unwrap()); println!("{}", file.display()); if !file.exists() { return self.not_found(req); } return Response { body: fs::read(&file).unwrap(), code: 200, headers: vec![], }; } /// Serve the registry index pub fn index(&self, req: &Request) -> Response { let file = self .registry_path .join(req.url.path().strip_prefix("/index/").unwrap()); if !file.exists() { return self.not_found(req); } else { // Now grab info about the file. let data = fs::read(&file).unwrap(); let etag = Sha256::new().update(&data).finish_hex(); let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap()); // Start to construct our response: let mut any_match = false; let mut all_match = true; if let Some(expected) = &req.if_none_match { if &etag != expected { all_match = false; } else { any_match = true; } } if let Some(expected) = &req.if_modified_since { // NOTE: Equality comparison is good enough for tests. if &last_modified != expected { all_match = false; } else { any_match = true; } } if any_match && all_match { return Response { body: Vec::new(), code: 304, headers: vec![], }; } else { return Response { body: data, code: 200, headers: vec![ format!("ETag: \"{}\"", etag), format!("Last-Modified: {}", last_modified), ], }; } } } pub fn check_authorized_publish(&self, req: &Request) -> Response { if let Some(body) = &req.body { // Mimic the publish behavior for local registries by writing out the request // so tests can verify publishes made to either registry type. let path = self.api_path.join("api/v1/crates/new"); t!(fs::create_dir_all(path.parent().unwrap())); t!(fs::write(&path, body)); // Get the metadata of the package let (len, remaining) = body.split_at(4); let json_len = u32::from_le_bytes(len.try_into().unwrap()); let (json, remaining) = remaining.split_at(json_len as usize); let new_crate = serde_json::from_slice::(json).unwrap(); // Get the `.crate` file let (len, remaining) = remaining.split_at(4); let file_len = u32::from_le_bytes(len.try_into().unwrap()); let (file, _remaining) = remaining.split_at(file_len as usize); let file_cksum = cksum(&file); if !self.check_authorized( req, Some(Mutation { mutation: "publish", name: Some(&new_crate.name), vers: Some(&new_crate.vers), cksum: Some(&file_cksum), }), ) { return self.unauthorized(req); } let dst = self .dl_path .join(&new_crate.name) .join(&new_crate.vers) .join("download"); if self.delayed_index_update == 0 { save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path); } else { let delayed_index_update = self.delayed_index_update; let registry_path = self.registry_path.clone(); let file = Vec::from(file); thread::spawn(move || { thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0)); save_new_crate(dst, new_crate, &file, file_cksum, ®istry_path); }); } self.ok(&req) } else { Response { code: 400, headers: vec![], body: b"The request was missing a body".to_vec(), } } } } fn save_new_crate( dst: PathBuf, new_crate: crates_io::NewCrate, file: &[u8], file_cksum: String, registry_path: &Path, ) { // Write the `.crate` t!(fs::create_dir_all(dst.parent().unwrap())); t!(fs::write(&dst, file)); let deps = new_crate .deps .iter() .map(|dep| { let (name, package) = match &dep.explicit_name_in_toml { Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())), None => (dep.name.to_string(), None), }; serde_json::json!({ "name": name, "req": dep.version_req, "features": dep.features, "default_features": dep.default_features, "target": dep.target, "optional": dep.optional, "kind": dep.kind, "registry": dep.registry, "package": package, "artifact": dep.artifact, "bindep_target": dep.bindep_target, "lib": dep.lib, }) }) .collect::>(); let line = create_index_line( serde_json::json!(new_crate.name), &new_crate.vers, deps, &file_cksum, new_crate.features, false, new_crate.links, new_crate.rust_version.as_deref(), None, None, ); write_to_index(registry_path, &new_crate.name, line, false); } impl Package { /// Creates a new package builder. /// Call `publish()` to finalize and build the package. pub fn new(name: &str, vers: &str) -> Package { let config = paths::cargo_home().join("config.toml"); if !config.exists() { init(); } Package { name: name.to_string(), vers: vers.to_string(), deps: Vec::new(), files: Vec::new(), yanked: false, features: BTreeMap::new(), local: false, alternative: false, invalid_index_line: false, index_line: None, edition: None, resolver: None, proc_macro: false, links: None, rust_version: None, cargo_features: Vec::new(), pubtime: None, v: None, } } /// Call with `true` to publish in a "local registry". /// /// See `source-replacement.html#local-registry-sources` for more details /// on local registries. See `local_registry.rs` for the tests that use /// this. pub fn local(&mut self, local: bool) -> &mut Package { self.local = local; self } /// Call with `true` to publish in an "alternative registry". /// /// The name of the alternative registry is called "alternative". /// /// See `src/doc/src/reference/registries.md` for more details on /// alternative registries. See `alt_registry.rs` for the tests that use /// this. /// /// **Requires:** [`alt_init`] pub fn alternative(&mut self, alternative: bool) -> &mut Package { self.alternative = alternative; self } /// Adds a file to the package. pub fn file(&mut self, name: &str, contents: &str) -> &mut Package { self.file_with_mode(name, DEFAULT_MODE, contents) } /// Adds a file with a specific Unix mode. pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package { self.files.push(PackageFile { path: path.to_string(), contents: EntryData::Regular(contents.into()), mode, extra: false, }); self } /// Adds a symlink to a path to the package. pub fn symlink(&mut self, dst: &str, src: &str) -> &mut Package { self.files.push(PackageFile { path: dst.to_string(), contents: EntryData::Symlink(src.into()), mode: DEFAULT_MODE, extra: false, }); self } /// Adds an empty directory at the given path. pub fn directory(&mut self, path: &str) -> &mut Package { self.files.push(PackageFile { path: path.to_string(), contents: EntryData::Directory, mode: DEFAULT_MODE, extra: false, }); self } /// Adds an "extra" file that is not rooted within the package. /// /// Normal files are automatically placed within a directory named /// `$PACKAGE-$VERSION`. This allows you to override that behavior, /// typically for testing invalid behavior. pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package { self.files.push(PackageFile { path: path.to_string(), contents: EntryData::Regular(contents.to_string()), mode: DEFAULT_MODE, extra: true, }); self } /// Adds a normal dependency. Example: /// ```toml /// [dependencies] /// foo = {version = "1.0"} /// ``` pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package { self.add_dep(&Dependency::new(name, vers)) } /// Adds a dependency with the given feature. Example: /// ```toml /// [dependencies] /// foo = {version = "1.0", "features": ["feat1", "feat2"]} /// ``` pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package { self.add_dep(Dependency::new(name, vers).enable_features(features)) } /// Adds a platform-specific dependency. Example: /// ```toml /// [target.'cfg(windows)'.dependencies] /// foo = {version = "1.0"} /// ``` pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package { self.add_dep(Dependency::new(name, vers).target(target)) } /// Adds a dependency to the alternative registry. pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package { self.add_dep(Dependency::new(name, vers).registry("alternative")) } /// Adds a dev-dependency. Example: /// ```toml /// [dev-dependencies] /// foo = {version = "1.0"} /// ``` pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package { self.add_dep(Dependency::new(name, vers).dev()) } /// Adds a build-dependency. Example: /// ```toml /// [build-dependencies] /// foo = {version = "1.0"} /// ``` pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package { self.add_dep(Dependency::new(name, vers).build()) } pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package { self.deps.push(dep.clone()); self } /// Specifies whether or not the package is "yanked". pub fn yanked(&mut self, yanked: bool) -> &mut Package { self.yanked = yanked; self } /// Specifies `package.edition` pub fn edition(&mut self, edition: &str) -> &mut Package { self.edition = Some(edition.to_owned()); self } /// Specifies `package.resolver` pub fn resolver(&mut self, resolver: &str) -> &mut Package { self.resolver = Some(resolver.to_owned()); self } /// Specifies whether or not this is a proc macro. pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package { self.proc_macro = proc_macro; self } /// Adds an entry in the `[features]` section. pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package { let deps = deps.iter().map(|s| s.to_string()).collect(); self.features.insert(name.to_string(), deps); self } /// Specify a minimal Rust version. pub fn rust_version(&mut self, rust_version: &str) -> &mut Package { self.rust_version = Some(rust_version.into()); self } /// Causes the JSON line emitted in the index to be invalid, presumably /// causing Cargo to skip over this version. pub fn invalid_index_line(&mut self, invalid: bool) -> &mut Package { self.invalid_index_line = invalid; self } /// Override the auto-generated index line /// /// This can give more control over error cases than [`Package::invalid_index_line`] pub fn index_line(&mut self, line: &str) -> &mut Package { self.index_line = Some(line.to_owned()); self } pub fn links(&mut self, links: &str) -> &mut Package { self.links = Some(links.to_string()); self } pub fn cargo_feature(&mut self, feature: &str) -> &mut Package { self.cargo_features.push(feature.to_owned()); self } /// The publish time for the package in ISO8601 with UTC timezone (e.g. 2025-11-12T19:30:12Z) pub fn pubtime(&mut self, time: &str) -> &mut Package { self.pubtime = Some(time.to_owned()); self } /// Sets the index schema version for this package. /// /// See `cargo::sources::registry::IndexPackage` for more information. pub fn schema_version(&mut self, version: u32) -> &mut Package { self.v = Some(version); self } /// Creates the package and place it in the registry. /// /// This does not actually use Cargo's publishing system, but instead /// manually creates the entry in the registry on the filesystem. /// /// Returns the checksum for the package. pub fn publish(&self) -> String { self.make_archive(); // Figure out what we're going to write into the index. let deps = self .deps .iter() .map(|dep| { // In the index, the `registry` is null if it is from the same registry. // In Cargo.toml, it is None if it is from crates.io. let registry_url = match (self.alternative, dep.registry.as_deref()) { (false, None) => None, (false, Some("alternative")) => Some(alt_registry_url().to_string()), (true, None) => { Some("https://github.com/rust-lang/crates.io-index".to_string()) } (true, Some("alternative")) => None, _ => panic!("registry_dep currently only supports `alternative`"), }; let artifact = if let Some(artifact) = &dep.artifact { serde_json::json!([artifact]) } else { serde_json::json!(null) }; serde_json::json!({ "name": dep.name, "req": dep.vers, "features": dep.features, "default_features": dep.default_features, "target": dep.target, "artifact": artifact, "bindep_target": dep.bindep_target, "lib": dep.lib, "optional": dep.optional, "kind": dep.kind, "registry": registry_url, "package": dep.package, "public": dep.public, }) }) .collect::>(); let cksum = { let c = t!(fs::read(&self.archive_dst())); cksum(&c) }; let line = if let Some(line) = self.index_line.clone() { line } else { let name = if self.invalid_index_line { serde_json::json!(1) } else { serde_json::json!(self.name) }; create_index_line( name, &self.vers, deps, &cksum, self.features.clone(), self.yanked, self.links.clone(), self.rust_version.as_deref(), self.pubtime.as_deref(), self.v, ) }; let registry_path = if self.alternative { alt_registry_path() } else { registry_path() }; write_to_index(®istry_path, &self.name, line, self.local); cksum } fn make_archive(&self) { let dst = self.archive_dst(); t!(fs::create_dir_all(dst.parent().unwrap())); let f = t!(File::create(&dst)); let mut a = Builder::new(GzEncoder::new(f, Compression::none())); a.sparse(false); if !self .files .iter() .any(|PackageFile { path, .. }| path == "Cargo.toml") { self.append_manifest(&mut a); } if self.files.is_empty() { self.append( &mut a, "src/lib.rs", DEFAULT_MODE, &EntryData::Regular("".into()), ); } else { for PackageFile { path, contents, mode, extra, } in &self.files { if *extra { self.append_raw(&mut a, path, *mode, contents); } else { self.append(&mut a, path, *mode, contents); } } } } fn append_manifest(&self, ar: &mut Builder) { let mut manifest = String::new(); if !self.cargo_features.is_empty() { let mut features = String::new(); serde::Serialize::serialize( &self.cargo_features, toml::ser::ValueSerializer::new(&mut features), ) .unwrap(); manifest.push_str(&format!("cargo-features = {}\n\n", features)); } manifest.push_str(&format!( r#" [package] name = "{}" version = "{}" authors = [] "#, self.name, self.vers )); if let Some(version) = &self.rust_version { manifest.push_str(&format!("rust-version = \"{}\"\n", version)); } if let Some(edition) = &self.edition { manifest.push_str(&format!("edition = \"{}\"\n", edition)); } if let Some(resolver) = &self.resolver { manifest.push_str(&format!("resolver = \"{}\"\n", resolver)); } if !self.features.is_empty() { let features: Vec = self .features .iter() .map(|(feature, features)| { if features.is_empty() { format!("{} = []", feature) } else { format!( "{} = [{}]", feature, features .iter() .map(|s| format!("\"{}\"", s)) .collect::>() .join(", ") ) } }) .collect(); manifest.push_str(&format!("\n[features]\n{}", features.join("\n"))); } for dep in self.deps.iter() { let target = match dep.target { None => String::new(), Some(ref s) => format!("target.'{}'.", s), }; let kind = match &dep.kind[..] { "build" => "build-", "dev" => "dev-", _ => "", }; manifest.push_str(&format!( r#" [{}{}dependencies.{}] version = "{}" "#, target, kind, dep.name, dep.vers )); if dep.optional { manifest.push_str("optional = true\n"); } if let Some(artifact) = &dep.artifact { manifest.push_str(&format!("artifact = \"{}\"\n", artifact)); } if let Some(target) = &dep.bindep_target { manifest.push_str(&format!("target = \"{}\"\n", target)); } if dep.lib { manifest.push_str("lib = true\n"); } if let Some(registry) = &dep.registry { assert_eq!(registry, "alternative"); manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url())); } if !dep.default_features { manifest.push_str("default-features = false\n"); } if !dep.features.is_empty() { let mut features = String::new(); serde::Serialize::serialize( &dep.features, toml::ser::ValueSerializer::new(&mut features), ) .unwrap(); manifest.push_str(&format!("features = {}\n", features)); } if let Some(package) = &dep.package { manifest.push_str(&format!("package = \"{}\"\n", package)); } } if self.proc_macro { manifest.push_str("[lib]\nproc-macro = true\n"); } self.append( ar, "Cargo.toml", DEFAULT_MODE, &EntryData::Regular(manifest.into()), ); } fn append(&self, ar: &mut Builder, file: &str, mode: u32, contents: &EntryData) { self.append_raw( ar, &format!("{}-{}/{}", self.name, self.vers, file), mode, contents, ); } fn append_raw( &self, ar: &mut Builder, path: &str, mode: u32, contents: &EntryData, ) { let mut header = Header::new_ustar(); let contents = match contents { EntryData::Regular(contents) => contents.as_str(), EntryData::Symlink(src) => { header.set_entry_type(tar::EntryType::Symlink); t!(header.set_link_name(src)); "" // Symlink has no contents. } EntryData::Directory => { header.set_entry_type(tar::EntryType::Directory); "" } }; header.set_size(contents.len() as u64); t!(header.set_path(path)); header.set_mode(mode); header.set_cksum(); t!(ar.append(&header, contents.as_bytes())); } /// Returns the path to the compressed package file. pub fn archive_dst(&self) -> PathBuf { if self.local { let path = if self.alternative { alt_registry_path() } else { registry_path() }; path.join(format!("{}-{}.crate", self.name, self.vers)) } else if self.alternative { alt_dl_path() .join(&self.name) .join(&self.vers) .join("download") } else { dl_path().join(&self.name).join(&self.vers).join("download") } } } /// Generate a checksum pub fn cksum(s: &[u8]) -> String { Sha256::new().update(s).finish_hex() } impl Dependency { pub fn new(name: &str, vers: &str) -> Dependency { Dependency { name: name.to_string(), vers: vers.to_string(), kind: "normal".to_string(), artifact: None, bindep_target: None, lib: false, target: None, features: Vec::new(), package: None, optional: false, registry: None, default_features: true, public: false, } } /// Changes this to `[build-dependencies]`. pub fn build(&mut self) -> &mut Self { self.kind = "build".to_string(); self } /// Changes this to `[dev-dependencies]`. pub fn dev(&mut self) -> &mut Self { self.kind = "dev".to_string(); self } /// Changes this to `[target.$target.dependencies]`. pub fn target(&mut self, target: &str) -> &mut Self { self.target = Some(target.to_string()); self } /// Change the artifact to be of the given kind, like "bin", or "staticlib", /// along with a specific target triple if provided. pub fn artifact(&mut self, kind: &str, target: Option) -> &mut Self { self.artifact = Some(kind.to_string()); self.bindep_target = target; self } /// Adds `registry = $registry` to this dependency. pub fn registry(&mut self, registry: &str) -> &mut Self { self.registry = Some(registry.to_string()); self } /// Adds `features = [ ... ]` to this dependency. pub fn enable_features(&mut self, features: &[&str]) -> &mut Self { self.features.extend(features.iter().map(|s| s.to_string())); self } /// Adds `package = ...` to this dependency. pub fn package(&mut self, pkg: &str) -> &mut Self { self.package = Some(pkg.to_string()); self } /// Changes this to an optional dependency. pub fn optional(&mut self, optional: bool) -> &mut Self { self.optional = optional; self } /// Changes this to an public dependency. pub fn public(&mut self, public: bool) -> &mut Self { self.public = public; self } /// Adds `default-features = false` if the argument is `false`. pub fn default_features(&mut self, default_features: bool) -> &mut Self { self.default_features = default_features; self } } ================================================ FILE: crates/cargo-util/Cargo.toml ================================================ [package] name = "cargo-util" version = "0.2.29" rust-version = "1.94" # MSRV:1 edition.workspace = true license.workspace = true repository.workspace = true description = "Miscellaneous support code used by Cargo." [dependencies] anyhow.workspace = true filetime.workspace = true hex.workspace = true ignore.workspace = true jobserver.workspace = true same-file.workspace = true sha2.workspace = true shell-escape.workspace = true tempfile.workspace = true tracing.workspace = true walkdir.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true [target.'cfg(unix)'.dependencies] libc.workspace = true [target.'cfg(windows)'.dependencies] miow.workspace = true windows-sys = { workspace = true, features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_System_Console"] } [lints] workspace = true ================================================ FILE: crates/cargo-util/README.md ================================================ > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use (except as a transitive dependency). This > crate may make major changes to its APIs or be deprecated without warning. ================================================ FILE: crates/cargo-util/src/du.rs ================================================ //! A simple disk usage estimator. use anyhow::{Context, Result}; use ignore::overrides::OverrideBuilder; use ignore::{WalkBuilder, WalkState}; use std::path::Path; use std::sync::{Arc, Mutex}; /// Determines the disk usage of all files in the given directory. /// /// The given patterns are gitignore style patterns relative to the given /// path. If there are patterns, it will only count things matching that /// pattern. `!` can be used to exclude things. See [`OverrideBuilder::add`] /// for more info. /// /// This is a primitive implementation that doesn't handle hard links, and /// isn't particularly fast (for example, not using `getattrlistbulk` on /// macOS). It also only uses actual byte sizes instead of block counts (and /// thus vastly undercounts directories with lots of small files). It would be /// nice to improve this or replace it with something better. pub fn du(path: &Path, patterns: &[&str]) -> Result { du_inner(path, patterns).with_context(|| format!("failed to walk `{}`", path.display())) } fn du_inner(path: &Path, patterns: &[&str]) -> Result { let mut builder = OverrideBuilder::new(path); for pattern in patterns { builder.add(pattern)?; } let overrides = builder.build()?; let mut builder = WalkBuilder::new(path); builder .overrides(overrides) .hidden(false) .parents(false) .ignore(false) .git_global(false) .git_ignore(false) .git_exclude(false); let walker = builder.build_parallel(); // Platforms like PowerPC don't support AtomicU64, so we use a Mutex instead. // // See: // - https://github.com/rust-lang/cargo/pull/12981 // - https://github.com/rust-lang/rust/pull/117916#issuecomment-1812635848 let total = Arc::new(Mutex::new(0u64)); // A slot used to indicate there was an error while walking. // // It is possible that more than one error happens (such as in different // threads). The error returned is arbitrary in that case. let err = Arc::new(Mutex::new(None)); walker.run(|| { Box::new(|entry| { match entry { Ok(entry) => match entry.metadata() { Ok(meta) => { if meta.is_file() { let mut lock = total.lock().unwrap(); *lock += meta.len(); } } Err(e) => { *err.lock().unwrap() = Some(e.into()); return WalkState::Quit; } }, Err(e) => { *err.lock().unwrap() = Some(e.into()); return WalkState::Quit; } } WalkState::Continue }) }); if let Some(e) = err.lock().unwrap().take() { return Err(e); } let total = *total.lock().unwrap(); Ok(total) } ================================================ FILE: crates/cargo-util/src/lib.rs ================================================ //! Miscellaneous support code used by Cargo. //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use (except as a transitive dependency). This //! > crate may make major changes to its APIs or be deprecated without warning. #![allow(clippy::disallowed_methods)] pub use self::read2::read2; pub use du::du; pub use process_builder::ProcessBuilder; pub use process_error::{ProcessError, exit_status_to_string, is_simple_exit_code}; pub use sha256::Sha256; mod du; pub mod paths; mod process_builder; mod process_error; mod read2; pub mod registry; mod sha256; /// Whether or not this running in a Continuous Integration environment. pub fn is_ci() -> bool { std::env::var("CI").is_ok() || std::env::var("TF_BUILD").is_ok() } ================================================ FILE: crates/cargo-util/src/paths.rs ================================================ //! Various utilities for working with files and paths. use anyhow::{Context, Result}; use filetime::FileTime; use std::env; use std::ffi::{OsStr, OsString}; use std::fs::{self, File, Metadata, OpenOptions}; use std::io; use std::io::prelude::*; use std::iter; use std::path::{Component, Path, PathBuf}; use tempfile::Builder as TempFileBuilder; /// Joins paths into a string suitable for the `PATH` environment variable. /// /// This is equivalent to [`std::env::join_paths`], but includes a more /// detailed error message. The given `env` argument is the name of the /// environment variable this is will be used for, which is included in the /// error message. pub fn join_paths>(paths: &[T], env: &str) -> Result { env::join_paths(paths.iter()).with_context(|| { let mut message = format!( "failed to join paths from `${env}` together\n\n\ Check if any of path segments listed below contain an \ unterminated quote character or path separator:" ); for path in paths { use std::fmt::Write; write!(&mut message, "\n {:?}", Path::new(path)).unwrap(); } message }) } /// Returns the name of the environment variable used for searching for /// dynamic libraries. pub fn dylib_path_envvar() -> &'static str { if cfg!(windows) { "PATH" } else if cfg!(target_os = "macos") { // When loading and linking a dynamic library or bundle, dlopen // searches in LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, PWD, and // DYLD_FALLBACK_LIBRARY_PATH. // In the Mach-O format, a dynamic library has an "install path." // Clients linking against the library record this path, and the // dynamic linker, dyld, uses it to locate the library. // dyld searches DYLD_LIBRARY_PATH *before* the install path. // dyld searches DYLD_FALLBACK_LIBRARY_PATH only if it cannot // find the library in the install path. // Setting DYLD_LIBRARY_PATH can easily have unintended // consequences. // // Also, DYLD_LIBRARY_PATH appears to have significant performance // penalty starting in 10.13. Cargo's testsuite ran more than twice as // slow with it on CI. "DYLD_FALLBACK_LIBRARY_PATH" } else if cfg!(target_os = "aix") { "LIBPATH" } else { "LD_LIBRARY_PATH" } } /// Returns a list of directories that are searched for dynamic libraries. /// /// Note that some operating systems will have defaults if this is empty that /// will need to be dealt with. pub fn dylib_path() -> Vec { match env::var_os(dylib_path_envvar()) { Some(var) => env::split_paths(&var).collect(), None => Vec::new(), } } /// Normalize a path, removing things like `.` and `..`. /// /// CAUTION: This does not resolve symlinks (unlike /// [`std::fs::canonicalize`]). This may cause incorrect or surprising /// behavior at times. This should be used carefully. Unfortunately, /// [`std::fs::canonicalize`] can be hard to use correctly, since it can often /// fail, or on Windows returns annoying device paths. This is a problem Cargo /// needs to improve on. pub fn normalize_path(path: &Path) -> PathBuf { let mut components = path.components().peekable(); let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { components.next(); PathBuf::from(c.as_os_str()) } else { PathBuf::new() }; for component in components { match component { Component::Prefix(..) => unreachable!(), Component::RootDir => { ret.push(Component::RootDir); } Component::CurDir => {} Component::ParentDir => { if ret.ends_with(Component::ParentDir) { ret.push(Component::ParentDir); } else { let popped = ret.pop(); if !popped && !ret.has_root() { ret.push(Component::ParentDir); } } } Component::Normal(c) => { ret.push(c); } } } ret } /// Returns the absolute path of where the given executable is located based /// on searching the `PATH` environment variable. /// /// Returns an error if it cannot be found. pub fn resolve_executable(exec: &Path) -> Result { if exec.components().count() == 1 { let paths = env::var_os("PATH").ok_or_else(|| anyhow::format_err!("no PATH"))?; let candidates = env::split_paths(&paths).flat_map(|path| { let candidate = path.join(&exec); let with_exe = if env::consts::EXE_EXTENSION.is_empty() { None } else { Some(candidate.with_extension(env::consts::EXE_EXTENSION)) }; iter::once(candidate).chain(with_exe) }); for candidate in candidates { if candidate.is_file() { return Ok(candidate); } } anyhow::bail!("no executable for `{}` found in PATH", exec.display()) } else { Ok(exec.into()) } } /// Returns metadata for a file (follows symlinks). /// /// Equivalent to [`std::fs::metadata`] with better error messages. pub fn metadata>(path: P) -> Result { let path = path.as_ref(); std::fs::metadata(path) .with_context(|| format!("failed to load metadata for path `{}`", path.display())) } /// Returns metadata for a file without following symlinks. /// /// Equivalent to [`std::fs::metadata`] with better error messages. pub fn symlink_metadata>(path: P) -> Result { let path = path.as_ref(); std::fs::symlink_metadata(path) .with_context(|| format!("failed to load metadata for path `{}`", path.display())) } /// Reads a file to a string. /// /// Equivalent to [`std::fs::read_to_string`] with better error messages. pub fn read(path: &Path) -> Result { match String::from_utf8(read_bytes(path)?) { Ok(s) => Ok(s), Err(_) => anyhow::bail!("path at `{}` was not valid utf-8", path.display()), } } /// Reads a file into a bytes vector. /// /// Equivalent to [`std::fs::read`] with better error messages. pub fn read_bytes(path: &Path) -> Result> { fs::read(path).with_context(|| format!("failed to read `{}`", path.display())) } /// Writes a file to disk. /// /// Equivalent to [`std::fs::write`] with better error messages. pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { let path = path.as_ref(); fs::write(path, contents.as_ref()) .with_context(|| format!("failed to write `{}`", path.display())) } /// Writes a file to disk atomically. /// /// This uses `tempfile::persist` to accomplish atomic writes. /// If the path is a symlink, it will follow the symlink and write to the actual target. pub fn write_atomic, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { let path = path.as_ref(); // Check if the path is a symlink and follow it if it is let resolved_path; let path = if path.is_symlink() { resolved_path = fs::read_link(path) .with_context(|| format!("failed to read symlink at `{}`", path.display()))?; &resolved_path } else { path }; // On unix platforms, get the permissions of the original file. Copy only the user/group/other // read/write/execute permission bits. The tempfile lib defaults to an initial mode of 0o600, // and we'll set the proper permissions after creating the file. #[cfg(unix)] let perms = path.metadata().ok().map(|meta| { use std::os::unix::fs::PermissionsExt; // these constants are u16 on macOS let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO); let mode = meta.permissions().mode() & mask; std::fs::Permissions::from_mode(mode) }); let mut tmp = TempFileBuilder::new() .prefix(path.file_name().unwrap()) .tempfile_in(path.parent().unwrap())?; tmp.write_all(contents.as_ref())?; // On unix platforms, set the permissions on the newly created file. We can use fchmod (called // by the std lib; subject to change) which ignores the umask so that the new file has the same // permissions as the old file. #[cfg(unix)] if let Some(perms) = perms { tmp.as_file().set_permissions(perms)?; } tmp.persist(path)?; Ok(()) } /// Equivalent to [`write()`], but does not write anything if the file contents /// are identical to the given contents. pub fn write_if_changed, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { (|| -> Result<()> { let contents = contents.as_ref(); let mut f = OpenOptions::new() .read(true) .write(true) .create(true) .open(&path)?; let mut orig = Vec::new(); f.read_to_end(&mut orig)?; if orig != contents { f.set_len(0)?; f.seek(io::SeekFrom::Start(0))?; f.write_all(contents)?; } Ok(()) })() .with_context(|| format!("failed to write `{}`", path.as_ref().display()))?; Ok(()) } /// Equivalent to [`write()`], but appends to the end instead of replacing the /// contents. pub fn append(path: &Path, contents: &[u8]) -> Result<()> { (|| -> Result<()> { let mut f = OpenOptions::new() .write(true) .append(true) .create(true) .open(path)?; f.write_all(contents)?; Ok(()) })() .with_context(|| format!("failed to write `{}`", path.display()))?; Ok(()) } /// Creates a new file. pub fn create>(path: P) -> Result { let path = path.as_ref(); File::create(path).with_context(|| format!("failed to create file `{}`", path.display())) } /// Opens an existing file. pub fn open>(path: P) -> Result { let path = path.as_ref(); File::open(path).with_context(|| format!("failed to open file `{}`", path.display())) } /// Returns the last modification time of a file. pub fn mtime(path: &Path) -> Result { let meta = metadata(path)?; Ok(FileTime::from_last_modification_time(&meta)) } /// Returns the maximum mtime of the given path, recursing into /// subdirectories, and following symlinks. pub fn mtime_recursive(path: &Path) -> Result { let meta = metadata(path)?; if !meta.is_dir() { return Ok(FileTime::from_last_modification_time(&meta)); } let max_meta = walkdir::WalkDir::new(path) .follow_links(true) .into_iter() .filter_map(|e| match e { Ok(e) => Some(e), Err(e) => { // Ignore errors while walking. If Cargo can't access it, the // build script probably can't access it, either. tracing::debug!("failed to determine mtime while walking directory: {}", e); None } }) .filter_map(|e| { if e.path_is_symlink() { // Use the mtime of both the symlink and its target, to // handle the case where the symlink is modified to a // different target. let sym_meta = match std::fs::symlink_metadata(e.path()) { Ok(m) => m, Err(err) => { // I'm not sure when this is really possible (maybe a // race with unlinking?). Regardless, if Cargo can't // read it, the build script probably can't either. tracing::debug!( "failed to determine mtime while fetching symlink metadata of {}: {}", e.path().display(), err ); return None; } }; let sym_mtime = FileTime::from_last_modification_time(&sym_meta); // Walkdir follows symlinks. match e.metadata() { Ok(target_meta) => { let target_mtime = FileTime::from_last_modification_time(&target_meta); Some(sym_mtime.max(target_mtime)) } Err(err) => { // Can't access the symlink target. If Cargo can't // access it, the build script probably can't access // it either. tracing::debug!( "failed to determine mtime of symlink target for {}: {}", e.path().display(), err ); Some(sym_mtime) } } } else { let meta = match e.metadata() { Ok(m) => m, Err(err) => { // I'm not sure when this is really possible (maybe a // race with unlinking?). Regardless, if Cargo can't // read it, the build script probably can't either. tracing::debug!( "failed to determine mtime while fetching metadata of {}: {}", e.path().display(), err ); return None; } }; Some(FileTime::from_last_modification_time(&meta)) } }) .max() // or_else handles the case where there are no files in the directory. .unwrap_or_else(|| FileTime::from_last_modification_time(&meta)); Ok(max_meta) } /// Record the current time on the filesystem (using the filesystem's clock) /// using a file at the given directory. Returns the current time. pub fn set_invocation_time(path: &Path) -> Result { // note that if `FileTime::from_system_time(SystemTime::now());` is determined to be sufficient, // then this can be removed. let timestamp = path.join("invoked.timestamp"); write( ×tamp, "This file has an mtime of when this was started.", )?; let ft = mtime(×tamp)?; tracing::debug!("invocation time for {:?} is {}", path, ft); Ok(ft) } /// Converts a path to UTF-8 bytes. pub fn path2bytes(path: &Path) -> Result<&[u8]> { #[cfg(unix)] { use std::os::unix::prelude::*; Ok(path.as_os_str().as_bytes()) } #[cfg(windows)] { match path.as_os_str().to_str() { Some(s) => Ok(s.as_bytes()), None => Err(anyhow::format_err!( "invalid non-unicode path: {}", path.display() )), } } } /// Converts UTF-8 bytes to a path. pub fn bytes2path(bytes: &[u8]) -> Result { #[cfg(unix)] { use std::os::unix::prelude::*; Ok(PathBuf::from(OsStr::from_bytes(bytes))) } #[cfg(windows)] { use std::str; match str::from_utf8(bytes) { Ok(s) => Ok(PathBuf::from(s)), Err(..) => Err(anyhow::format_err!("invalid non-unicode path")), } } } /// Returns an iterator that walks up the directory hierarchy towards the root. /// /// Each item is a [`Path`]. It will start with the given path, finishing at /// the root. If the `stop_root_at` parameter is given, it will stop at the /// given path (which will be the last item). pub fn ancestors<'a>(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> { PathAncestors::new(path, stop_root_at) } pub struct PathAncestors<'a> { current: Option<&'a Path>, stop_at: Option, } impl<'a> PathAncestors<'a> { fn new(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> { let stop_at = env::var("__CARGO_TEST_ROOT") .ok() .map(PathBuf::from) .or_else(|| stop_root_at.map(|p| p.to_path_buf())); PathAncestors { current: Some(path), //HACK: avoid reading `~/.cargo/config` when testing Cargo itself. stop_at, } } } impl<'a> Iterator for PathAncestors<'a> { type Item = &'a Path; fn next(&mut self) -> Option<&'a Path> { if let Some(path) = self.current { self.current = path.parent(); if let Some(ref stop_at) = self.stop_at { if path == stop_at { self.current = None; } } Some(path) } else { None } } } /// Equivalent to [`std::fs::create_dir_all`] with better error messages. pub fn create_dir_all(p: impl AsRef) -> Result<()> { _create_dir_all(p.as_ref()) } fn _create_dir_all(p: &Path) -> Result<()> { fs::create_dir_all(p) .with_context(|| format!("failed to create directory `{}`", p.display()))?; Ok(()) } /// Equivalent to [`std::fs::remove_dir_all`] with better error messages. /// /// This does *not* follow symlinks. pub fn remove_dir_all>(p: P) -> Result<()> { _remove_dir_all(p.as_ref()).or_else(|prev_err| { // `std::fs::remove_dir_all` is highly specialized for different platforms // and may be more reliable than a simple walk. We try the walk first in // order to report more detailed errors. fs::remove_dir_all(p.as_ref()).with_context(|| { format!( "{:?}\n\nError: failed to remove directory `{}`", prev_err, p.as_ref().display(), ) }) }) } fn _remove_dir_all(p: &Path) -> Result<()> { if symlink_metadata(p)?.is_symlink() { return remove_file(p); } let entries = p .read_dir() .with_context(|| format!("failed to read directory `{}`", p.display()))?; for entry in entries { let entry = entry?; let path = entry.path(); if entry.file_type()?.is_dir() { remove_dir_all(&path)?; } else { remove_file(&path)?; } } remove_dir(&p) } /// Equivalent to [`std::fs::remove_dir`] with better error messages. pub fn remove_dir>(p: P) -> Result<()> { _remove_dir(p.as_ref()) } fn _remove_dir(p: &Path) -> Result<()> { fs::remove_dir(p).with_context(|| format!("failed to remove directory `{}`", p.display()))?; Ok(()) } /// Equivalent to [`std::fs::remove_file`] with better error messages. /// /// If the file is readonly, this will attempt to change the permissions to /// force the file to be deleted. /// On Windows, if the file is a symlink to a directory, this will attempt to remove /// the symlink itself. pub fn remove_file>(p: P) -> Result<()> { _remove_file(p.as_ref()) } fn _remove_file(p: &Path) -> Result<()> { // For Windows, we need to check if the file is a symlink to a directory // and remove the symlink itself by calling `remove_dir` instead of // `remove_file`. #[cfg(target_os = "windows")] { use std::os::windows::fs::FileTypeExt; let metadata = symlink_metadata(p)?; let file_type = metadata.file_type(); if file_type.is_symlink_dir() { return remove_symlink_dir_with_permission_check(p); } } remove_file_with_permission_check(p) } #[cfg(target_os = "windows")] fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> { remove_with_permission_check(fs::remove_dir, p) .with_context(|| format!("failed to remove symlink dir `{}`", p.display())) } fn remove_file_with_permission_check(p: &Path) -> Result<()> { remove_with_permission_check(fs::remove_file, p) .with_context(|| format!("failed to remove file `{}`", p.display())) } fn remove_with_permission_check(remove_func: F, p: P) -> io::Result<()> where F: Fn(P) -> io::Result<()>, P: AsRef + Clone, { match remove_func(p.clone()) { Ok(()) => Ok(()), Err(e) => { if e.kind() == io::ErrorKind::PermissionDenied && set_not_readonly(p.as_ref()).unwrap_or(false) { remove_func(p) } else { Err(e) } } } } fn set_not_readonly(p: &Path) -> io::Result { let mut perms = p.metadata()?.permissions(); if !perms.readonly() { return Ok(false); } perms.set_readonly(false); fs::set_permissions(p, perms)?; Ok(true) } /// Hardlink (file) or symlink (dir) src to dst if possible, otherwise copy it. /// /// If the destination already exists, it is removed before linking. pub fn link_or_copy(src: impl AsRef, dst: impl AsRef) -> Result<()> { let src = src.as_ref(); let dst = dst.as_ref(); _link_or_copy(src, dst) } fn _link_or_copy(src: &Path, dst: &Path) -> Result<()> { tracing::debug!("linking {} to {}", src.display(), dst.display()); if same_file::is_same_file(src, dst).unwrap_or(false) { return Ok(()); } // NB: we can't use dst.exists(), as if dst is a broken symlink, // dst.exists() will return false. This is problematic, as we still need to // unlink dst in this case. symlink_metadata(dst).is_ok() will tell us // whether dst exists *without* following symlinks, which is what we want. if fs::symlink_metadata(dst).is_ok() { remove_file(&dst)?; } let link_result = if src.is_dir() { #[cfg(unix)] use std::os::unix::fs::symlink; #[cfg(windows)] // FIXME: This should probably panic or have a copy fallback. Symlinks // are not supported in all windows environments. Currently symlinking // is only used for .dSYM directories on macos, but this shouldn't be // accidentally relied upon. use std::os::windows::fs::symlink_dir as symlink; let dst_dir = dst.parent().unwrap(); let src = if src.starts_with(dst_dir) { src.strip_prefix(dst_dir).unwrap() } else { src }; symlink(src, dst) } else { if cfg!(target_os = "macos") { // There seems to be a race condition with APFS when hard-linking // binaries. Gatekeeper does not have signing or hash information // stored in kernel when running the process. Therefore killing it. // This problem does not appear when copying files as kernel has // time to process it. Note that: fs::copy on macos is using // CopyOnWrite (syscall fclonefileat) which should be as fast as // hardlinking. See these issues for the details: // // * https://github.com/rust-lang/cargo/issues/7821 // * https://github.com/rust-lang/cargo/issues/10060 fs::copy(src, dst).map_or_else( |e| { if e.raw_os_error() .map_or(false, |os_err| os_err == 35 /* libc::EAGAIN */) { tracing::info!("copy failed {e:?}. falling back to fs::hard_link"); // Working around an issue copying too fast with zfs (probably related to // https://github.com/openzfsonosx/zfs/issues/809) // See https://github.com/rust-lang/cargo/issues/13838 fs::hard_link(src, dst) } else { Err(e) } }, |_| Ok(()), ) } else { fs::hard_link(src, dst) } }; link_result .or_else(|err| { tracing::debug!("link failed {}. falling back to fs::copy", err); fs::copy(src, dst).map(|_| ()) }) .with_context(|| { format!( "failed to link or copy `{}` to `{}`", src.display(), dst.display() ) })?; Ok(()) } /// Copies a file from one location to another. /// /// Equivalent to [`std::fs::copy`] with better error messages. pub fn copy, Q: AsRef>(from: P, to: Q) -> Result { let from = from.as_ref(); let to = to.as_ref(); fs::copy(from, to) .with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display())) } /// Changes the filesystem mtime (and atime if possible) for the given file. /// /// This intentionally does not return an error, as this is sometimes not /// supported on network filesystems. For the current uses in Cargo, this is a /// "best effort" approach, and errors shouldn't be propagated. pub fn set_file_time_no_err>(path: P, time: FileTime) { let path = path.as_ref(); match filetime::set_file_times(path, time, time) { Ok(()) => tracing::debug!("set file mtime {} to {}", path.display(), time), Err(e) => tracing::warn!( "could not set mtime of {} to {}: {:?}", path.display(), time, e ), } } /// Strips `base` from `path`. /// /// This canonicalizes both paths before stripping. This is useful if the /// paths are obtained in different ways, and one or the other may or may not /// have been normalized in some way. pub fn strip_prefix_canonical( path: impl AsRef, base: impl AsRef, ) -> Result { // Not all filesystems support canonicalize. Just ignore if it doesn't work. let safe_canonicalize = |path: &Path| match path.canonicalize() { Ok(p) => p, Err(e) => { tracing::warn!("cannot canonicalize {:?}: {:?}", path, e); path.to_path_buf() } }; let canon_path = safe_canonicalize(path.as_ref()); let canon_base = safe_canonicalize(base.as_ref()); canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf()) } /// Creates an excluded from cache directory atomically with its parents as needed. /// /// The atomicity only covers creating the leaf directory and exclusion from cache. Any missing /// parent directories will not be created in an atomic manner. /// /// This function is idempotent and in addition to that it won't exclude ``p`` from cache if it /// already exists. pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef) -> Result<()> { let path = p.as_ref(); if path.is_dir() { return Ok(()); } let parent = path.parent().unwrap(); let base = path.file_name().unwrap(); create_dir_all(parent)?; // We do this in two steps (first create a temporary directory and exclude // it from backups, then rename it to the desired name. If we created the // directory directly where it should be and then excluded it from backups // we would risk a situation where cargo is interrupted right after the directory // creation but before the exclusion the directory would remain non-excluded from // backups because we only perform exclusion right after we created the directory // ourselves. // // We need the tempdir created in parent instead of $TMP, because only then we can be // easily sure that rename() will succeed (the new name needs to be on the same mount // point as the old one). let tempdir = TempFileBuilder::new().prefix(base).tempdir_in(parent)?; exclude_from_backups(tempdir.path()); exclude_from_content_indexing(tempdir.path()); // Previously std::fs::create_dir_all() (through paths::create_dir_all()) was used // here to create the directory directly and fs::create_dir_all() explicitly treats // the directory being created concurrently by another thread or process as success, // hence the check below to follow the existing behavior. If we get an error at // rename() and suddenly the directory (which didn't exist a moment earlier) exists // we can infer from it's another cargo process doing work. if let Err(e) = fs::rename(tempdir.path(), path) { if !path.exists() { return Err(anyhow::Error::from(e)) .with_context(|| format!("failed to create directory `{}`", path.display())); } } Ok(()) } /// Mark an existing directory as excluded from backups and indexing. /// /// Errors in marking it are ignored. pub fn exclude_from_backups_and_indexing(p: impl AsRef) { let path = p.as_ref(); exclude_from_backups(path); exclude_from_content_indexing(path); } /// Marks the directory as excluded from archives/backups. /// /// This is recommended to prevent derived/temporary files from bloating backups. There are two /// mechanisms used to achieve this right now: /// /// * A dedicated resource property excluding from Time Machine backups on macOS /// * CACHEDIR.TAG files supported by various tools in a platform-independent way fn exclude_from_backups(path: &Path) { exclude_from_time_machine_and_cloud_sync(path); let file = path.join("CACHEDIR.TAG"); if !file.exists() { let _ = std::fs::write( file, "Signature: 8a477f597d28d172789f06886806bc55 # This file is a cache directory tag created by cargo. # For information about cache directory tags see https://bford.info/cachedir/ ", ); // Similarly to exclude_from_time_machine_and_cloud_sync() we ignore errors here as it's an optional feature. } } /// Marks the directory as excluded from content indexing. /// /// This is recommended to prevent the content of derived/temporary files from being indexed. /// This is very important for Windows users, as the live content indexing significantly slows /// cargo's I/O operations. /// /// This is currently a no-op on non-Windows platforms. fn exclude_from_content_indexing(path: &Path) { #[cfg(windows)] { use std::iter::once; use std::os::windows::prelude::OsStrExt; use windows_sys::Win32::Storage::FileSystem::{ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, GetFileAttributesW, SetFileAttributesW, }; let path: Vec = path.as_os_str().encode_wide().chain(once(0)).collect(); unsafe { SetFileAttributesW( path.as_ptr(), GetFileAttributesW(path.as_ptr()) | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, ); } } #[cfg(not(windows))] { let _ = path; } } #[cfg(not(target_os = "macos"))] fn exclude_from_time_machine_and_cloud_sync(_: &Path) {} #[cfg(target_os = "macos")] /// Marks files or directories as excluded from Time Machine and iCloud Drive on macOS fn exclude_from_time_machine_and_cloud_sync(path: &Path) { use core_foundation::base::TCFType; use core_foundation::{number, string, url}; use std::ptr; let path = match url::CFURL::from_path(path, false) { Some(url) => url, None => return, }; // For compatibility with old systems strings are used instead of global symbols const KEY_NAMES: [&str; 2] = [ "NSURLIsExcludedFromBackupKey", // kCFURLIsExcludedFromBackupKey "NSURLUbiquitousItemIsExcludedFromSyncKey", // kCFURLUbiquitousItemIsExcludedFromSyncKey ]; for key_name in KEY_NAMES { let is_excluded_key = match key_name.parse::() { Ok(key) => key, Err(_) => continue, }; unsafe { url::CFURLSetResourcePropertyForKey( path.as_concrete_TypeRef(), is_excluded_key.as_concrete_TypeRef(), number::kCFBooleanTrue as *const _, ptr::null_mut(), ); } } // Errors are ignored, since it's an optional feature and failure // doesn't prevent Cargo from working } #[cfg(test)] mod tests { use super::join_paths; use super::normalize_path; use super::write; use super::write_atomic; #[test] fn test_normalize_path() { let cases = &[ ("", ""), (".", ""), (".////./.", ""), ("/", "/"), ("/..", "/"), ("/foo/bar", "/foo/bar"), ("/foo/bar/", "/foo/bar"), ("/foo/bar/./././///", "/foo/bar"), ("/foo/bar/..", "/foo"), ("/foo/bar/../..", "/"), ("/foo/bar/../../..", "/"), ("foo/bar", "foo/bar"), ("foo/bar/", "foo/bar"), ("foo/bar/./././///", "foo/bar"), ("foo/bar/..", "foo"), ("foo/bar/../..", ""), ("foo/bar/../../..", ".."), ("../../foo/bar", "../../foo/bar"), ("../../foo/bar/", "../../foo/bar"), ("../../foo/bar/./././///", "../../foo/bar"), ("../../foo/bar/..", "../../foo"), ("../../foo/bar/../..", "../.."), ("../../foo/bar/../../..", "../../.."), ]; for (input, expected) in cases { let actual = normalize_path(std::path::Path::new(input)); assert_eq!(actual, std::path::Path::new(expected), "input: {input}"); } } #[test] fn write_works() { let original_contents = "[dependencies]\nfoo = 0.1.0"; let tmpdir = tempfile::tempdir().unwrap(); let path = tmpdir.path().join("Cargo.toml"); write(&path, original_contents).unwrap(); let contents = std::fs::read_to_string(&path).unwrap(); assert_eq!(contents, original_contents); } #[test] fn write_atomic_works() { let original_contents = "[dependencies]\nfoo = 0.1.0"; let tmpdir = tempfile::tempdir().unwrap(); let path = tmpdir.path().join("Cargo.toml"); write_atomic(&path, original_contents).unwrap(); let contents = std::fs::read_to_string(&path).unwrap(); assert_eq!(contents, original_contents); } #[test] #[cfg(unix)] fn write_atomic_permissions() { use std::os::unix::fs::PermissionsExt; let original_perms = std::fs::Permissions::from_mode(u32::from( libc::S_IRWXU | libc::S_IRGRP | libc::S_IWGRP | libc::S_IROTH, )); let tmp = tempfile::Builder::new().tempfile().unwrap(); // need to set the permissions after creating the file to avoid umask tmp.as_file() .set_permissions(original_perms.clone()) .unwrap(); // after this call, the file at `tmp.path()` will not be the same as the file held by `tmp` write_atomic(tmp.path(), "new").unwrap(); assert_eq!(std::fs::read_to_string(tmp.path()).unwrap(), "new"); let new_perms = std::fs::metadata(tmp.path()).unwrap().permissions(); let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO); assert_eq!(original_perms.mode(), new_perms.mode() & mask); } #[test] fn join_paths_lists_paths_on_error() { let valid_paths = vec!["/testing/one", "/testing/two"]; // does not fail on valid input let _joined = join_paths(&valid_paths, "TESTING1").unwrap(); #[cfg(unix)] { let invalid_paths = vec!["/testing/one", "/testing/t:wo/three"]; let err = join_paths(&invalid_paths, "TESTING2").unwrap_err(); assert_eq!( err.to_string(), "failed to join paths from `$TESTING2` together\n\n\ Check if any of path segments listed below contain an \ unterminated quote character or path separator:\ \n \"/testing/one\"\ \n \"/testing/t:wo/three\"\ " ); } #[cfg(windows)] { let invalid_paths = vec!["/testing/one", "/testing/t\"wo/three"]; let err = join_paths(&invalid_paths, "TESTING2").unwrap_err(); assert_eq!( err.to_string(), "failed to join paths from `$TESTING2` together\n\n\ Check if any of path segments listed below contain an \ unterminated quote character or path separator:\ \n \"/testing/one\"\ \n \"/testing/t\\\"wo/three\"\ " ); } } #[test] fn write_atomic_symlink() { let tmpdir = tempfile::tempdir().unwrap(); let target_path = tmpdir.path().join("target.txt"); let symlink_path = tmpdir.path().join("symlink.txt"); // Create initial file write(&target_path, "initial").unwrap(); // Create symlink #[cfg(unix)] std::os::unix::fs::symlink(&target_path, &symlink_path).unwrap(); #[cfg(windows)] std::os::windows::fs::symlink_file(&target_path, &symlink_path).unwrap(); // Write through symlink write_atomic(&symlink_path, "updated").unwrap(); // Verify both paths show the updated content assert_eq!(std::fs::read_to_string(&target_path).unwrap(), "updated"); assert_eq!(std::fs::read_to_string(&symlink_path).unwrap(), "updated"); // Verify symlink still exists and points to the same target assert!(symlink_path.is_symlink()); assert_eq!(std::fs::read_link(&symlink_path).unwrap(), target_path); } #[test] #[cfg(windows)] fn test_remove_symlink_dir() { use super::*; use std::fs; use std::os::windows::fs::symlink_dir; let tmpdir = tempfile::tempdir().unwrap(); let dir_path = tmpdir.path().join("testdir"); let symlink_path = tmpdir.path().join("symlink"); fs::create_dir(&dir_path).unwrap(); symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink"); assert!(symlink_path.exists()); assert!(remove_file(symlink_path.clone()).is_ok()); assert!(!symlink_path.exists()); assert!(dir_path.exists()); } #[test] #[cfg(windows)] fn test_remove_symlink_file() { use super::*; use std::fs; use std::os::windows::fs::symlink_file; let tmpdir = tempfile::tempdir().unwrap(); let file_path = tmpdir.path().join("testfile"); let symlink_path = tmpdir.path().join("symlink"); fs::write(&file_path, b"test").unwrap(); symlink_file(&file_path, &symlink_path).expect("failed to create symlink"); assert!(symlink_path.exists()); assert!(remove_file(symlink_path.clone()).is_ok()); assert!(!symlink_path.exists()); assert!(file_path.exists()); } } ================================================ FILE: crates/cargo-util/src/process_builder.rs ================================================ use crate::process_error::ProcessError; use crate::read2; use anyhow::{Context, Result, bail}; use jobserver::Client; use shell_escape::escape; use tempfile::NamedTempFile; use std::collections::BTreeMap; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt; use std::io::{self, Write}; use std::iter::once; use std::path::Path; use std::process::{Command, ExitStatus, Output, Stdio}; /// A builder object for an external process, similar to [`std::process::Command`]. #[derive(Clone, Debug)] pub struct ProcessBuilder { /// The program to execute. program: OsString, /// Best-effort replacement for arg0 arg0: Option, /// A list of arguments to pass to the program. args: Vec, /// Any environment variables that should be set for the program. env: BTreeMap>, /// The directory to run the program from. cwd: Option, /// A list of wrappers that wrap the original program when calling /// [`ProcessBuilder::wrapped`]. The last one is the outermost one. wrappers: Vec, /// The `make` jobserver. See the [jobserver crate] for /// more information. /// /// [jobserver crate]: https://docs.rs/jobserver/ jobserver: Option, /// `true` to include environment variable in display. display_env_vars: bool, /// `true` to retry with an argfile if hitting "command line too big" error. /// See [`ProcessBuilder::retry_with_argfile`] for more information. retry_with_argfile: bool, /// Data to write to stdin. stdin: Option>, } impl fmt::Display for ProcessBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "`")?; if self.display_env_vars { for (key, val) in self.env.iter() { if let Some(val) = val { let val = escape(val.to_string_lossy()); if cfg!(windows) { write!(f, "set {}={}&& ", key, val)?; } else { write!(f, "{}={} ", key, val)?; } } } } write!(f, "{}", self.get_program().to_string_lossy())?; for arg in self.get_args() { write!(f, " {}", escape(arg.to_string_lossy()))?; } write!(f, "`") } } impl ProcessBuilder { /// Creates a new [`ProcessBuilder`] with the given executable path. pub fn new>(cmd: T) -> ProcessBuilder { ProcessBuilder { program: cmd.as_ref().to_os_string(), arg0: None, args: Vec::new(), cwd: None, env: BTreeMap::new(), wrappers: Vec::new(), jobserver: None, display_env_vars: false, retry_with_argfile: false, stdin: None, } } /// (chainable) Sets the executable for the process. pub fn program>(&mut self, program: T) -> &mut ProcessBuilder { self.program = program.as_ref().to_os_string(); self } /// (chainable) Overrides `arg0` for this program. pub fn arg0>(&mut self, arg: T) -> &mut ProcessBuilder { self.arg0 = Some(arg.as_ref().to_os_string()); self } /// (chainable) Adds `arg` to the args list. pub fn arg>(&mut self, arg: T) -> &mut ProcessBuilder { self.args.push(arg.as_ref().to_os_string()); self } /// (chainable) Adds multiple `args` to the args list. pub fn args>(&mut self, args: &[T]) -> &mut ProcessBuilder { self.args .extend(args.iter().map(|t| t.as_ref().to_os_string())); self } /// (chainable) Replaces the args list with the given `args`. pub fn args_replace>(&mut self, args: &[T]) -> &mut ProcessBuilder { if let Some(program) = self.wrappers.pop() { // User intend to replace all args, so we // - use the outermost wrapper as the main program, and // - cleanup other inner wrappers. self.program = program; self.wrappers = Vec::new(); } self.args = args.iter().map(|t| t.as_ref().to_os_string()).collect(); self } /// (chainable) Sets the current working directory of the process. pub fn cwd>(&mut self, path: T) -> &mut ProcessBuilder { self.cwd = Some(path.as_ref().to_os_string()); self } /// (chainable) Sets an environment variable for the process. pub fn env>(&mut self, key: &str, val: T) -> &mut ProcessBuilder { self.env .insert(key.to_string(), Some(val.as_ref().to_os_string())); self } /// (chainable) Unsets an environment variable for the process. pub fn env_remove(&mut self, key: &str) -> &mut ProcessBuilder { self.env.insert(key.to_string(), None); self } /// Gets the executable name. pub fn get_program(&self) -> &OsString { self.wrappers.last().unwrap_or(&self.program) } /// Gets the program arg0. pub fn get_arg0(&self) -> Option<&OsStr> { self.arg0.as_deref() } /// Gets the program arguments. pub fn get_args(&self) -> impl Iterator { self.wrappers .iter() .rev() .chain(once(&self.program)) .chain(self.args.iter()) .skip(1) // Skip the main `program } /// Gets the current working directory for the process. pub fn get_cwd(&self) -> Option<&Path> { self.cwd.as_ref().map(Path::new) } /// Gets an environment variable as the process will see it (will inherit from environment /// unless explicitly unset). pub fn get_env(&self, var: &str) -> Option { self.env .get(var) .cloned() .or_else(|| Some(env::var_os(var))) .and_then(|s| s) } /// Gets all environment variables explicitly set or unset for the process (not inherited /// vars). pub fn get_envs(&self) -> &BTreeMap> { &self.env } /// Sets the `make` jobserver. See the [jobserver crate][jobserver_docs] for /// more information. /// /// [jobserver_docs]: https://docs.rs/jobserver/latest/jobserver/ pub fn inherit_jobserver(&mut self, jobserver: &Client) -> &mut Self { self.jobserver = Some(jobserver.clone()); self } /// Enables environment variable display. pub fn display_env_vars(&mut self) -> &mut Self { self.display_env_vars = true; self } /// Enables retrying with an argfile if hitting "command line too big" error /// /// This is primarily for the `@path` arg of rustc and rustdoc, which treat /// each line as an command-line argument, so `LF` and `CRLF` bytes are not /// valid as an argument for argfile at this moment. /// For example, `RUSTDOCFLAGS="--crate-version foo\nbar" cargo doc` is /// valid when invoking from command-line but not from argfile. /// /// To sum up, the limitations of the argfile are: /// /// - Must be valid UTF-8 encoded. /// - Must not contain any newlines in each argument. /// /// Ref: /// /// - /// - pub fn retry_with_argfile(&mut self, enabled: bool) -> &mut Self { self.retry_with_argfile = enabled; self } /// Sets a value that will be written to stdin of the process on launch. pub fn stdin>>(&mut self, stdin: T) -> &mut Self { self.stdin = Some(stdin.into()); self } fn should_retry_with_argfile(&self, err: &io::Error) -> bool { self.retry_with_argfile && imp::command_line_too_big(err) } /// Like [`Command::status`] but with a better error message. pub fn status(&self) -> Result { self._status() .with_context(|| ProcessError::could_not_execute(self)) } fn _status(&self) -> io::Result { if !debug_force_argfile(self.retry_with_argfile) { let mut cmd = self.build_command(); match cmd.spawn() { Err(ref e) if self.should_retry_with_argfile(e) => {} Err(e) => return Err(e), Ok(mut child) => return child.wait(), } } let (mut cmd, argfile) = self.build_command_with_argfile()?; let status = cmd.spawn()?.wait(); close_tempfile_and_log_error(argfile); status } /// Runs the process, waiting for completion, and mapping non-success exit codes to an error. pub fn exec(&self) -> Result<()> { let exit = self.status()?; if exit.success() { Ok(()) } else { Err(ProcessError::new( &format!("process didn't exit successfully: {}", self), Some(exit), None, ) .into()) } } /// Replaces the current process with the target process. /// /// On Unix, this executes the process using the Unix syscall `execvp`, which will block /// this process, and will only return if there is an error. /// /// On Windows this isn't technically possible. Instead we emulate it to the best of our /// ability. One aspect we fix here is that we specify a handler for the Ctrl-C handler. /// In doing so (and by effectively ignoring it) we should emulate proxying Ctrl-C /// handling to the application at hand, which will either terminate or handle it itself. /// According to Microsoft's documentation at /// . /// the Ctrl-C signal is sent to all processes attached to a terminal, which should /// include our child process. If the child terminates then we'll reap them in Cargo /// pretty quickly, and if the child handles the signal then we won't terminate /// (and we shouldn't!) until the process itself later exits. pub fn exec_replace(&self) -> Result<()> { imp::exec_replace(self) } /// Like [`Command::output`] but with a better error message. pub fn output(&self) -> Result { self._output() .with_context(|| ProcessError::could_not_execute(self)) } fn _output(&self) -> io::Result { if !debug_force_argfile(self.retry_with_argfile) { let mut cmd = self.build_command(); match piped(&mut cmd, self.stdin.is_some()).spawn() { Err(ref e) if self.should_retry_with_argfile(e) => {} Err(e) => return Err(e), Ok(mut child) => { if let Some(stdin) = &self.stdin { child.stdin.take().unwrap().write_all(stdin)?; } return child.wait_with_output(); } } } let (mut cmd, argfile) = self.build_command_with_argfile()?; let mut child = piped(&mut cmd, self.stdin.is_some()).spawn()?; if let Some(stdin) = &self.stdin { child.stdin.take().unwrap().write_all(stdin)?; } let output = child.wait_with_output(); close_tempfile_and_log_error(argfile); output } /// Executes the process, returning the stdio output, or an error if non-zero exit status. pub fn exec_with_output(&self) -> Result { let output = self.output()?; if output.status.success() { Ok(output) } else { Err(ProcessError::new( &format!("process didn't exit successfully: {}", self), Some(output.status), Some(&output), ) .into()) } } /// Executes a command, passing each line of stdout and stderr to the supplied callbacks, which /// can mutate the string data. /// /// If any invocations of these function return an error, it will be propagated. /// /// If `capture_output` is true, then all the output will also be buffered /// and stored in the returned `Output` object. If it is false, no caching /// is done, and the callbacks are solely responsible for handling the /// output. pub fn exec_with_streaming( &self, on_stdout_line: &mut dyn FnMut(&str) -> Result<()>, on_stderr_line: &mut dyn FnMut(&str) -> Result<()>, capture_output: bool, ) -> Result { let mut stdout = Vec::new(); let mut stderr = Vec::new(); let mut callback_error = None; let mut stdout_pos = 0; let mut stderr_pos = 0; let spawn = |mut cmd| { if !debug_force_argfile(self.retry_with_argfile) { match piped(&mut cmd, false).spawn() { Err(ref e) if self.should_retry_with_argfile(e) => {} Err(e) => return Err(e), Ok(child) => return Ok((child, None)), } } let (mut cmd, argfile) = self.build_command_with_argfile()?; Ok((piped(&mut cmd, false).spawn()?, Some(argfile))) }; let status = (|| { let cmd = self.build_command(); let (mut child, argfile) = spawn(cmd)?; let out = child.stdout.take().unwrap(); let err = child.stderr.take().unwrap(); read2(out, err, &mut |is_out, data, eof| { let pos = if is_out { &mut stdout_pos } else { &mut stderr_pos }; let idx = if eof { data.len() } else { match data[*pos..].iter().rposition(|b| *b == b'\n') { Some(i) => *pos + i + 1, None => { *pos = data.len(); return; } } }; let new_lines = &data[..idx]; for line in String::from_utf8_lossy(new_lines).lines() { if callback_error.is_some() { break; } let callback_result = if is_out { on_stdout_line(line) } else { on_stderr_line(line) }; if let Err(e) = callback_result { callback_error = Some(e); break; } } if capture_output { let dst = if is_out { &mut stdout } else { &mut stderr }; dst.extend(new_lines); } data.drain(..idx); *pos = 0; })?; let status = child.wait(); if let Some(argfile) = argfile { close_tempfile_and_log_error(argfile); } status })() .with_context(|| ProcessError::could_not_execute(self))?; let output = Output { status, stdout, stderr, }; { let to_print = if capture_output { Some(&output) } else { None }; if let Some(e) = callback_error { let cx = ProcessError::new( &format!("failed to parse process output: {}", self), Some(output.status), to_print, ); bail!(anyhow::Error::new(cx).context(e)); } else if !output.status.success() { bail!(ProcessError::new( &format!("process didn't exit successfully: {}", self), Some(output.status), to_print, )); } } Ok(output) } /// Builds the command with an `@` argfile that contains all the /// arguments. This is primarily served for rustc/rustdoc command family. fn build_command_with_argfile(&self) -> io::Result<(Command, NamedTempFile)> { use std::io::Write as _; let mut tmp = tempfile::Builder::new() .prefix("cargo-argfile.") .tempfile()?; let mut arg = OsString::from("@"); arg.push(tmp.path()); let mut cmd = self.build_command_without_args(); cmd.arg(arg); tracing::debug!("created argfile at {} for {self}", tmp.path().display()); let cap = self.get_args().map(|arg| arg.len() + 1).sum::(); let mut buf = Vec::with_capacity(cap); for arg in &self.args { let arg = arg.to_str().ok_or_else(|| { io::Error::new( io::ErrorKind::Other, format!( "argument for argfile contains invalid UTF-8 characters: `{}`", arg.to_string_lossy() ), ) })?; if arg.contains('\n') { return Err(io::Error::new( io::ErrorKind::Other, format!("argument for argfile contains newlines: `{arg}`"), )); } writeln!(buf, "{arg}")?; } tmp.write_all(&mut buf)?; Ok((cmd, tmp)) } /// Builds a command from `ProcessBuilder` for everything but not `args`. fn build_command_without_args(&self) -> Command { let mut command = { let mut iter = self.wrappers.iter().rev().chain(once(&self.program)); let mut cmd = Command::new(iter.next().expect("at least one `program` exists")); cmd.args(iter); cmd }; #[cfg(unix)] if let Some(arg0) = self.get_arg0() { use std::os::unix::process::CommandExt as _; command.arg0(arg0); } if let Some(cwd) = self.get_cwd() { command.current_dir(cwd); } for (k, v) in &self.env { match *v { Some(ref v) => { command.env(k, v); } None => { command.env_remove(k); } } } if let Some(ref c) = self.jobserver { c.configure(&mut command); } command } /// Converts `ProcessBuilder` into a `std::process::Command`, and handles /// the jobserver, if present. /// /// Note that this method doesn't take argfile fallback into account. The /// caller should handle it by themselves. pub fn build_command(&self) -> Command { let mut command = self.build_command_without_args(); for arg in &self.args { command.arg(arg); } command } /// Wraps an existing command with the provided wrapper, if it is present and valid. /// /// # Examples /// /// ```rust /// use cargo_util::ProcessBuilder; /// // Running this would execute `rustc` /// let cmd = ProcessBuilder::new("rustc"); /// /// // Running this will execute `sccache rustc` /// let cmd = cmd.wrapped(Some("sccache")); /// ``` pub fn wrapped(mut self, wrapper: Option>) -> Self { if let Some(wrapper) = wrapper.as_ref() { let wrapper = wrapper.as_ref(); if !wrapper.is_empty() { self.wrappers.push(wrapper.to_os_string()); } } self } } /// Forces the command to use `@path` argfile. /// /// You should set `__CARGO_TEST_FORCE_ARGFILE` to enable this. fn debug_force_argfile(retry_enabled: bool) -> bool { cfg!(debug_assertions) && env::var("__CARGO_TEST_FORCE_ARGFILE").is_ok() && retry_enabled } /// Creates new pipes for stderr, stdout, and optionally stdin. fn piped(cmd: &mut Command, pipe_stdin: bool) -> &mut Command { cmd.stdout(Stdio::piped()) .stderr(Stdio::piped()) .stdin(if pipe_stdin { Stdio::piped() } else { Stdio::null() }) } fn close_tempfile_and_log_error(file: NamedTempFile) { file.close().unwrap_or_else(|e| { tracing::warn!("failed to close temporary file: {e}"); }); } #[cfg(unix)] mod imp { use super::{ProcessBuilder, ProcessError, close_tempfile_and_log_error, debug_force_argfile}; use anyhow::Result; use std::io; use std::os::unix::process::CommandExt; pub fn exec_replace(process_builder: &ProcessBuilder) -> Result<()> { let mut error; let mut file = None; if debug_force_argfile(process_builder.retry_with_argfile) { let (mut command, argfile) = process_builder.build_command_with_argfile()?; file = Some(argfile); error = command.exec() } else { let mut command = process_builder.build_command(); error = command.exec(); if process_builder.should_retry_with_argfile(&error) { let (mut command, argfile) = process_builder.build_command_with_argfile()?; file = Some(argfile); error = command.exec() } } if let Some(file) = file { close_tempfile_and_log_error(file); } Err(anyhow::Error::from(error).context(ProcessError::new( &format!("could not execute process {}", process_builder), None, None, ))) } pub fn command_line_too_big(err: &io::Error) -> bool { err.raw_os_error() == Some(libc::E2BIG) } } #[cfg(windows)] mod imp { use super::{ProcessBuilder, ProcessError}; use anyhow::Result; use std::io; use windows_sys::Win32::Foundation::{FALSE, TRUE}; use windows_sys::Win32::System::Console::SetConsoleCtrlHandler; use windows_sys::core::BOOL; unsafe extern "system" fn ctrlc_handler(_: u32) -> BOOL { // Do nothing; let the child process handle it. TRUE } pub fn exec_replace(process_builder: &ProcessBuilder) -> Result<()> { unsafe { if SetConsoleCtrlHandler(Some(ctrlc_handler), TRUE) == FALSE { return Err(ProcessError::new("Could not set Ctrl-C handler.", None, None).into()); } } // Just execute the process as normal. process_builder.exec() } pub fn command_line_too_big(err: &io::Error) -> bool { use windows_sys::Win32::Foundation::ERROR_FILENAME_EXCED_RANGE; err.raw_os_error() == Some(ERROR_FILENAME_EXCED_RANGE as i32) } } #[cfg(test)] mod tests { use super::ProcessBuilder; use std::fs; #[test] fn argfile_build_succeeds() { let mut cmd = ProcessBuilder::new("echo"); cmd.args(["foo", "bar"].as_slice()); let (cmd, argfile) = cmd.build_command_with_argfile().unwrap(); assert_eq!(cmd.get_program(), "echo"); let cmd_args: Vec<_> = cmd.get_args().map(|s| s.to_str().unwrap()).collect(); assert_eq!(cmd_args.len(), 1); assert!(cmd_args[0].starts_with("@")); assert!(cmd_args[0].contains("cargo-argfile.")); let buf = fs::read_to_string(argfile.path()).unwrap(); assert_eq!(buf, "foo\nbar\n"); } #[test] fn argfile_build_fails_if_arg_contains_newline() { let mut cmd = ProcessBuilder::new("echo"); cmd.arg("foo\n"); let err = cmd.build_command_with_argfile().unwrap_err(); assert_eq!( err.to_string(), "argument for argfile contains newlines: `foo\n`" ); } #[test] fn argfile_build_fails_if_arg_contains_invalid_utf8() { let mut cmd = ProcessBuilder::new("echo"); #[cfg(windows)] let invalid_arg = { use std::os::windows::prelude::*; std::ffi::OsString::from_wide(&[0x0066, 0x006f, 0xD800, 0x006f]) }; #[cfg(unix)] let invalid_arg = { use std::os::unix::ffi::OsStrExt; std::ffi::OsStr::from_bytes(&[0x66, 0x6f, 0x80, 0x6f]).to_os_string() }; cmd.arg(invalid_arg); let err = cmd.build_command_with_argfile().unwrap_err(); assert_eq!( err.to_string(), "argument for argfile contains invalid UTF-8 characters: `fo�o`" ); } } ================================================ FILE: crates/cargo-util/src/process_error.rs ================================================ //! Error value for [`crate::ProcessBuilder`] when a process fails. use std::fmt; use std::process::{ExitStatus, Output}; use std::str; #[derive(Debug)] pub struct ProcessError { /// A detailed description to show to the user why the process failed. pub desc: String, /// The exit status of the process. /// /// This can be `None` if the process failed to launch (like process not /// found) or if the exit status wasn't a code but was instead something /// like termination via a signal. pub code: Option, /// The stdout from the process. /// /// This can be `None` if the process failed to launch, or the output was /// not captured. pub stdout: Option>, /// The stderr from the process. /// /// This can be `None` if the process failed to launch, or the output was /// not captured. pub stderr: Option>, } impl fmt::Display for ProcessError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.desc.fmt(f) } } impl std::error::Error for ProcessError {} impl ProcessError { /// Creates a new [`ProcessError`]. /// /// * `status` can be `None` if the process did not launch. /// * `output` can be `None` if the process did not launch, or output was not captured. pub fn new(msg: &str, status: Option, output: Option<&Output>) -> ProcessError { let exit = match status { Some(s) => exit_status_to_string(s), None => "never executed".to_string(), }; Self::new_raw( msg, status.and_then(|s| s.code()), &exit, output.map(|s| s.stdout.as_slice()), output.map(|s| s.stderr.as_slice()), ) } /// Creates a new [`ProcessError`] with the raw output data. /// /// * `code` can be `None` for situations like being killed by a signal on unix. pub fn new_raw( msg: &str, code: Option, status: &str, stdout: Option<&[u8]>, stderr: Option<&[u8]>, ) -> ProcessError { let mut desc = format!("{} ({})", msg, status); if let Some(out) = stdout { match str::from_utf8(out) { Ok(s) if !s.trim().is_empty() => { desc.push_str("\n--- stdout\n"); desc.push_str(s); } Ok(..) | Err(..) => {} } } if let Some(out) = stderr { match str::from_utf8(out) { Ok(s) if !s.trim().is_empty() => { desc.push_str("\n--- stderr\n"); desc.push_str(s); } Ok(..) | Err(..) => {} } } ProcessError { desc, code, stdout: stdout.map(|s| s.to_vec()), stderr: stderr.map(|s| s.to_vec()), } } /// Creates a [`ProcessError`] with "could not execute process {cmd}". /// /// * `cmd` is usually but not limited to [`std::process::Command`]. pub fn could_not_execute(cmd: impl fmt::Display) -> ProcessError { ProcessError::new(&format!("could not execute process {cmd}"), None, None) } } /// Converts an [`ExitStatus`] to a human-readable string suitable for /// displaying to a user. pub fn exit_status_to_string(status: ExitStatus) -> String { return status_to_string(status); #[cfg(unix)] fn status_to_string(status: ExitStatus) -> String { use std::os::unix::process::*; if let Some(signal) = status.signal() { let name = match signal as libc::c_int { libc::SIGABRT => ", SIGABRT: process abort signal", libc::SIGALRM => ", SIGALRM: alarm clock", libc::SIGFPE => ", SIGFPE: erroneous arithmetic operation", libc::SIGHUP => ", SIGHUP: hangup", libc::SIGILL => ", SIGILL: illegal instruction", libc::SIGINT => ", SIGINT: terminal interrupt signal", libc::SIGKILL => ", SIGKILL: kill", libc::SIGPIPE => ", SIGPIPE: write on a pipe with no one to read", libc::SIGQUIT => ", SIGQUIT: terminal quit signal", libc::SIGSEGV => ", SIGSEGV: invalid memory reference", libc::SIGTERM => ", SIGTERM: termination signal", libc::SIGBUS => ", SIGBUS: access to undefined memory", #[cfg(not(target_os = "haiku"))] libc::SIGSYS => ", SIGSYS: bad system call", libc::SIGTRAP => ", SIGTRAP: trace/breakpoint trap", _ => "", }; format!("signal: {}{}", signal, name) } else { status.to_string() } } #[cfg(windows)] fn status_to_string(status: ExitStatus) -> String { use windows_sys::Win32::Foundation::*; let mut base = status.to_string(); let extra = match status.code().unwrap() as i32 { STATUS_ACCESS_VIOLATION => "STATUS_ACCESS_VIOLATION", STATUS_IN_PAGE_ERROR => "STATUS_IN_PAGE_ERROR", STATUS_INVALID_HANDLE => "STATUS_INVALID_HANDLE", STATUS_INVALID_PARAMETER => "STATUS_INVALID_PARAMETER", STATUS_NO_MEMORY => "STATUS_NO_MEMORY", STATUS_ILLEGAL_INSTRUCTION => "STATUS_ILLEGAL_INSTRUCTION", STATUS_NONCONTINUABLE_EXCEPTION => "STATUS_NONCONTINUABLE_EXCEPTION", STATUS_INVALID_DISPOSITION => "STATUS_INVALID_DISPOSITION", STATUS_ARRAY_BOUNDS_EXCEEDED => "STATUS_ARRAY_BOUNDS_EXCEEDED", STATUS_FLOAT_DENORMAL_OPERAND => "STATUS_FLOAT_DENORMAL_OPERAND", STATUS_FLOAT_DIVIDE_BY_ZERO => "STATUS_FLOAT_DIVIDE_BY_ZERO", STATUS_FLOAT_INEXACT_RESULT => "STATUS_FLOAT_INEXACT_RESULT", STATUS_FLOAT_INVALID_OPERATION => "STATUS_FLOAT_INVALID_OPERATION", STATUS_FLOAT_OVERFLOW => "STATUS_FLOAT_OVERFLOW", STATUS_FLOAT_STACK_CHECK => "STATUS_FLOAT_STACK_CHECK", STATUS_FLOAT_UNDERFLOW => "STATUS_FLOAT_UNDERFLOW", STATUS_INTEGER_DIVIDE_BY_ZERO => "STATUS_INTEGER_DIVIDE_BY_ZERO", STATUS_INTEGER_OVERFLOW => "STATUS_INTEGER_OVERFLOW", STATUS_PRIVILEGED_INSTRUCTION => "STATUS_PRIVILEGED_INSTRUCTION", STATUS_STACK_OVERFLOW => "STATUS_STACK_OVERFLOW", STATUS_DLL_NOT_FOUND => "STATUS_DLL_NOT_FOUND", STATUS_ORDINAL_NOT_FOUND => "STATUS_ORDINAL_NOT_FOUND", STATUS_ENTRYPOINT_NOT_FOUND => "STATUS_ENTRYPOINT_NOT_FOUND", STATUS_CONTROL_C_EXIT => "STATUS_CONTROL_C_EXIT", STATUS_DLL_INIT_FAILED => "STATUS_DLL_INIT_FAILED", STATUS_FLOAT_MULTIPLE_FAULTS => "STATUS_FLOAT_MULTIPLE_FAULTS", STATUS_FLOAT_MULTIPLE_TRAPS => "STATUS_FLOAT_MULTIPLE_TRAPS", STATUS_REG_NAT_CONSUMPTION => "STATUS_REG_NAT_CONSUMPTION", STATUS_HEAP_CORRUPTION => "STATUS_HEAP_CORRUPTION", STATUS_STACK_BUFFER_OVERRUN => "STATUS_STACK_BUFFER_OVERRUN", STATUS_ASSERTION_FAILURE => "STATUS_ASSERTION_FAILURE", _ => return base, }; base.push_str(", "); base.push_str(extra); base } } /// Returns `true` if the given process exit code is something a normal /// process would exit with. /// /// This helps differentiate from abnormal termination codes, such as /// segmentation faults or signals. pub fn is_simple_exit_code(code: i32) -> bool { // Typical unix exit codes are 0 to 127. // Windows doesn't have anything "typical", and is a // 32-bit number (which appears signed here, but is really // unsigned). However, most of the interesting NTSTATUS // codes are very large. This is just a rough // approximation of which codes are "normal" and which // ones are abnormal termination. code >= 0 && code <= 127 } ================================================ FILE: crates/cargo-util/src/read2.rs ================================================ pub use self::imp::read2; #[cfg(unix)] mod imp { use libc::{F_GETFL, F_SETFL, O_NONBLOCK, c_int, fcntl}; use std::io; use std::io::prelude::*; use std::mem; use std::os::unix::prelude::*; use std::process::{ChildStderr, ChildStdout}; fn set_nonblock(fd: c_int) -> io::Result<()> { let flags = unsafe { fcntl(fd, F_GETFL) }; if flags == -1 || unsafe { fcntl(fd, F_SETFL, flags | O_NONBLOCK) } == -1 { return Err(io::Error::last_os_error()); } Ok(()) } pub fn read2( mut out_pipe: ChildStdout, mut err_pipe: ChildStderr, data: &mut dyn FnMut(bool, &mut Vec, bool), ) -> io::Result<()> { set_nonblock(out_pipe.as_raw_fd())?; set_nonblock(err_pipe.as_raw_fd())?; let mut out_done = false; let mut err_done = false; let mut out = Vec::new(); let mut err = Vec::new(); let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() }; fds[0].fd = out_pipe.as_raw_fd(); fds[0].events = libc::POLLIN; fds[1].fd = err_pipe.as_raw_fd(); fds[1].events = libc::POLLIN; let mut nfds = 2; let mut errfd = 1; while nfds > 0 { // wait for either pipe to become readable using `poll` let r = unsafe { libc::poll(fds.as_mut_ptr(), nfds, -1) }; if r == -1 { let err = io::Error::last_os_error(); if err.kind() == io::ErrorKind::Interrupted { continue; } return Err(err); } // Read as much as we can from each pipe, ignoring EWOULDBLOCK or // EAGAIN. If we hit EOF, then this will happen because the underlying // reader will return Ok(0), in which case we'll see `Ok` ourselves. In // this case we flip the other fd back into blocking mode and read // whatever's leftover on that file descriptor. let handle = |res: io::Result<_>| match res { Ok(_) => Ok(true), Err(e) => { if e.kind() == io::ErrorKind::WouldBlock { Ok(false) } else { Err(e) } } }; if !err_done && fds[errfd].revents != 0 && handle(err_pipe.read_to_end(&mut err))? { err_done = true; nfds -= 1; } data(false, &mut err, err_done); if !out_done && fds[0].revents != 0 && handle(out_pipe.read_to_end(&mut out))? { out_done = true; fds[0].fd = err_pipe.as_raw_fd(); errfd = 0; nfds -= 1; } data(true, &mut out, out_done); } Ok(()) } } #[cfg(windows)] mod imp { use std::io; use std::os::windows::prelude::*; use std::process::{ChildStderr, ChildStdout}; use std::slice; use miow::Overlapped; use miow::iocp::{CompletionPort, CompletionStatus}; use miow::pipe::NamedPipe; use windows_sys::Win32::Foundation::ERROR_BROKEN_PIPE; struct Pipe<'a> { dst: &'a mut Vec, overlapped: Overlapped, pipe: NamedPipe, done: bool, } pub fn read2( out_pipe: ChildStdout, err_pipe: ChildStderr, data: &mut dyn FnMut(bool, &mut Vec, bool), ) -> io::Result<()> { let mut out = Vec::new(); let mut err = Vec::new(); let port = CompletionPort::new(1)?; port.add_handle(0, &out_pipe)?; port.add_handle(1, &err_pipe)?; unsafe { let mut out_pipe = Pipe::new(out_pipe, &mut out); let mut err_pipe = Pipe::new(err_pipe, &mut err); out_pipe.read()?; err_pipe.read()?; let mut status = [CompletionStatus::zero(), CompletionStatus::zero()]; while !out_pipe.done || !err_pipe.done { for status in port.get_many(&mut status, None)? { if status.token() == 0 { out_pipe.complete(status); data(true, out_pipe.dst, out_pipe.done); out_pipe.read()?; } else { err_pipe.complete(status); data(false, err_pipe.dst, err_pipe.done); err_pipe.read()?; } } } Ok(()) } } impl<'a> Pipe<'a> { unsafe fn new(p: P, dst: &'a mut Vec) -> Pipe<'a> { // SAFETY: Handle must be owned, open, and closeable with CloseHandle. let pipe = unsafe { NamedPipe::from_raw_handle(p.into_raw_handle()) }; Pipe { dst, pipe, overlapped: Overlapped::zero(), done: false, } } unsafe fn read(&mut self) -> io::Result<()> { let dst = unsafe { slice_to_end(self.dst) }; // SAFETY: The buffer must be valid until the end of the I/O, // which is handled in `read2`. match unsafe { self.pipe.read_overlapped(dst, self.overlapped.raw()) } { Ok(_) => Ok(()), Err(e) => { if e.raw_os_error() == Some(ERROR_BROKEN_PIPE as i32) { self.done = true; Ok(()) } else { Err(e) } } } } unsafe fn complete(&mut self, status: &CompletionStatus) { let prev = self.dst.len(); unsafe { self.dst.set_len(prev + status.bytes_transferred() as usize) }; if status.bytes_transferred() == 0 { self.done = true; } } } unsafe fn slice_to_end(v: &mut Vec) -> &mut [u8] { if v.capacity() == 0 { v.reserve(16); } if v.capacity() == v.len() { v.reserve(1); } unsafe { slice::from_raw_parts_mut(v.as_mut_ptr().add(v.len()), v.capacity() - v.len()) } } } ================================================ FILE: crates/cargo-util/src/registry.rs ================================================ /// Make a path to a dependency, which aligns to /// /// - [index from of Cargo's index on filesystem][1], and /// - [index from Crates.io][2]. /// ///
/// /// Note: For index files, `dep_name` must have had `to_lowercase` called on it. /// ///
/// /// [1]: https://docs.rs/cargo/latest/cargo/sources/registry/index.html#the-format-of-the-index /// [2]: https://github.com/rust-lang/crates.io-index pub fn make_dep_path(dep_name: &str, prefix_only: bool) -> String { let (slash, name) = if prefix_only { ("", "") } else { ("/", dep_name) }; match dep_name.len() { 1 => format!("1{}{}", slash, name), 2 => format!("2{}{}", slash, name), 3 => format!("3/{}{}{}", &dep_name[..1], slash, name), _ => format!("{}/{}{}{}", &dep_name[0..2], &dep_name[2..4], slash, name), } } #[cfg(test)] mod tests { use super::make_dep_path; #[test] fn prefix_only() { assert_eq!(make_dep_path("a", true), "1"); assert_eq!(make_dep_path("ab", true), "2"); assert_eq!(make_dep_path("abc", true), "3/a"); assert_eq!(make_dep_path("Abc", true), "3/A"); assert_eq!(make_dep_path("AbCd", true), "Ab/Cd"); assert_eq!(make_dep_path("aBcDe", true), "aB/cD"); } #[test] fn full() { assert_eq!(make_dep_path("a", false), "1/a"); assert_eq!(make_dep_path("ab", false), "2/ab"); assert_eq!(make_dep_path("abc", false), "3/a/abc"); assert_eq!(make_dep_path("Abc", false), "3/A/Abc"); assert_eq!(make_dep_path("AbCd", false), "Ab/Cd/AbCd"); assert_eq!(make_dep_path("aBcDe", false), "aB/cD/aBcDe"); } } ================================================ FILE: crates/cargo-util/src/sha256.rs ================================================ use super::paths; use anyhow::{Context, Result}; use sha2::{Digest, Sha256 as Sha2_sha256}; use std::fs::File; use std::io::{self, Read}; use std::path::Path; pub struct Sha256(Sha2_sha256); impl Sha256 { pub fn new() -> Sha256 { let hasher = Sha2_sha256::new(); Sha256(hasher) } pub fn update(&mut self, bytes: &[u8]) -> &mut Sha256 { let _ = self.0.update(bytes); self } pub fn update_file(&mut self, mut file: &File) -> io::Result<&mut Sha256> { let mut buf = [0; 64 * 1024]; loop { let n = file.read(&mut buf)?; if n == 0 { break Ok(self); } self.update(&buf[..n]); } } pub fn update_path>(&mut self, path: P) -> Result<&mut Sha256> { let path = path.as_ref(); let file = paths::open(path)?; self.update_file(&file) .with_context(|| format!("failed to read `{}`", path.display()))?; Ok(self) } pub fn finish(&mut self) -> [u8; 32] { self.0.finalize_reset().into() } pub fn finish_hex(&mut self) -> String { hex::encode(self.finish()) } } impl Default for Sha256 { fn default() -> Self { Self::new() } } ================================================ FILE: crates/cargo-util-schemas/Cargo.toml ================================================ [package] name = "cargo-util-schemas" version = "0.13.1" rust-version = "1.94" # MSRV:1 edition.workspace = true license.workspace = true repository.workspace = true description = "Deserialization schemas for Cargo" [dependencies] jiff = { workspace = true, features = ["std", "serde"] } schemars = { workspace = true, features = ["preserve_order", "semver1"], optional = true } semver.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, optional = true } serde-untagged.workspace = true serde-value.workspace = true thiserror.workspace = true toml = { workspace = true, features = ["serde"] } unicode-ident.workspace = true url.workspace = true [lints] workspace = true [dev-dependencies] snapbox.workspace = true serde_json.workspace = true [features] unstable-schema = ["dep:schemars", "dep:serde_json"] ================================================ FILE: crates/cargo-util-schemas/README.md ================================================ > This crate is maintained by the Cargo team for use by the wider > ecosystem. This crate follows semver compatibility for its APIs. ================================================ FILE: crates/cargo-util-schemas/index.schema.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "IndexPackage", "description": "A single line in the index representing a single version of a package.", "type": "object", "properties": { "name": { "description": "Name of the package.", "type": "string" }, "vers": { "description": "The version of this dependency.", "$ref": "#/$defs/SemVer" }, "deps": { "description": "All kinds of direct dependencies of the package, including dev and\nbuild dependencies.", "type": "array", "items": { "$ref": "#/$defs/RegistryDependency" } }, "features": { "description": "Set of features defined for the package, i.e., `[features]` table.", "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } }, "default": {} }, "features2": { "description": "This field contains features with new, extended syntax. Specifically,\nnamespaced features (`dep:`) and weak dependencies (`pkg?/feat`).\n\nThis is separated from `features` because versions older than 1.19\nwill fail to load due to not being able to parse the new syntax, even\nwith a `Cargo.lock` file.", "type": [ "object", "null" ], "additionalProperties": { "type": "array", "items": { "type": "string" } } }, "cksum": { "description": "Checksum for verifying the integrity of the corresponding downloaded package.", "type": "string" }, "yanked": { "description": "If `true`, Cargo will skip this version when resolving.\n\nThis was added in 2014. Everything in the crates.io index has this set\nnow, so this probably doesn't need to be an option anymore.", "type": [ "boolean", "null" ] }, "links": { "description": "Native library name this package links to.\n\nAdded early 2018 (see ),\ncan be `None` if published before then.", "type": [ "string", "null" ] }, "rust_version": { "description": "Required version of rust\n\nCorresponds to `package.rust-version`.\n\nAdded in 2023 (see ),\ncan be `None` if published before then or if not set in the manifest.", "type": [ "string", "null" ] }, "pubtime": { "description": "The publish time of this package version (optional).\n\nThe format is a subset of ISO8601:\n- `yyyy-mm-ddThh:mm:ssZ`\n- no fractional seconds\n- always `Z` for UTC timezone, no timezone offsets supported\n- fields are 0-padded\n\nExample: 2025-11-12T19:30:12Z\n\nThis should be the original publish time and not changed on any status changes,\nlike [`IndexPackage::yanked`].", "type": [ "string", "null" ], "default": null }, "v": { "description": "The schema version for this entry.\n\nIf this is None, it defaults to version `1`. Entries with unknown\nversions are ignored.\n\nVersion `2` schema adds the `features2` field.\n\nVersion `3` schema adds `artifact`, `bindep_targes`, and `lib` for\nartifact dependencies support.\n\nThis provides a method to safely introduce changes to index entries\nand allow older versions of cargo to ignore newer entries it doesn't\nunderstand. This is honored as of 1.51, so unfortunately older\nversions will ignore it, and potentially misinterpret version 2 and\nnewer entries.\n\nThe intent is that versions older than 1.51 will work with a\npre-existing `Cargo.lock`, but they may not correctly process `cargo\nupdate` or build a lock from scratch. In that case, cargo may\nincorrectly select a new package that uses a new index schema. A\nworkaround is to downgrade any packages that are incompatible with the\n`--precise` flag of `cargo update`.", "type": [ "integer", "null" ], "format": "uint32", "minimum": 0 } }, "required": [ "name", "vers", "deps", "cksum" ], "$defs": { "SemVer": { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" }, "RegistryDependency": { "description": "A dependency as encoded in the [`IndexPackage`] index JSON.", "type": "object", "properties": { "name": { "description": "Name of the dependency. If the dependency is renamed, the original\nwould be stored in [`RegistryDependency::package`].", "type": "string" }, "req": { "description": "The SemVer requirement for this dependency.", "type": "string" }, "features": { "description": "Set of features enabled for this dependency.", "type": "array", "items": { "type": "string" }, "default": [] }, "optional": { "description": "Whether or not this is an optional dependency.", "type": "boolean", "default": false }, "default_features": { "description": "Whether or not default features are enabled.", "type": "boolean", "default": true }, "target": { "description": "The target platform for this dependency.", "type": [ "string", "null" ] }, "kind": { "description": "The dependency kind. \"dev\", \"build\", and \"normal\".", "type": [ "string", "null" ] }, "registry": { "description": "The URL of the index of the registry where this dependency is from.\n`None` if it is from the same index.", "type": [ "string", "null" ] }, "package": { "description": "The original name if the dependency is renamed.", "type": [ "string", "null" ] }, "public": { "description": "Whether or not this is a public dependency. Unstable. See [RFC 1977].\n\n[RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html", "type": [ "boolean", "null" ] }, "artifact": { "description": "The artifacts to build from this dependency.", "type": [ "array", "null" ], "items": { "type": "string" } }, "bindep_target": { "description": "The target for bindep.", "type": [ "string", "null" ] }, "lib": { "description": "Whether or not this is a library dependency.", "type": "boolean", "default": false } }, "required": [ "name", "req" ] } } } ================================================ FILE: crates/cargo-util-schemas/lockfile.schema.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "TomlLockfile", "description": "Serialization of `Cargo.lock`", "type": "object", "properties": { "version": { "description": "The lockfile format version (`version =` field).\n\nThis field is optional for backward compatibility. Older lockfiles, i.e. V1 and V2, does\nnot have the version field serialized.", "type": [ "integer", "null" ], "format": "uint32", "minimum": 0 }, "package": { "description": "The list of `[[package]]` entries describing each resolved dependency.", "type": [ "array", "null" ], "items": { "$ref": "#/$defs/TomlLockfileDependency" } }, "root": { "description": "The `[root]` table describing the root package.\n\nThis field is optional for backward compatibility. Older lockfiles have the root package\nseparated, whereas newer lockfiles have the root package as part of `[[package]]`.", "anyOf": [ { "$ref": "#/$defs/TomlLockfileDependency" }, { "type": "null" } ] }, "metadata": { "description": "The `[metadata]` table\n\n\nIn older lockfile versions, dependency checksums were stored here instead of alongside each\npackage entry.", "type": [ "object", "null" ], "additionalProperties": { "type": "string" } }, "patch": { "description": "The `[patch]` table describing unused patches.\n\nThe lockfile stores them as a list of `[[patch.unused]]` entries.", "$ref": "#/$defs/TomlLockfilePatch" } }, "$defs": { "TomlLockfileDependency": { "description": "Serialization of lockfiles dependencies", "type": "object", "properties": { "name": { "description": "The name of the dependency.", "type": "string" }, "version": { "description": "The version of the dependency.", "type": "string" }, "source": { "description": "The source of the dependency.\n\nCargo does not serialize path dependencies.", "anyOf": [ { "$ref": "#/$defs/TomlLockfileSourceId" }, { "type": "null" } ] }, "checksum": { "description": "The checksum of the dependency.\n\nIn older lockfiles, checksums were not stored here and instead on a separate `[metadata]`\ntable (see [`TomlLockfileMetadata`]).", "type": [ "string", "null" ] }, "dependencies": { "description": "The transitive dependencies used by this dependency.", "type": [ "array", "null" ], "items": { "$ref": "#/$defs/TomlLockfilePackageId" } }, "replace": { "description": "The replace of the dependency.", "anyOf": [ { "$ref": "#/$defs/TomlLockfilePackageId" }, { "type": "null" } ] } }, "required": [ "name", "version" ] }, "TomlLockfileSourceId": { "description": "Serialization of dependency's source", "type": "string" }, "TomlLockfilePackageId": { "description": "Serialization of package IDs.\n\nThe version and source are only included when necessary to disambiguate between packages:\n- If multiple packages share the same name, the version is included.\n- If multiple packages share the same name and version, the source is included.", "type": "object", "properties": { "name": { "type": "string" }, "version": { "type": [ "string", "null" ] }, "source": { "anyOf": [ { "$ref": "#/$defs/TomlLockfileSourceId" }, { "type": "null" } ] } }, "required": [ "name" ] }, "TomlLockfilePatch": { "description": "Serialization of unused patches\n\nCargo stores patches that were declared but not used during resolution.", "type": "object", "properties": { "unused": { "description": "The list of unused dependency patches.", "type": "array", "items": { "$ref": "#/$defs/TomlLockfileDependency" } } }, "required": [ "unused" ] } } } ================================================ FILE: crates/cargo-util-schemas/manifest.schema.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "TomlManifest", "description": "This type is used to deserialize `Cargo.toml` files.", "type": "object", "properties": { "cargo-features": { "type": [ "array", "null" ], "items": { "type": "string" } }, "package": { "anyOf": [ { "$ref": "#/$defs/TomlPackage" }, { "type": "null" } ] }, "project": { "anyOf": [ { "$ref": "#/$defs/TomlPackage" }, { "type": "null" } ] }, "badges": { "type": [ "object", "null" ], "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "features": { "type": [ "object", "null" ], "additionalProperties": { "type": "array", "items": { "type": "string" } } }, "lib": { "anyOf": [ { "$ref": "#/$defs/TomlTarget" }, { "type": "null" } ] }, "bin": { "type": [ "array", "null" ], "items": { "$ref": "#/$defs/TomlTarget" } }, "example": { "type": [ "array", "null" ], "items": { "$ref": "#/$defs/TomlTarget" } }, "test": { "type": [ "array", "null" ], "items": { "$ref": "#/$defs/TomlTarget" } }, "bench": { "type": [ "array", "null" ], "items": { "$ref": "#/$defs/TomlTarget" } }, "dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "dev-dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "dev_dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "build-dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "build_dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "target": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/TomlPlatform" } }, "lints": { "anyOf": [ { "$ref": "#/$defs/InheritableLints" }, { "type": "null" } ] }, "hints": { "anyOf": [ { "$ref": "#/$defs/Hints" }, { "type": "null" } ] }, "workspace": { "anyOf": [ { "$ref": "#/$defs/TomlWorkspace" }, { "type": "null" } ] }, "profile": { "anyOf": [ { "$ref": "#/$defs/TomlProfiles" }, { "type": "null" } ] }, "patch": { "type": [ "object", "null" ], "additionalProperties": { "type": "object", "additionalProperties": { "$ref": "#/$defs/TomlDependency" } } }, "replace": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/TomlDependency" } } }, "$defs": { "TomlPackage": { "description": "Represents the `package`/`project` sections of a `Cargo.toml`.\n\nNote that the order of the fields matters, since this is the order they\nare serialized to a TOML file. For example, you cannot have values after\nthe field `metadata`, since it is a table and values cannot appear after\ntables.", "type": "object", "properties": { "edition": { "anyOf": [ { "$ref": "#/$defs/InheritableField" }, { "type": "null" } ] }, "rust-version": { "type": [ "string", "null" ] }, "name": { "type": [ "string", "null" ] }, "version": { "anyOf": [ { "$ref": "#/$defs/InheritableField2" }, { "type": "null" } ] }, "authors": { "anyOf": [ { "$ref": "#/$defs/InheritableField3" }, { "type": "null" } ] }, "build": { "anyOf": [ { "$ref": "#/$defs/TomlPackageBuild" }, { "type": "null" } ] }, "metabuild": { "anyOf": [ { "$ref": "#/$defs/StringOrVec" }, { "type": "null" } ] }, "default-target": { "type": [ "string", "null" ] }, "forced-target": { "type": [ "string", "null" ] }, "links": { "type": [ "string", "null" ] }, "exclude": { "anyOf": [ { "$ref": "#/$defs/InheritableField3" }, { "type": "null" } ] }, "include": { "anyOf": [ { "$ref": "#/$defs/InheritableField3" }, { "type": "null" } ] }, "publish": { "anyOf": [ { "$ref": "#/$defs/InheritableField4" }, { "type": "null" } ] }, "workspace": { "type": [ "string", "null" ] }, "im-a-teapot": { "type": [ "boolean", "null" ] }, "autolib": { "type": [ "boolean", "null" ] }, "autobins": { "type": [ "boolean", "null" ] }, "autoexamples": { "type": [ "boolean", "null" ] }, "autotests": { "type": [ "boolean", "null" ] }, "autobenches": { "type": [ "boolean", "null" ] }, "default-run": { "type": [ "string", "null" ] }, "description": { "anyOf": [ { "$ref": "#/$defs/InheritableField" }, { "type": "null" } ] }, "homepage": { "anyOf": [ { "$ref": "#/$defs/InheritableField" }, { "type": "null" } ] }, "documentation": { "anyOf": [ { "$ref": "#/$defs/InheritableField" }, { "type": "null" } ] }, "readme": { "anyOf": [ { "$ref": "#/$defs/InheritableField5" }, { "type": "null" } ] }, "keywords": { "anyOf": [ { "$ref": "#/$defs/InheritableField3" }, { "type": "null" } ] }, "categories": { "anyOf": [ { "$ref": "#/$defs/InheritableField3" }, { "type": "null" } ] }, "license": { "anyOf": [ { "$ref": "#/$defs/InheritableField" }, { "type": "null" } ] }, "license-file": { "anyOf": [ { "$ref": "#/$defs/InheritableField" }, { "type": "null" } ] }, "repository": { "anyOf": [ { "$ref": "#/$defs/InheritableField" }, { "type": "null" } ] }, "resolver": { "type": [ "string", "null" ] }, "metadata": { "anyOf": [ { "$ref": "#/$defs/TomlValue" }, { "type": "null" } ] } } }, "InheritableField": { "description": "An enum that allows for inheriting keys from a workspace in a Cargo.toml.", "anyOf": [ { "description": "The type that is used when not inheriting from a workspace.", "type": "string" }, { "description": "The type when inheriting from a workspace.", "$ref": "#/$defs/TomlInheritedField" } ] }, "TomlInheritedField": { "type": "object", "properties": { "workspace": { "$ref": "#/$defs/WorkspaceValue" } }, "required": [ "workspace" ] }, "WorkspaceValue": { "type": "boolean" }, "InheritableField2": { "description": "An enum that allows for inheriting keys from a workspace in a Cargo.toml.", "anyOf": [ { "description": "The type that is used when not inheriting from a workspace.", "$ref": "#/$defs/SemVer" }, { "description": "The type when inheriting from a workspace.", "$ref": "#/$defs/TomlInheritedField" } ] }, "SemVer": { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" }, "InheritableField3": { "description": "An enum that allows for inheriting keys from a workspace in a Cargo.toml.", "anyOf": [ { "description": "The type that is used when not inheriting from a workspace.", "type": "array", "items": { "type": "string" } }, { "description": "The type when inheriting from a workspace.", "$ref": "#/$defs/TomlInheritedField" } ] }, "TomlPackageBuild": { "anyOf": [ { "description": "If build scripts are disabled or enabled.\nIf true, `build.rs` in the root folder will be the build script.", "type": "boolean" }, { "description": "Path of Build Script if there's just one script.", "type": "string" }, { "description": "Vector of paths if multiple build script are to be used.", "type": "array", "items": { "type": "string" } } ] }, "StringOrVec": { "description": "This can be parsed from either a TOML string or array,\nbut is always stored as a vector.", "type": "array", "items": { "type": "string" } }, "InheritableField4": { "description": "An enum that allows for inheriting keys from a workspace in a Cargo.toml.", "anyOf": [ { "description": "The type that is used when not inheriting from a workspace.", "$ref": "#/$defs/VecStringOrBool" }, { "description": "The type when inheriting from a workspace.", "$ref": "#/$defs/TomlInheritedField" } ] }, "VecStringOrBool": { "anyOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "boolean" } ] }, "InheritableField5": { "description": "An enum that allows for inheriting keys from a workspace in a Cargo.toml.", "anyOf": [ { "description": "The type that is used when not inheriting from a workspace.", "$ref": "#/$defs/StringOrBool" }, { "description": "The type when inheriting from a workspace.", "$ref": "#/$defs/TomlInheritedField" } ] }, "StringOrBool": { "anyOf": [ { "type": "string" }, { "type": "boolean" } ] }, "TomlValue": true, "TomlTarget": { "type": "object", "properties": { "name": { "type": [ "string", "null" ] }, "crate-type": { "type": [ "array", "null" ], "items": { "type": "string" } }, "crate_type": { "type": [ "array", "null" ], "items": { "type": "string" } }, "path": { "type": [ "string", "null" ] }, "filename": { "type": [ "string", "null" ] }, "test": { "type": [ "boolean", "null" ] }, "doctest": { "type": [ "boolean", "null" ] }, "bench": { "type": [ "boolean", "null" ] }, "doc": { "type": [ "boolean", "null" ] }, "doc-scrape-examples": { "type": [ "boolean", "null" ] }, "proc-macro": { "type": [ "boolean", "null" ] }, "proc_macro": { "type": [ "boolean", "null" ] }, "harness": { "type": [ "boolean", "null" ] }, "required-features": { "type": [ "array", "null" ], "items": { "type": "string" } }, "edition": { "type": [ "string", "null" ] } } }, "InheritableDependency": { "anyOf": [ { "description": "The type that is used when not inheriting from a workspace.", "$ref": "#/$defs/TomlDependency" }, { "description": "The type when inheriting from a workspace.", "$ref": "#/$defs/TomlInheritedDependency" } ] }, "TomlDependency": { "anyOf": [ { "description": "In the simple format, only a version is specified, eg.\n`package = \"\"`", "type": "string" }, { "description": "The simple format is equivalent to a detailed dependency\nspecifying only a version, eg.\n`package = { version = \"\" }`", "$ref": "#/$defs/TomlDetailedDependency" } ] }, "TomlDetailedDependency": { "type": "object", "properties": { "version": { "type": [ "string", "null" ] }, "registry": { "type": [ "string", "null" ] }, "registry-index": { "description": "The URL of the `registry` field.\nThis is an internal implementation detail. When Cargo creates a\npackage, it replaces `registry` with `registry-index` so that the\nmanifest contains the correct URL. All users won't have the same\nregistry names configured, so Cargo can't rely on just the name for\ncrates published by other users.", "type": [ "string", "null" ] }, "path": { "type": [ "string", "null" ] }, "base": { "type": [ "string", "null" ] }, "git": { "type": [ "string", "null" ] }, "branch": { "type": [ "string", "null" ] }, "tag": { "type": [ "string", "null" ] }, "rev": { "type": [ "string", "null" ] }, "features": { "type": [ "array", "null" ], "items": { "type": "string" } }, "optional": { "type": [ "boolean", "null" ] }, "default-features": { "type": [ "boolean", "null" ] }, "default_features": { "type": [ "boolean", "null" ] }, "package": { "type": [ "string", "null" ] }, "public": { "type": [ "boolean", "null" ] }, "artifact": { "description": "One or more of `bin`, `cdylib`, `staticlib`, `bin:`.", "anyOf": [ { "$ref": "#/$defs/StringOrVec" }, { "type": "null" } ] }, "lib": { "description": "If set, the artifact should also be a dependency", "type": [ "boolean", "null" ] }, "target": { "description": "A platform name, like `x86_64-apple-darwin`", "type": [ "string", "null" ] } } }, "TomlInheritedDependency": { "type": "object", "properties": { "workspace": { "type": "boolean" }, "features": { "type": [ "array", "null" ], "items": { "type": "string" } }, "default-features": { "type": [ "boolean", "null" ] }, "default_features": { "type": [ "boolean", "null" ] }, "optional": { "type": [ "boolean", "null" ] }, "public": { "type": [ "boolean", "null" ] } }, "required": [ "workspace" ] }, "TomlPlatform": { "description": "Corresponds to a `target` entry, but `TomlTarget` is already used.", "type": "object", "properties": { "dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "build-dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "build_dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "dev-dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } }, "dev_dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/InheritableDependency" } } } }, "InheritableLints": { "type": "object", "properties": { "workspace": { "type": "boolean" } }, "additionalProperties": { "type": "object", "additionalProperties": { "$ref": "#/$defs/TomlLint" } } }, "TomlLint": { "anyOf": [ { "$ref": "#/$defs/TomlLintLevel" }, { "$ref": "#/$defs/TomlLintConfig" } ] }, "TomlLintLevel": { "type": "string", "enum": [ "forbid", "deny", "warn", "allow" ] }, "TomlLintConfig": { "type": "object", "properties": { "level": { "$ref": "#/$defs/TomlLintLevel" }, "priority": { "type": "integer", "format": "int8", "minimum": -128, "maximum": 127, "default": 0 } }, "required": [ "level" ], "additionalProperties": { "$ref": "#/$defs/TomlValue" } }, "Hints": { "type": "object", "properties": { "mostly-unused": { "anyOf": [ { "$ref": "#/$defs/TomlValue" }, { "type": "null" } ] } } }, "TomlWorkspace": { "type": "object", "properties": { "members": { "type": [ "array", "null" ], "items": { "type": "string" } }, "exclude": { "type": [ "array", "null" ], "items": { "type": "string" } }, "default-members": { "type": [ "array", "null" ], "items": { "type": "string" } }, "resolver": { "type": [ "string", "null" ] }, "metadata": { "anyOf": [ { "$ref": "#/$defs/TomlValue" }, { "type": "null" } ] }, "package": { "anyOf": [ { "$ref": "#/$defs/InheritablePackage" }, { "type": "null" } ] }, "dependencies": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/TomlDependency" } }, "lints": { "type": [ "object", "null" ], "additionalProperties": { "type": "object", "additionalProperties": { "$ref": "#/$defs/TomlLint" } } } } }, "InheritablePackage": { "description": "A group of fields that are inheritable by members of the workspace", "type": "object", "properties": { "version": { "anyOf": [ { "$ref": "#/$defs/SemVer" }, { "type": "null" } ] }, "authors": { "type": [ "array", "null" ], "items": { "type": "string" } }, "description": { "type": [ "string", "null" ] }, "homepage": { "type": [ "string", "null" ] }, "documentation": { "type": [ "string", "null" ] }, "readme": { "anyOf": [ { "$ref": "#/$defs/StringOrBool" }, { "type": "null" } ] }, "keywords": { "type": [ "array", "null" ], "items": { "type": "string" } }, "categories": { "type": [ "array", "null" ], "items": { "type": "string" } }, "license": { "type": [ "string", "null" ] }, "license-file": { "type": [ "string", "null" ] }, "repository": { "type": [ "string", "null" ] }, "publish": { "anyOf": [ { "$ref": "#/$defs/VecStringOrBool" }, { "type": "null" } ] }, "edition": { "type": [ "string", "null" ] }, "badges": { "type": [ "object", "null" ], "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "exclude": { "type": [ "array", "null" ], "items": { "type": "string" } }, "include": { "type": [ "array", "null" ], "items": { "type": "string" } }, "rust-version": { "type": [ "string", "null" ] } } }, "TomlProfiles": { "type": "object", "additionalProperties": { "$ref": "#/$defs/TomlProfile" } }, "TomlProfile": { "type": "object", "properties": { "opt-level": { "anyOf": [ { "$ref": "#/$defs/TomlOptLevel" }, { "type": "null" } ], "default": null }, "lto": { "anyOf": [ { "$ref": "#/$defs/StringOrBool" }, { "type": "null" } ], "default": null }, "codegen-backend": { "type": [ "string", "null" ], "default": null }, "codegen-units": { "type": [ "integer", "null" ], "format": "uint32", "minimum": 0, "default": null }, "debug": { "anyOf": [ { "$ref": "#/$defs/TomlDebugInfo" }, { "type": "null" } ], "default": null }, "split-debuginfo": { "type": [ "string", "null" ], "default": null }, "debug-assertions": { "type": [ "boolean", "null" ], "default": null }, "rpath": { "type": [ "boolean", "null" ], "default": null }, "panic": { "type": [ "string", "null" ], "default": null }, "overflow-checks": { "type": [ "boolean", "null" ], "default": null }, "incremental": { "type": [ "boolean", "null" ], "default": null }, "dir-name": { "type": [ "string", "null" ], "default": null }, "inherits": { "type": [ "string", "null" ], "default": null }, "strip": { "anyOf": [ { "$ref": "#/$defs/StringOrBool" }, { "type": "null" } ], "default": null }, "rustflags": { "type": [ "array", "null" ], "items": { "type": "string" }, "default": null }, "package": { "type": [ "object", "null" ], "additionalProperties": { "$ref": "#/$defs/TomlProfile" }, "default": null }, "build-override": { "anyOf": [ { "$ref": "#/$defs/TomlProfile" }, { "type": "null" } ], "default": null }, "trim-paths": { "description": "Unstable feature `-Ztrim-paths`.", "anyOf": [ { "$ref": "#/$defs/TomlTrimPaths" }, { "type": "null" } ], "default": null }, "hint-mostly-unused": { "description": "Unstable feature `hint-mostly-unused`", "type": [ "boolean", "null" ], "default": null } } }, "TomlOptLevel": { "type": "string" }, "TomlDebugInfo": { "type": "string", "enum": [ "None", "LineDirectivesOnly", "LineTablesOnly", "Limited", "Full" ] }, "PackageIdSpec": { "type": "string" }, "TomlTrimPaths": { "anyOf": [ { "type": "array", "items": { "$ref": "#/$defs/TomlTrimPathsValue" } }, { "type": "null" } ] }, "TomlTrimPathsValue": { "type": "string", "enum": [ "diagnostics", "macro", "object" ] } } } ================================================ FILE: crates/cargo-util-schemas/src/core/mod.rs ================================================ mod package_id_spec; mod partial_version; mod source_kind; pub use package_id_spec::PackageIdSpec; pub use package_id_spec::PackageIdSpecError; pub use partial_version::PartialVersion; pub use partial_version::PartialVersionError; pub use source_kind::GitReference; pub use source_kind::SourceKind; ================================================ FILE: crates/cargo-util-schemas/src/core/package_id_spec.rs ================================================ use std::fmt; use semver::Version; use serde::{de, ser}; use url::Url; use crate::core::GitReference; use crate::core::PartialVersion; use crate::core::PartialVersionError; use crate::core::SourceKind; use crate::manifest::PackageName; use crate::restricted_names::NameValidationError; type Result = std::result::Result; /// Some or all of the data required to identify a package: /// /// 1. the package name (a `String`, required) /// 2. the package version (a `Version`, optional) /// 3. the package source (a `Url`, optional) /// /// If any of the optional fields are omitted, then the package ID may be ambiguous, there may be /// more than one package/version/url combo that will match. However, often just the name is /// sufficient to uniquely define a package ID. #[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct PackageIdSpec { name: String, version: Option, url: Option, kind: Option, } impl PackageIdSpec { pub fn new(name: String) -> Self { Self { name, version: None, url: None, kind: None, } } pub fn with_version(mut self, version: PartialVersion) -> Self { self.version = Some(version); self } pub fn with_url(mut self, url: Url) -> Self { self.url = Some(url); self } pub fn with_kind(mut self, kind: SourceKind) -> Self { self.kind = Some(kind); self } /// Parses a spec string and returns a `PackageIdSpec` if the string was valid. /// /// # Examples /// Some examples of valid strings /// /// ``` /// use cargo_util_schemas::core::PackageIdSpec; /// /// let specs = vec![ /// "foo", /// "foo@1.4", /// "foo@1.4.3", /// "foo:1.2.3", /// "https://github.com/rust-lang/crates.io-index#foo", /// "https://github.com/rust-lang/crates.io-index#foo@1.4.3", /// "ssh://git@github.com/rust-lang/foo.git#foo@1.4.3", /// "file:///path/to/my/project/foo", /// "file:///path/to/my/project/foo#1.1.8" /// ]; /// for spec in specs { /// assert!(PackageIdSpec::parse(spec).is_ok()); /// } pub fn parse(spec: &str) -> Result { if spec.contains("://") { if let Ok(url) = Url::parse(spec) { return PackageIdSpec::from_url(url); } } else if spec.contains('/') || spec.contains('\\') { let abs = std::env::current_dir().unwrap_or_default().join(spec); if abs.exists() { let maybe_url = Url::from_file_path(abs) .map_or_else(|_| "a file:// URL".to_string(), |url| url.to_string()); return Err(ErrorKind::MaybeFilePath { spec: spec.into(), maybe_url, } .into()); } } let (name, version) = parse_spec(spec)?.unwrap_or_else(|| (spec.to_owned(), None)); PackageName::new(&name)?; Ok(PackageIdSpec { name: String::from(name), version, url: None, kind: None, }) } /// Tries to convert a valid `Url` to a `PackageIdSpec`. fn from_url(mut url: Url) -> Result { let mut kind = None; if let Some((kind_str, scheme)) = url.scheme().split_once('+') { match kind_str { "git" => { let git_ref = GitReference::from_query(url.query_pairs()); url.set_query(None); kind = Some(SourceKind::Git(git_ref)); url = strip_url_protocol(&url); } "registry" => { if url.query().is_some() { return Err(ErrorKind::UnexpectedQueryString(url).into()); } kind = Some(SourceKind::Registry); url = strip_url_protocol(&url); } "sparse" => { if url.query().is_some() { return Err(ErrorKind::UnexpectedQueryString(url).into()); } kind = Some(SourceKind::SparseRegistry); // Leave `sparse` as part of URL, see `SourceId::new` // url = strip_url_protocol(&url); } "path" => { if url.query().is_some() { return Err(ErrorKind::UnexpectedQueryString(url).into()); } if scheme != "file" { return Err(ErrorKind::UnsupportedPathPlusScheme(scheme.into()).into()); } kind = Some(SourceKind::Path); url = strip_url_protocol(&url); } kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()), } } else { if url.query().is_some() { return Err(ErrorKind::UnexpectedQueryString(url).into()); } } let frag = url.fragment().map(|s| s.to_owned()); url.set_fragment(None); let (name, version) = { let Some(path_name) = url.path_segments().and_then(|mut p| p.next_back()) else { return Err(ErrorKind::MissingUrlPath(url).into()); }; match frag { Some(fragment) => match parse_spec(&fragment)? { Some((name, ver)) => (name, ver), None => { let Some(f) = fragment.chars().next() else { return Err(PackageIdSpecError(ErrorKind::EmptyFragment)); }; if f.is_alphabetic() { (String::from(fragment.as_str()), None) } else { let version = fragment.parse::()?; (String::from(path_name), Some(version)) } } }, None => (String::from(path_name), None), } }; PackageName::new(&name)?; Ok(PackageIdSpec { name, version, url: Some(url), kind, }) } pub fn name(&self) -> &str { self.name.as_str() } /// Full `semver::Version`, if present pub fn version(&self) -> Option { self.version.as_ref().and_then(|v| v.to_version()) } pub fn partial_version(&self) -> Option<&PartialVersion> { self.version.as_ref() } pub fn url(&self) -> Option<&Url> { self.url.as_ref() } pub fn set_url(&mut self, url: Url) { self.url = Some(url); } pub fn kind(&self) -> Option<&SourceKind> { self.kind.as_ref() } pub fn set_kind(&mut self, kind: SourceKind) { self.kind = Some(kind); } } fn parse_spec(spec: &str) -> Result)>> { let Some((name, ver)) = spec .rsplit_once('@') .or_else(|| spec.rsplit_once(':').filter(|(n, _)| !n.ends_with(':'))) else { return Ok(None); }; let name = name.to_owned(); let ver = ver.parse::()?; Ok(Some((name, Some(ver)))) } fn strip_url_protocol(url: &Url) -> Url { // Ridiculous hoop because `Url::set_scheme` errors when changing to http/https let raw = url.to_string(); raw.split_once('+').unwrap().1.parse().unwrap() } impl fmt::Display for PackageIdSpec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut printed_name = false; match self.url { Some(ref url) => { if let Some(protocol) = self.kind.as_ref().and_then(|k| k.protocol()) { write!(f, "{protocol}+")?; } write!(f, "{}", url)?; if let Some(SourceKind::Git(git_ref)) = self.kind.as_ref() { if let Some(pretty) = git_ref.pretty_ref(true) { write!(f, "?{}", pretty)?; } } if url.path_segments().unwrap().next_back().unwrap() != &*self.name { printed_name = true; write!(f, "#{}", self.name)?; } } None => { printed_name = true; write!(f, "{}", self.name)?; } } if let Some(ref v) = self.version { write!(f, "{}{}", if printed_name { "@" } else { "#" }, v)?; } Ok(()) } } impl ser::Serialize for PackageIdSpec { fn serialize(&self, s: S) -> std::result::Result where S: ser::Serializer, { self.to_string().serialize(s) } } impl<'de> de::Deserialize<'de> for PackageIdSpec { fn deserialize(d: D) -> std::result::Result where D: de::Deserializer<'de>, { let string = String::deserialize(d)?; PackageIdSpec::parse(&string).map_err(de::Error::custom) } } #[cfg(feature = "unstable-schema")] impl schemars::JsonSchema for PackageIdSpec { fn schema_name() -> std::borrow::Cow<'static, str> { "PackageIdSpec".into() } fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { ::json_schema(generator) } } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct PackageIdSpecError(#[from] ErrorKind); impl From for PackageIdSpecError { fn from(value: PartialVersionError) -> Self { ErrorKind::PartialVersion(value).into() } } impl From for PackageIdSpecError { fn from(value: NameValidationError) -> Self { ErrorKind::NameValidation(value).into() } } /// Non-public error kind for [`PackageIdSpecError`]. #[non_exhaustive] #[derive(Debug, thiserror::Error)] enum ErrorKind { #[error("unsupported source protocol: {0}")] UnsupportedProtocol(String), #[error("`path+{0}` is unsupported; `path+file` and `file` schemes are supported")] UnsupportedPathPlusScheme(String), #[error("cannot have a query string in a pkgid: {0}")] UnexpectedQueryString(Url), #[error("pkgid urls must have at least one path component: {0}")] MissingUrlPath(Url), #[error("package ID specification `{spec}` looks like a file path, maybe try {maybe_url}")] MaybeFilePath { spec: String, maybe_url: String }, #[error("pkgid url cannot have an empty fragment")] EmptyFragment, #[error(transparent)] NameValidation(#[from] crate::restricted_names::NameValidationError), #[error(transparent)] PartialVersion(#[from] crate::core::PartialVersionError), } #[cfg(test)] mod tests { use super::ErrorKind; use super::PackageIdSpec; use crate::core::{GitReference, SourceKind}; use url::Url; #[track_caller] fn ok(spec: &str, expected: PackageIdSpec, expected_rendered: &str) { let parsed = PackageIdSpec::parse(spec).unwrap(); assert_eq!(parsed, expected); let rendered = parsed.to_string(); assert_eq!(rendered, expected_rendered); let reparsed = PackageIdSpec::parse(&rendered).unwrap(); assert_eq!(reparsed, expected); } macro_rules! err { ($spec:expr, $expected:pat) => { let err = PackageIdSpec::parse($spec).unwrap_err(); let kind = err.0; assert!( matches!(kind, $expected), "`{}` parse error mismatch, got {kind:?}", $spec ); }; } #[test] fn good_parsing() { ok( "https://crates.io/foo", PackageIdSpec { name: String::from("foo"), version: None, url: Some(Url::parse("https://crates.io/foo").unwrap()), kind: None, }, "https://crates.io/foo", ); ok( "https://crates.io/foo#1.2.3", PackageIdSpec { name: String::from("foo"), version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), kind: None, }, "https://crates.io/foo#1.2.3", ); ok( "https://crates.io/foo#1.2", PackageIdSpec { name: String::from("foo"), version: Some("1.2".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), kind: None, }, "https://crates.io/foo#1.2", ); ok( "https://crates.io/foo#bar:1.2.3", PackageIdSpec { name: String::from("bar"), version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), kind: None, }, "https://crates.io/foo#bar@1.2.3", ); ok( "https://crates.io/foo#bar@1.2.3", PackageIdSpec { name: String::from("bar"), version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), kind: None, }, "https://crates.io/foo#bar@1.2.3", ); ok( "https://crates.io/foo#bar@1.2", PackageIdSpec { name: String::from("bar"), version: Some("1.2".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), kind: None, }, "https://crates.io/foo#bar@1.2", ); ok( "registry+https://crates.io/foo#bar@1.2", PackageIdSpec { name: String::from("bar"), version: Some("1.2".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), kind: Some(SourceKind::Registry), }, "registry+https://crates.io/foo#bar@1.2", ); ok( "sparse+https://crates.io/foo#bar@1.2", PackageIdSpec { name: String::from("bar"), version: Some("1.2".parse().unwrap()), url: Some(Url::parse("sparse+https://crates.io/foo").unwrap()), kind: Some(SourceKind::SparseRegistry), }, "sparse+https://crates.io/foo#bar@1.2", ); ok( "foo", PackageIdSpec { name: String::from("foo"), version: None, url: None, kind: None, }, "foo", ); ok( "foo::bar", PackageIdSpec { name: String::from("foo::bar"), version: None, url: None, kind: None, }, "foo::bar", ); ok( "foo:1.2.3", PackageIdSpec { name: String::from("foo"), version: Some("1.2.3".parse().unwrap()), url: None, kind: None, }, "foo@1.2.3", ); ok( "foo::bar:1.2.3", PackageIdSpec { name: String::from("foo::bar"), version: Some("1.2.3".parse().unwrap()), url: None, kind: None, }, "foo::bar@1.2.3", ); ok( "foo@1.2.3", PackageIdSpec { name: String::from("foo"), version: Some("1.2.3".parse().unwrap()), url: None, kind: None, }, "foo@1.2.3", ); ok( "foo::bar@1.2.3", PackageIdSpec { name: String::from("foo::bar"), version: Some("1.2.3".parse().unwrap()), url: None, kind: None, }, "foo::bar@1.2.3", ); ok( "foo@1.2", PackageIdSpec { name: String::from("foo"), version: Some("1.2".parse().unwrap()), url: None, kind: None, }, "foo@1.2", ); // pkgid-spec.md ok( "regex", PackageIdSpec { name: String::from("regex"), version: None, url: None, kind: None, }, "regex", ); ok( "regex@1.4", PackageIdSpec { name: String::from("regex"), version: Some("1.4".parse().unwrap()), url: None, kind: None, }, "regex@1.4", ); ok( "regex@1.4.3", PackageIdSpec { name: String::from("regex"), version: Some("1.4.3".parse().unwrap()), url: None, kind: None, }, "regex@1.4.3", ); ok( "https://github.com/rust-lang/crates.io-index#regex", PackageIdSpec { name: String::from("regex"), version: None, url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()), kind: None, }, "https://github.com/rust-lang/crates.io-index#regex", ); ok( "https://github.com/rust-lang/crates.io-index#regex@1.4.3", PackageIdSpec { name: String::from("regex"), version: Some("1.4.3".parse().unwrap()), url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()), kind: None, }, "https://github.com/rust-lang/crates.io-index#regex@1.4.3", ); ok( "sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3", PackageIdSpec { name: String::from("regex"), version: Some("1.4.3".parse().unwrap()), url: Some( Url::parse("sparse+https://github.com/rust-lang/crates.io-index").unwrap(), ), kind: Some(SourceKind::SparseRegistry), }, "sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3", ); ok( "https://github.com/rust-lang/cargo#0.52.0", PackageIdSpec { name: String::from("cargo"), version: Some("0.52.0".parse().unwrap()), url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()), kind: None, }, "https://github.com/rust-lang/cargo#0.52.0", ); ok( "https://github.com/rust-lang/cargo#cargo-platform@0.1.2", PackageIdSpec { name: String::from("cargo-platform"), version: Some("0.1.2".parse().unwrap()), url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()), kind: None, }, "https://github.com/rust-lang/cargo#cargo-platform@0.1.2", ); ok( "ssh://git@github.com/rust-lang/regex.git#regex@1.4.3", PackageIdSpec { name: String::from("regex"), version: Some("1.4.3".parse().unwrap()), url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()), kind: None, }, "ssh://git@github.com/rust-lang/regex.git#regex@1.4.3", ); ok( "git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3", PackageIdSpec { name: String::from("regex"), version: Some("1.4.3".parse().unwrap()), url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()), kind: Some(SourceKind::Git(GitReference::DefaultBranch)), }, "git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3", ); ok( "git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3", PackageIdSpec { name: String::from("regex"), version: Some("1.4.3".parse().unwrap()), url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()), kind: Some(SourceKind::Git(GitReference::Branch("dev".to_owned()))), }, "git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3", ); ok( "file:///path/to/my/project/foo", PackageIdSpec { name: String::from("foo"), version: None, url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: None, }, "file:///path/to/my/project/foo", ); ok( "file:///path/to/my/project/foo::bar", PackageIdSpec { name: String::from("foo::bar"), version: None, url: Some(Url::parse("file:///path/to/my/project/foo::bar").unwrap()), kind: None, }, "file:///path/to/my/project/foo::bar", ); ok( "file:///path/to/my/project/foo#1.1.8", PackageIdSpec { name: String::from("foo"), version: Some("1.1.8".parse().unwrap()), url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: None, }, "file:///path/to/my/project/foo#1.1.8", ); ok( "path+file:///path/to/my/project/foo#1.1.8", PackageIdSpec { name: String::from("foo"), version: Some("1.1.8".parse().unwrap()), url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: Some(SourceKind::Path), }, "path+file:///path/to/my/project/foo#1.1.8", ); ok( "path+file:///path/to/my/project/foo#bar", PackageIdSpec { name: String::from("bar"), version: None, url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: Some(SourceKind::Path), }, "path+file:///path/to/my/project/foo#bar", ); ok( "path+file:///path/to/my/project/foo#foo::bar", PackageIdSpec { name: String::from("foo::bar"), version: None, url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: Some(SourceKind::Path), }, "path+file:///path/to/my/project/foo#foo::bar", ); ok( "path+file:///path/to/my/project/foo#bar:1.1.8", PackageIdSpec { name: String::from("bar"), version: Some("1.1.8".parse().unwrap()), url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: Some(SourceKind::Path), }, "path+file:///path/to/my/project/foo#bar@1.1.8", ); ok( "path+file:///path/to/my/project/foo#foo::bar:1.1.8", PackageIdSpec { name: String::from("foo::bar"), version: Some("1.1.8".parse().unwrap()), url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: Some(SourceKind::Path), }, "path+file:///path/to/my/project/foo#foo::bar@1.1.8", ); ok( "path+file:///path/to/my/project/foo#bar@1.1.8", PackageIdSpec { name: String::from("bar"), version: Some("1.1.8".parse().unwrap()), url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: Some(SourceKind::Path), }, "path+file:///path/to/my/project/foo#bar@1.1.8", ); ok( "path+file:///path/to/my/project/foo#foo::bar@1.1.8", PackageIdSpec { name: String::from("foo::bar"), version: Some("1.1.8".parse().unwrap()), url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), kind: Some(SourceKind::Path), }, "path+file:///path/to/my/project/foo#foo::bar@1.1.8", ); } #[test] fn bad_parsing() { err!("baz:", ErrorKind::PartialVersion(_)); err!("baz:*", ErrorKind::PartialVersion(_)); err!("baz@", ErrorKind::PartialVersion(_)); err!("baz@*", ErrorKind::PartialVersion(_)); err!("baz@^1.0", ErrorKind::PartialVersion(_)); err!("https://baz:1.0", ErrorKind::NameValidation(_)); err!("https://#baz:1.0", ErrorKind::NameValidation(_)); err!( "foobar+https://github.com/rust-lang/crates.io-index", ErrorKind::UnsupportedProtocol(_) ); err!( "path+https://github.com/rust-lang/crates.io-index", ErrorKind::UnsupportedPathPlusScheme(_) ); // Only `git+` can use `?` err!( "file:///path/to/my/project/foo?branch=dev", ErrorKind::UnexpectedQueryString(_) ); err!( "path+file:///path/to/my/project/foo?branch=dev", ErrorKind::UnexpectedQueryString(_) ); err!( "registry+https://github.com/rust-lang/cargo?branch=dev#0.52.0", ErrorKind::UnexpectedQueryString(_) ); err!( "sparse+https://github.com/rust-lang/cargo?branch=dev#0.52.0", ErrorKind::UnexpectedQueryString(_) ); err!("@1.2.3", ErrorKind::NameValidation(_)); err!("registry+https://github.com", ErrorKind::NameValidation(_)); err!("https://crates.io/1foo#1.2.3", ErrorKind::NameValidation(_)); err!("https://example.com/foo#", ErrorKind::EmptyFragment); } } ================================================ FILE: crates/cargo-util-schemas/src/core/partial_version.rs ================================================ use std::fmt::{self, Display}; use semver::{Comparator, Version, VersionReq}; use serde_untagged::UntaggedEnumVisitor; #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] pub struct PartialVersion { pub major: u64, pub minor: Option, pub patch: Option, pub pre: Option, pub build: Option, } impl PartialVersion { pub fn to_version(&self) -> Option { Some(Version { major: self.major, minor: self.minor?, patch: self.patch?, pre: self.pre.clone().unwrap_or_default(), build: self.build.clone().unwrap_or_default(), }) } pub fn to_caret_req(&self) -> VersionReq { VersionReq { comparators: vec![Comparator { op: semver::Op::Caret, major: self.major, minor: self.minor, patch: self.patch, pre: self.pre.as_ref().cloned().unwrap_or_default(), }], } } /// Check if this matches a version, including build metadata /// /// Build metadata does not affect version precedence but may be necessary for uniquely /// identifying a package. pub fn matches(&self, version: &Version) -> bool { if !version.pre.is_empty() && self.pre.is_none() { // Pre-release versions must be explicitly opted into, if for no other reason than to // give us room to figure out and define the semantics return false; } self.major == version.major && self.minor.map(|f| f == version.minor).unwrap_or(true) && self.patch.map(|f| f == version.patch).unwrap_or(true) && self.pre.as_ref().map(|f| f == &version.pre).unwrap_or(true) && self .build .as_ref() .map(|f| f == &version.build) .unwrap_or(true) } } impl From for PartialVersion { fn from(ver: semver::Version) -> Self { let pre = if ver.pre.is_empty() { None } else { Some(ver.pre) }; let build = if ver.build.is_empty() { None } else { Some(ver.build) }; Self { major: ver.major, minor: Some(ver.minor), patch: Some(ver.patch), pre, build, } } } impl std::str::FromStr for PartialVersion { type Err = PartialVersionError; fn from_str(value: &str) -> Result { match semver::Version::parse(value) { Ok(ver) => Ok(ver.into()), Err(_) => { // HACK: Leverage `VersionReq` for partial version parsing let mut version_req = match semver::VersionReq::parse(value) { Ok(req) => req, Err(_) if value.contains('-') => return Err(ErrorKind::Prerelease.into()), Err(_) if value.contains('+') => return Err(ErrorKind::BuildMetadata.into()), Err(_) => return Err(ErrorKind::Unexpected.into()), }; if version_req.comparators.len() != 1 { return Err(ErrorKind::VersionReq.into()); } let comp = version_req.comparators.pop().unwrap(); if comp.op != semver::Op::Caret { return Err(ErrorKind::VersionReq.into()); } else if value.starts_with('^') { // Can't distinguish between `^` present or not return Err(ErrorKind::VersionReq.into()); } let pre = if comp.pre.is_empty() { None } else { Some(comp.pre) }; Ok(Self { major: comp.major, minor: comp.minor, patch: comp.patch, pre, build: None, }) } } } } impl Display for PartialVersion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let major = self.major; write!(f, "{major}")?; if let Some(minor) = self.minor { write!(f, ".{minor}")?; } if let Some(patch) = self.patch { write!(f, ".{patch}")?; } if let Some(pre) = self.pre.as_ref() { write!(f, "-{pre}")?; } if let Some(build) = self.build.as_ref() { write!(f, "+{build}")?; } Ok(()) } } impl serde::Serialize for PartialVersion { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.collect_str(self) } } impl<'de> serde::Deserialize<'de> for PartialVersion { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { UntaggedEnumVisitor::new() .expecting("SemVer version") .string(|value| value.parse().map_err(serde::de::Error::custom)) .deserialize(deserializer) } } /// Error parsing a [`PartialVersion`]. #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct PartialVersionError(#[from] ErrorKind); /// Non-public error kind for [`PartialVersionError`]. #[non_exhaustive] #[derive(Debug, thiserror::Error)] enum ErrorKind { #[error("unexpected version requirement, expected a version like \"1.32\"")] VersionReq, #[error("unexpected prerelease field, expected a version like \"1.32\"")] Prerelease, #[error("unexpected build field, expected a version like \"1.32\"")] BuildMetadata, #[error("expected a version like \"1.32\"")] Unexpected, } #[cfg(test)] mod test { use super::*; use snapbox::prelude::*; use snapbox::str; #[test] fn parse_success() { let cases = &[ // Valid pre-release ("1.43.0-beta.1", str!["1.43.0-beta.1"]), // Valid pre-release with wildcard ("1.43.0-beta.1.x", str!["1.43.0-beta.1.x"]), ]; for (input, expected) in cases { let actual: Result = input.parse(); let actual = match actual { Ok(result) => result.to_string(), Err(err) => format!("didn't pass: {err}"), }; snapbox::assert_data_eq!(actual, expected.clone().raw()); } } #[test] fn parse_errors() { let cases = &[ // Disallow caret ( "^1.43", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), // Bad pre-release ( "1.43-beta.1", str![[r#"unexpected prerelease field, expected a version like "1.32""#]], ), // Weird wildcard ( "x", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), ( "1.x", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), ( "1.1.x", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), // Non-sense ("foodaddle", str![[r#"expected a version like "1.32""#]]), ]; for (input, expected) in cases { let actual: Result = input.parse(); let actual = match actual { Ok(result) => format!("didn't fail: {result:?}"), Err(err) => err.to_string(), }; snapbox::assert_data_eq!(actual, expected.clone().raw()); } } } ================================================ FILE: crates/cargo-util-schemas/src/core/source_kind.rs ================================================ use std::cmp::Ordering; /// The possible kinds of code source. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SourceKind { /// A git repository. Git(GitReference), /// A local path. Path, /// A remote registry. Registry, /// A sparse registry. SparseRegistry, /// A local filesystem-based registry. LocalRegistry, /// A directory-based registry. Directory, } // The hash here is important for what folder packages get downloaded into. // Changes trigger all users to download another copy of their crates. // So the `stable_hash` test checks that we only change it intentionally. // We implement hash manually to callout the stability impact. impl std::hash::Hash for SourceKind { fn hash(&self, state: &mut H) { core::mem::discriminant(self).hash(state); if let SourceKind::Git(git) = self { git.hash(state); } } } impl SourceKind { pub fn protocol(&self) -> Option<&str> { match self { SourceKind::Path => Some("path"), SourceKind::Git(_) => Some("git"), SourceKind::Registry => Some("registry"), // Sparse registry URL already includes the `sparse+` prefix, see `SourceId::new` SourceKind::SparseRegistry => None, SourceKind::LocalRegistry => Some("local-registry"), SourceKind::Directory => Some("directory"), } } } // The ordering here is important for how packages are serialized into lock files. // We implement it manually to callout the stability guarantee. // See https://github.com/rust-lang/cargo/pull/9397 for the history. impl Ord for SourceKind { fn cmp(&self, other: &SourceKind) -> Ordering { match (self, other) { (SourceKind::Path, SourceKind::Path) => Ordering::Equal, (SourceKind::Path, _) => Ordering::Less, (_, SourceKind::Path) => Ordering::Greater, (SourceKind::Registry, SourceKind::Registry) => Ordering::Equal, (SourceKind::Registry, _) => Ordering::Less, (_, SourceKind::Registry) => Ordering::Greater, (SourceKind::SparseRegistry, SourceKind::SparseRegistry) => Ordering::Equal, (SourceKind::SparseRegistry, _) => Ordering::Less, (_, SourceKind::SparseRegistry) => Ordering::Greater, (SourceKind::LocalRegistry, SourceKind::LocalRegistry) => Ordering::Equal, (SourceKind::LocalRegistry, _) => Ordering::Less, (_, SourceKind::LocalRegistry) => Ordering::Greater, (SourceKind::Directory, SourceKind::Directory) => Ordering::Equal, (SourceKind::Directory, _) => Ordering::Less, (_, SourceKind::Directory) => Ordering::Greater, (SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b), } } } /// Forwards to `Ord` impl PartialOrd for SourceKind { fn partial_cmp(&self, other: &SourceKind) -> Option { Some(self.cmp(other)) } } /// Information to find a specific commit in a Git repository. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum GitReference { /// From a tag. Tag(String), /// From a branch. Branch(String), /// From a specific revision. Can be a commit hash (either short or full), /// or a named reference like `refs/pull/493/head`. Rev(String), /// The default branch of the repository, the reference named `HEAD`. DefaultBranch, } impl GitReference { pub fn from_query( query_pairs: impl Iterator, impl AsRef)>, ) -> Self { let mut reference = GitReference::DefaultBranch; for (k, v) in query_pairs { let v = v.as_ref(); match k.as_ref() { // Map older 'ref' to branch. "branch" | "ref" => reference = GitReference::Branch(v.to_owned()), "rev" => reference = GitReference::Rev(v.to_owned()), "tag" => reference = GitReference::Tag(v.to_owned()), _ => {} } } reference } /// Returns a `Display`able view of this git reference, or None if using /// the head of the default branch pub fn pretty_ref(&self, url_encoded: bool) -> Option> { match self { GitReference::DefaultBranch => None, _ => Some(PrettyRef { inner: self, url_encoded, }), } } } /// A git reference that can be `Display`ed pub struct PrettyRef<'a> { inner: &'a GitReference, url_encoded: bool, } impl<'a> std::fmt::Display for PrettyRef<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let value: &str; match self.inner { GitReference::Branch(s) => { write!(f, "branch=")?; value = s; } GitReference::Tag(s) => { write!(f, "tag=")?; value = s; } GitReference::Rev(s) => { write!(f, "rev=")?; value = s; } GitReference::DefaultBranch => unreachable!(), } if self.url_encoded { for value in url::form_urlencoded::byte_serialize(value.as_bytes()) { write!(f, "{value}")?; } } else { write!(f, "{value}")?; } Ok(()) } } ================================================ FILE: crates/cargo-util-schemas/src/index.rs ================================================ use crate::manifest::RustVersion; use semver::Version; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, collections::BTreeMap}; /// A single line in the index representing a single version of a package. #[derive(Deserialize, Serialize)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct IndexPackage<'a> { /// Name of the package. #[serde(borrow)] pub name: Cow<'a, str>, /// The version of this dependency. pub vers: Version, /// All kinds of direct dependencies of the package, including dev and /// build dependencies. #[serde(borrow)] pub deps: Vec>, /// Set of features defined for the package, i.e., `[features]` table. #[serde(default)] pub features: BTreeMap, Vec>>, /// This field contains features with new, extended syntax. Specifically, /// namespaced features (`dep:`) and weak dependencies (`pkg?/feat`). /// /// This is separated from `features` because versions older than 1.19 /// will fail to load due to not being able to parse the new syntax, even /// with a `Cargo.lock` file. pub features2: Option, Vec>>>, /// Checksum for verifying the integrity of the corresponding downloaded package. pub cksum: String, /// If `true`, Cargo will skip this version when resolving. /// /// This was added in 2014. Everything in the crates.io index has this set /// now, so this probably doesn't need to be an option anymore. pub yanked: Option, /// Native library name this package links to. /// /// Added early 2018 (see ), /// can be `None` if published before then. pub links: Option>, /// Required version of rust /// /// Corresponds to `package.rust-version`. /// /// Added in 2023 (see ), /// can be `None` if published before then or if not set in the manifest. #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub rust_version: Option, /// The publish time of this package version (optional). /// /// The format is a subset of ISO8601: /// - `yyyy-mm-ddThh:mm:ssZ` /// - no fractional seconds /// - always `Z` for UTC timezone, no timezone offsets supported /// - fields are 0-padded /// /// Example: 2025-11-12T19:30:12Z /// /// This should be the original publish time and not changed on any status changes, /// like [`IndexPackage::yanked`]. #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] #[serde(with = "serde_pubtime")] #[serde(default)] pub pubtime: Option, /// The schema version for this entry. /// /// If this is None, it defaults to version `1`. Entries with unknown /// versions are ignored. /// /// Version `2` schema adds the `features2` field. /// /// Version `3` schema adds `artifact`, `bindep_targes`, and `lib` for /// artifact dependencies support. /// /// This provides a method to safely introduce changes to index entries /// and allow older versions of cargo to ignore newer entries it doesn't /// understand. This is honored as of 1.51, so unfortunately older /// versions will ignore it, and potentially misinterpret version 2 and /// newer entries. /// /// The intent is that versions older than 1.51 will work with a /// pre-existing `Cargo.lock`, but they may not correctly process `cargo /// update` or build a lock from scratch. In that case, cargo may /// incorrectly select a new package that uses a new index schema. A /// workaround is to downgrade any packages that are incompatible with the /// `--precise` flag of `cargo update`. pub v: Option, } /// A dependency as encoded in the [`IndexPackage`] index JSON. #[derive(Deserialize, Serialize, Clone)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct RegistryDependency<'a> { /// Name of the dependency. If the dependency is renamed, the original /// would be stored in [`RegistryDependency::package`]. #[serde(borrow)] pub name: Cow<'a, str>, /// The SemVer requirement for this dependency. #[serde(borrow)] pub req: Cow<'a, str>, /// Set of features enabled for this dependency. #[serde(default)] pub features: Vec>, /// Whether or not this is an optional dependency. #[serde(default)] pub optional: bool, /// Whether or not default features are enabled. #[serde(default = "default_true")] pub default_features: bool, /// The target platform for this dependency. pub target: Option>, /// The dependency kind. "dev", "build", and "normal". pub kind: Option>, /// The URL of the index of the registry where this dependency is from. /// `None` if it is from the same index. pub registry: Option>, /// The original name if the dependency is renamed. pub package: Option>, /// Whether or not this is a public dependency. Unstable. See [RFC 1977]. /// /// [RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html pub public: Option, /// The artifacts to build from this dependency. pub artifact: Option>>, /// The target for bindep. pub bindep_target: Option>, /// Whether or not this is a library dependency. #[serde(default)] pub lib: bool, } pub fn parse_pubtime(s: &str) -> Result { let dt = jiff::civil::DateTime::strptime("%Y-%m-%dT%H:%M:%SZ", s)?; if s.len() == 20 { let zoned = dt.to_zoned(jiff::tz::TimeZone::UTC)?; let timestamp = zoned.timestamp(); Ok(timestamp) } else { Err(jiff::Error::from_args(format_args!( "padding required for `{s}`" ))) } } pub fn format_pubtime(t: jiff::Timestamp) -> String { t.strftime("%Y-%m-%dT%H:%M:%SZ").to_string() } mod serde_pubtime { #[inline] pub(super) fn serialize( timestamp: &Option, se: S, ) -> Result { match *timestamp { None => se.serialize_none(), Some(ref ts) => { let s = super::format_pubtime(*ts); se.serialize_str(&s) } } } #[inline] pub(super) fn deserialize<'de, D: serde::Deserializer<'de>>( de: D, ) -> Result, D::Error> { de.deserialize_option(OptionalVisitor( serde_untagged::UntaggedEnumVisitor::new() .expecting("date time") .string(|value| super::parse_pubtime(&value).map_err(serde::de::Error::custom)), )) } /// A generic visitor for `Option`. struct OptionalVisitor(V); impl<'de, V: serde::de::Visitor<'de, Value = jiff::Timestamp>> serde::de::Visitor<'de> for OptionalVisitor { type Value = Option; fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("date time") } #[inline] fn visit_some>( self, de: D, ) -> Result, D::Error> { de.deserialize_str(self.0).map(Some) } #[inline] fn visit_none(self) -> Result, E> { Ok(None) } } } fn default_true() -> bool { true } #[test] fn escaped_char_in_index_json_blob() { let _: IndexPackage<'_> = serde_json::from_str( r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{}}"#, ) .unwrap(); let _: IndexPackage<'_> = serde_json::from_str( r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{"test":["k","q"]},"links":"a-sys"}"# ).unwrap(); // Now we add escaped cher all the places they can go // these are not valid, but it should error later than json parsing let _: IndexPackage<'_> = serde_json::from_str( r#"{ "name":"This name has a escaped cher in it \n\t\" ", "vers":"0.0.1", "deps":[{ "name": " \n\t\" ", "req": " \n\t\" ", "features": [" \n\t\" "], "optional": true, "default_features": true, "target": " \n\t\" ", "kind": " \n\t\" ", "registry": " \n\t\" " }], "cksum":"bae3", "features":{"test \n\t\" ":["k \n\t\" ","q \n\t\" "]}, "links":" \n\t\" "}"#, ) .unwrap(); } #[cfg(feature = "unstable-schema")] #[test] fn dump_index_schema() { let schema = schemars::schema_for!(crate::index::IndexPackage<'_>); let dump = serde_json::to_string_pretty(&schema).unwrap(); snapbox::assert_data_eq!(dump, snapbox::file!("../index.schema.json").raw()); } #[test] fn pubtime_format() { use snapbox::str; let input = [ ("2025-11-12T19:30:12Z", Some(str!["2025-11-12T19:30:12Z"])), // Padded values ("2025-01-02T09:03:02Z", Some(str!["2025-01-02T09:03:02Z"])), // Alt timezone format ("2025-11-12T19:30:12-04", None), // Alt date/time separator ("2025-11-12 19:30:12Z", None), // Non-padded values ("2025-11-12T19:30:12+4", None), ("2025-1-12T19:30:12+4", None), ("2025-11-2T19:30:12+4", None), ("2025-11-12T9:30:12Z", None), ("2025-11-12T19:3:12Z", None), ("2025-11-12T19:30:2Z", None), ]; for (input, expected) in input { let (parsed, expected) = match (parse_pubtime(input), expected) { (Ok(_), None) => { panic!("`{input}` did not error"); } (Ok(parsed), Some(expected)) => (parsed, expected), (Err(err), Some(_)) => { panic!("`{input}` did not parse successfully: {err}"); } _ => { continue; } }; let output = format_pubtime(parsed); snapbox::assert_data_eq!(output, expected); } } ================================================ FILE: crates/cargo-util-schemas/src/lib.rs ================================================ //! Low-level Cargo format schemas //! //! This is types with logic mostly focused on `serde` and `FromStr` for use in reading files and //! parsing command-lines. //! Any logic for getting final semantics from these will likely need other tools to process, like //! `cargo metadata`. //! //! > This crate is maintained by the Cargo team for use by the wider //! > ecosystem. This crate follows semver compatibility for its APIs. pub mod core; pub mod index; pub mod lockfile; pub mod manifest; pub mod messages; #[cfg(feature = "unstable-schema")] pub mod schema; mod restricted_names; ================================================ FILE: crates/cargo-util-schemas/src/lockfile.rs ================================================ //! `Cargo.lock` / Lockfile schema definition use std::collections::BTreeMap; use std::fmt; use std::{cmp::Ordering, str::FromStr}; use serde::{Deserialize, Serialize, de, ser}; use url::Url; use crate::core::{GitReference, SourceKind}; /// Serialization of `Cargo.lock` #[derive(Serialize, Deserialize, Debug)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlLockfile { /// The lockfile format version (`version =` field). /// /// This field is optional for backward compatibility. Older lockfiles, i.e. V1 and V2, does /// not have the version field serialized. pub version: Option, /// The list of `[[package]]` entries describing each resolved dependency. pub package: Option>, /// The `[root]` table describing the root package. /// /// This field is optional for backward compatibility. Older lockfiles have the root package /// separated, whereas newer lockfiles have the root package as part of `[[package]]`. pub root: Option, /// The `[metadata]` table /// /// /// In older lockfile versions, dependency checksums were stored here instead of alongside each /// package entry. pub metadata: Option, /// The `[patch]` table describing unused patches. /// /// The lockfile stores them as a list of `[[patch.unused]]` entries. #[serde(default, skip_serializing_if = "TomlLockfilePatch::is_empty")] pub patch: TomlLockfilePatch, } /// Serialization of lockfiles metadata /// /// Older versions of lockfiles have their dependencies' checksums on this `[metadata]` table. pub type TomlLockfileMetadata = BTreeMap; /// Serialization of unused patches /// /// Cargo stores patches that were declared but not used during resolution. #[derive(Serialize, Deserialize, Debug, Default)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlLockfilePatch { /// The list of unused dependency patches. pub unused: Vec, } impl TomlLockfilePatch { fn is_empty(&self) -> bool { self.unused.is_empty() } } /// Serialization of lockfiles dependencies #[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlLockfileDependency { /// The name of the dependency. pub name: String, /// The version of the dependency. pub version: String, /// The source of the dependency. /// /// Cargo does not serialize path dependencies. pub source: Option, /// The checksum of the dependency. /// /// In older lockfiles, checksums were not stored here and instead on a separate `[metadata]` /// table (see [`TomlLockfileMetadata`]). pub checksum: Option, /// The transitive dependencies used by this dependency. pub dependencies: Option>, /// The replace of the dependency. pub replace: Option, } /// Serialization of dependency's source #[derive(Debug, Clone)] #[cfg_attr( feature = "unstable-schema", derive(schemars::JsonSchema), schemars(with = "String") )] pub struct TomlLockfileSourceId { /// The string representation of the source as it appears in the lockfile. source_str: String, /// The parsed source type, e.g. `git`, `registry`. /// /// Used for sources ordering. kind: SourceKind, /// The parsed URL of the source. /// /// Used for sources ordering. url: Url, } impl TomlLockfileSourceId { pub fn new(source: String) -> Result { let source_str = source.clone(); let (kind, url) = source .split_once('+') .ok_or_else(|| TomlLockfileSourceIdErrorKind::InvalidSource(source.clone()))?; // Sparse URLs store the kind prefix (sparse+) in the URL. Therefore, for sparse kinds, we // want to use the raw `source` instead of the split `url`. let url = Url::parse(if kind == "sparse" { &source } else { url }).map_err(|msg| { TomlLockfileSourceIdErrorKind::InvalidUrl { url: url.to_string(), msg: msg.to_string(), } })?; let kind = match kind { "git" => { let reference = GitReference::from_query(url.query_pairs()); SourceKind::Git(reference) } "registry" => SourceKind::Registry, "sparse" => SourceKind::SparseRegistry, "path" => SourceKind::Path, kind => { return Err( TomlLockfileSourceIdErrorKind::UnsupportedSource(kind.to_string()).into(), ); } }; Ok(Self { source_str, kind, url, }) } pub fn kind(&self) -> &SourceKind { &self.kind } pub fn url(&self) -> &Url { &self.url } pub fn source_str(&self) -> &String { &self.source_str } pub fn as_url(&self) -> impl fmt::Display + '_ { self.source_str.clone() } } impl ser::Serialize for TomlLockfileSourceId { fn serialize(&self, s: S) -> Result where S: ser::Serializer, { s.collect_str(&self.as_url()) } } impl<'de> de::Deserialize<'de> for TomlLockfileSourceId { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { let s = String::deserialize(d)?; Ok(TomlLockfileSourceId::new(s).map_err(de::Error::custom)?) } } impl std::hash::Hash for TomlLockfileSourceId { fn hash(&self, state: &mut H) { self.kind.hash(state); self.url.hash(state); } } impl std::cmp::PartialEq for TomlLockfileSourceId { fn eq(&self, other: &Self) -> bool { self.kind == other.kind && self.url == other.url } } impl std::cmp::Eq for TomlLockfileSourceId {} impl PartialOrd for TomlLockfileSourceId { fn partial_cmp(&self, other: &TomlLockfileSourceId) -> Option { Some(self.cmp(other)) } } impl Ord for TomlLockfileSourceId { fn cmp(&self, other: &TomlLockfileSourceId) -> Ordering { self.kind .cmp(&other.kind) .then_with(|| self.url.cmp(&other.url)) } } /// Serialization of package IDs. /// /// The version and source are only included when necessary to disambiguate between packages: /// - If multiple packages share the same name, the version is included. /// - If multiple packages share the same name and version, the source is included. #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlLockfilePackageId { pub name: String, pub version: Option, pub source: Option, } impl fmt::Display for TomlLockfilePackageId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name)?; if let Some(s) = &self.version { write!(f, " {}", s)?; } if let Some(s) = &self.source { write!(f, " ({})", s.as_url())?; } Ok(()) } } impl FromStr for TomlLockfilePackageId { type Err = TomlLockfilePackageIdError; fn from_str(s: &str) -> Result { let mut s = s.splitn(3, ' '); let name = s.next().unwrap(); let version = s.next(); let source_id = match s.next() { Some(s) => { if let Some(s) = s.strip_prefix('(').and_then(|s| s.strip_suffix(')')) { Some(TomlLockfileSourceId::new(s.to_string())?) } else { return Err(TomlLockfilePackageIdErrorKind::InvalidSerializedPackageId.into()); } } None => None, }; Ok(TomlLockfilePackageId { name: name.to_string(), version: version.map(|v| v.to_string()), source: source_id, }) } } impl ser::Serialize for TomlLockfilePackageId { fn serialize(&self, s: S) -> Result where S: ser::Serializer, { s.collect_str(self) } } impl<'de> de::Deserialize<'de> for TomlLockfilePackageId { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { String::deserialize(d).and_then(|string| { string .parse::() .map_err(de::Error::custom) }) } } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct TomlLockfileSourceIdError(#[from] TomlLockfileSourceIdErrorKind); #[non_exhaustive] #[derive(Debug, thiserror::Error)] enum TomlLockfileSourceIdErrorKind { #[error("invalid source `{0}`")] InvalidSource(String), #[error("invalid url `{url}`: {msg}")] InvalidUrl { url: String, msg: String }, #[error("unsupported source protocol: {0}")] UnsupportedSource(String), } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct TomlLockfilePackageIdError(#[from] TomlLockfilePackageIdErrorKind); impl From for TomlLockfilePackageIdError { fn from(value: TomlLockfileSourceIdError) -> Self { TomlLockfilePackageIdErrorKind::Source(value).into() } } #[non_exhaustive] #[derive(Debug, thiserror::Error)] enum TomlLockfilePackageIdErrorKind { #[error("invalid serialied PackageId")] InvalidSerializedPackageId, #[error(transparent)] Source(#[from] TomlLockfileSourceIdError), } #[cfg(feature = "unstable-schema")] #[test] fn dump_lockfile_schema() { let schema = schemars::schema_for!(crate::lockfile::TomlLockfile); let dump = serde_json::to_string_pretty(&schema).unwrap(); snapbox::assert_data_eq!(dump, snapbox::file!("../lockfile.schema.json").raw()); } #[cfg(test)] mod tests { use crate::core::{GitReference, SourceKind}; use crate::lockfile::{TomlLockfileSourceId, TomlLockfileSourceIdErrorKind}; #[track_caller] fn ok(source_str: &str, source_kind: SourceKind, url: &str) { let source_str = source_str.to_owned(); let source_id = TomlLockfileSourceId::new(source_str).unwrap(); assert_eq!(source_id.kind, source_kind); assert_eq!(source_id.url().to_string(), url); } macro_rules! err { ($src:expr, $expected:pat) => { let kind = TomlLockfileSourceId::new($src.to_owned()).unwrap_err().0; assert!( matches!(kind, $expected), "`{}` parse error mismatch, got {kind:?}", $src, ); }; } #[test] fn good_sources() { ok( "sparse+https://my-crates.io", SourceKind::SparseRegistry, "sparse+https://my-crates.io", ); ok( "registry+https://github.com/rust-lang/crates.io-index", SourceKind::Registry, "https://github.com/rust-lang/crates.io-index", ); ok( "git+https://github.com/rust-lang/cargo", SourceKind::Git(GitReference::DefaultBranch), "https://github.com/rust-lang/cargo", ); ok( "git+https://github.com/rust-lang/cargo?branch=dev", SourceKind::Git(GitReference::Branch("dev".to_owned())), "https://github.com/rust-lang/cargo?branch=dev", ); ok( "git+https://github.com/rust-lang/cargo?tag=v1.0", SourceKind::Git(GitReference::Tag("v1.0".to_owned())), "https://github.com/rust-lang/cargo?tag=v1.0", ); ok( "git+https://github.com/rust-lang/cargo?rev=refs/pull/493/head", SourceKind::Git(GitReference::Rev("refs/pull/493/head".to_owned())), "https://github.com/rust-lang/cargo?rev=refs/pull/493/head", ); ok( "path+file:///path/to/root", SourceKind::Path, "file:///path/to/root", ); } #[test] fn bad_sources() { err!( "unknown+https://my-crates.io", TomlLockfileSourceIdErrorKind::UnsupportedSource(..) ); err!( "registry+https//github.com/rust-lang/crates.io-index", TomlLockfileSourceIdErrorKind::InvalidUrl { .. } ); err!( "https//github.com/rust-lang/crates.io-index", TomlLockfileSourceIdErrorKind::InvalidSource(..) ); } } ================================================ FILE: crates/cargo-util-schemas/src/manifest/mod.rs ================================================ //! `Cargo.toml` / Manifest schema definition //! //! ## Style //! //! - Fields duplicated for an alias will have an accessor with the primary field's name //! - Keys that exist for bookkeeping but don't correspond to the schema have a `_` prefix use std::collections::BTreeMap; use std::collections::BTreeSet; #[cfg(feature = "unstable-schema")] use std::collections::HashMap; use std::fmt::{self, Display, Write}; use std::path::PathBuf; use std::str; use serde::de::{self, IntoDeserializer as _, Unexpected}; use serde::ser; use serde::{Deserialize, Serialize}; use serde_untagged::UntaggedEnumVisitor; use crate::core::PackageIdSpec; use crate::restricted_names; mod rust_version; pub use crate::restricted_names::NameValidationError; pub use rust_version::RustVersion; pub use rust_version::RustVersionError; #[cfg(feature = "unstable-schema")] use crate::schema::TomlValueWrapper; /// This type is used to deserialize `Cargo.toml` files. #[derive(Default, Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlManifest { pub cargo_features: Option>, // Update `requires_package` when adding new package-specific fields pub package: Option>, pub project: Option>, pub badges: Option>>, pub features: Option>>, pub lib: Option, pub bin: Option>, pub example: Option>, pub test: Option>, pub bench: Option>, pub dependencies: Option>, pub dev_dependencies: Option>, #[serde(rename = "dev_dependencies")] pub dev_dependencies2: Option>, pub build_dependencies: Option>, #[serde(rename = "build_dependencies")] pub build_dependencies2: Option>, pub target: Option>, pub lints: Option, pub hints: Option, pub workspace: Option, pub profile: Option, pub patch: Option>>, pub replace: Option>, /// Report unused keys (see also nested `_unused_keys`) /// Note: this is populated by the caller, rather than automatically #[serde(skip)] pub _unused_keys: BTreeSet, } impl TomlManifest { pub fn requires_package(&self) -> impl Iterator { [ self.badges.as_ref().map(|_| "badges"), self.features.as_ref().map(|_| "features"), self.lib.as_ref().map(|_| "lib"), self.bin.as_ref().map(|_| "bin"), self.example.as_ref().map(|_| "example"), self.test.as_ref().map(|_| "test"), self.bench.as_ref().map(|_| "bench"), self.dependencies.as_ref().map(|_| "dependencies"), self.dev_dependencies().as_ref().map(|_| "dev-dependencies"), self.build_dependencies() .as_ref() .map(|_| "build-dependencies"), self.target.as_ref().map(|_| "target"), self.lints.as_ref().map(|_| "lints"), self.hints.as_ref().map(|_| "hints"), ] .into_iter() .flatten() } pub fn has_profiles(&self) -> bool { self.profile.is_some() } pub fn package(&self) -> Option<&Box> { self.package.as_ref().or(self.project.as_ref()) } pub fn dev_dependencies(&self) -> Option<&BTreeMap> { self.dev_dependencies .as_ref() .or(self.dev_dependencies2.as_ref()) } pub fn build_dependencies(&self) -> Option<&BTreeMap> { self.build_dependencies .as_ref() .or(self.build_dependencies2.as_ref()) } pub fn features(&self) -> Option<&BTreeMap>> { self.features.as_ref() } pub fn normalized_lints(&self) -> Result, UnresolvedError> { self.lints.as_ref().map(|l| l.normalized()).transpose() } } #[derive(Debug, Default, Deserialize, Serialize, Clone)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlWorkspace { pub members: Option>, pub exclude: Option>, pub default_members: Option>, pub resolver: Option, #[cfg_attr( feature = "unstable-schema", schemars(with = "Option") )] pub metadata: Option, // Properties that can be inherited by members. pub package: Option, pub dependencies: Option>, pub lints: Option, } /// A group of fields that are inheritable by members of the workspace #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct InheritablePackage { pub version: Option, pub authors: Option>, pub description: Option, pub homepage: Option, pub documentation: Option, pub readme: Option, pub keywords: Option>, pub categories: Option>, pub license: Option, pub license_file: Option, pub repository: Option, pub publish: Option, pub edition: Option, pub badges: Option>>, pub exclude: Option>, pub include: Option>, #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub rust_version: Option, } /// Represents the `package`/`project` sections of a `Cargo.toml`. /// /// Note that the order of the fields matters, since this is the order they /// are serialized to a TOML file. For example, you cannot have values after /// the field `metadata`, since it is a table and values cannot appear after /// tables. #[derive(Deserialize, Serialize, Clone, Debug, Default)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlPackage { pub edition: Option, #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub rust_version: Option, #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub name: Option, pub version: Option, pub authors: Option, pub build: Option, pub metabuild: Option, pub default_target: Option, pub forced_target: Option, pub links: Option, pub exclude: Option, pub include: Option, pub publish: Option, pub workspace: Option, pub im_a_teapot: Option, pub autolib: Option, pub autobins: Option, pub autoexamples: Option, pub autotests: Option, pub autobenches: Option, pub default_run: Option, // Package metadata. pub description: Option, pub homepage: Option, pub documentation: Option, pub readme: Option, pub keywords: Option, pub categories: Option, pub license: Option, pub license_file: Option, pub repository: Option, pub resolver: Option, #[cfg_attr( feature = "unstable-schema", schemars(with = "Option") )] pub metadata: Option, /// Provide a helpful error message for a common user error. #[serde(rename = "cargo-features", skip_serializing)] #[cfg_attr(feature = "unstable-schema", schemars(skip))] pub _invalid_cargo_features: Option, } impl TomlPackage { pub fn new(name: PackageName) -> Self { Self { name: Some(name), ..Default::default() } } pub fn normalized_name(&self) -> Result<&PackageName, UnresolvedError> { self.name.as_ref().ok_or(UnresolvedError) } pub fn normalized_edition(&self) -> Result, UnresolvedError> { self.edition.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_rust_version(&self) -> Result, UnresolvedError> { self.rust_version .as_ref() .map(|v| v.normalized()) .transpose() } pub fn normalized_version(&self) -> Result, UnresolvedError> { self.version.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_authors(&self) -> Result>, UnresolvedError> { self.authors.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_build(&self) -> Result, UnresolvedError> { let build = self.build.as_ref().ok_or(UnresolvedError)?; match build { TomlPackageBuild::Auto(false) => Ok(None), TomlPackageBuild::Auto(true) => Err(UnresolvedError), TomlPackageBuild::SingleScript(value) => Ok(Some(std::slice::from_ref(value))), TomlPackageBuild::MultipleScript(scripts) => Ok(Some(scripts)), } } pub fn normalized_exclude(&self) -> Result>, UnresolvedError> { self.exclude.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_include(&self) -> Result>, UnresolvedError> { self.include.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_publish(&self) -> Result, UnresolvedError> { self.publish.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_description(&self) -> Result, UnresolvedError> { self.description .as_ref() .map(|v| v.normalized()) .transpose() } pub fn normalized_homepage(&self) -> Result, UnresolvedError> { self.homepage.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_documentation(&self) -> Result, UnresolvedError> { self.documentation .as_ref() .map(|v| v.normalized()) .transpose() } pub fn normalized_readme(&self) -> Result, UnresolvedError> { let readme = self.readme.as_ref().ok_or(UnresolvedError)?; readme.normalized().and_then(|sb| match sb { StringOrBool::Bool(false) => Ok(None), StringOrBool::Bool(true) => Err(UnresolvedError), StringOrBool::String(value) => Ok(Some(value)), }) } pub fn normalized_keywords(&self) -> Result>, UnresolvedError> { self.keywords.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_categories(&self) -> Result>, UnresolvedError> { self.categories.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_license(&self) -> Result, UnresolvedError> { self.license.as_ref().map(|v| v.normalized()).transpose() } pub fn normalized_license_file(&self) -> Result, UnresolvedError> { self.license_file .as_ref() .map(|v| v.normalized()) .transpose() } pub fn normalized_repository(&self) -> Result, UnresolvedError> { self.repository.as_ref().map(|v| v.normalized()).transpose() } } /// An enum that allows for inheriting keys from a workspace in a Cargo.toml. #[derive(Serialize, Copy, Clone, Debug)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum InheritableField { /// The type that is used when not inheriting from a workspace. Value(T), /// The type when inheriting from a workspace. Inherit(TomlInheritedField), } impl InheritableField { pub fn normalized(&self) -> Result<&T, UnresolvedError> { self.as_value().ok_or(UnresolvedError) } pub fn as_value(&self) -> Option<&T> { match self { InheritableField::Inherit(_) => None, InheritableField::Value(defined) => Some(defined), } } pub fn into_value(self) -> Option { match self { Self::Inherit(_) => None, Self::Value(defined) => Some(defined), } } pub fn is_inherited(&self) -> bool { matches!(self, Self::Inherit(_)) } } //. This already has a `Deserialize` impl from version_trim_whitespace pub type InheritableSemverVersion = InheritableField; impl<'de> de::Deserialize<'de> for InheritableSemverVersion { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { UntaggedEnumVisitor::new() .expecting("SemVer version") .string( |value| match value.trim().parse().map_err(de::Error::custom) { Ok(parsed) => Ok(InheritableField::Value(parsed)), Err(e) => Err(e), }, ) .map(|value| value.deserialize().map(InheritableField::Inherit)) .deserialize(d) } } pub type InheritableString = InheritableField; impl<'de> de::Deserialize<'de> for InheritableString { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = InheritableString; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.write_str("a string or workspace") } fn visit_string(self, value: String) -> Result where E: de::Error, { Ok(InheritableString::Value(value)) } fn visit_str(self, value: &str) -> Result where E: de::Error, { self.visit_string(value.to_owned()) } fn visit_map(self, map: V) -> Result where V: de::MapAccess<'de>, { let mvd = de::value::MapAccessDeserializer::new(map); TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit) } } d.deserialize_any(Visitor) } } pub type InheritableRustVersion = InheritableField; impl<'de> de::Deserialize<'de> for InheritableRustVersion { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = InheritableRustVersion; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.write_str("a semver or workspace") } fn visit_string(self, value: String) -> Result where E: de::Error, { let value = value.parse::().map_err(|e| E::custom(e))?; Ok(InheritableRustVersion::Value(value)) } fn visit_str(self, value: &str) -> Result where E: de::Error, { self.visit_string(value.to_owned()) } fn visit_map(self, map: V) -> Result where V: de::MapAccess<'de>, { let mvd = de::value::MapAccessDeserializer::new(map); TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit) } } d.deserialize_any(Visitor) } } pub type InheritableVecString = InheritableField>; impl<'de> de::Deserialize<'de> for InheritableVecString { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = InheritableVecString; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.write_str("a vector of strings or workspace") } fn visit_seq(self, v: A) -> Result where A: de::SeqAccess<'de>, { let seq = de::value::SeqAccessDeserializer::new(v); Vec::deserialize(seq).map(InheritableField::Value) } fn visit_map(self, map: V) -> Result where V: de::MapAccess<'de>, { let mvd = de::value::MapAccessDeserializer::new(map); TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit) } } d.deserialize_any(Visitor) } } pub type InheritableStringOrBool = InheritableField; impl<'de> de::Deserialize<'de> for InheritableStringOrBool { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = InheritableStringOrBool; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.write_str("a string, a bool, or workspace") } fn visit_bool(self, v: bool) -> Result where E: de::Error, { let b = de::value::BoolDeserializer::new(v); StringOrBool::deserialize(b).map(InheritableField::Value) } fn visit_string(self, v: String) -> Result where E: de::Error, { let string = de::value::StringDeserializer::new(v); StringOrBool::deserialize(string).map(InheritableField::Value) } fn visit_str(self, value: &str) -> Result where E: de::Error, { self.visit_string(value.to_owned()) } fn visit_map(self, map: V) -> Result where V: de::MapAccess<'de>, { let mvd = de::value::MapAccessDeserializer::new(map); TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit) } } d.deserialize_any(Visitor) } } pub type InheritableVecStringOrBool = InheritableField; impl<'de> de::Deserialize<'de> for InheritableVecStringOrBool { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = InheritableVecStringOrBool; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.write_str("a boolean, a vector of strings, or workspace") } fn visit_bool(self, v: bool) -> Result where E: de::Error, { let b = de::value::BoolDeserializer::new(v); VecStringOrBool::deserialize(b).map(InheritableField::Value) } fn visit_seq(self, v: A) -> Result where A: de::SeqAccess<'de>, { let seq = de::value::SeqAccessDeserializer::new(v); VecStringOrBool::deserialize(seq).map(InheritableField::Value) } fn visit_map(self, map: V) -> Result where V: de::MapAccess<'de>, { let mvd = de::value::MapAccessDeserializer::new(map); TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit) } } d.deserialize_any(Visitor) } } pub type InheritableBtreeMap = InheritableField>>; impl<'de> de::Deserialize<'de> for InheritableBtreeMap { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { let value = serde_value::Value::deserialize(deserializer)?; if let Ok(w) = TomlInheritedField::deserialize( serde_value::ValueDeserializer::::new(value.clone()), ) { return Ok(InheritableField::Inherit(w)); } BTreeMap::deserialize(serde_value::ValueDeserializer::::new(value)) .map(InheritableField::Value) } } #[derive(Deserialize, Serialize, Copy, Clone, Debug)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlInheritedField { workspace: WorkspaceValue, } impl TomlInheritedField { pub fn new() -> Self { TomlInheritedField { workspace: WorkspaceValue, } } } impl Default for TomlInheritedField { fn default() -> Self { Self::new() } } #[derive(Deserialize, Serialize, Copy, Clone, Debug)] #[serde(try_from = "bool")] #[serde(into = "bool")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] struct WorkspaceValue; impl TryFrom for WorkspaceValue { type Error = String; fn try_from(other: bool) -> Result { if other { Ok(WorkspaceValue) } else { Err("`workspace` cannot be false".to_owned()) } } } impl From for bool { fn from(_: WorkspaceValue) -> bool { true } } #[derive(Serialize, Clone, Debug)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum InheritableDependency { /// The type that is used when not inheriting from a workspace. Value(TomlDependency), /// The type when inheriting from a workspace. Inherit(TomlInheritedDependency), } impl InheritableDependency { pub fn unused_keys(&self) -> Vec { match self { InheritableDependency::Value(d) => d.unused_keys(), InheritableDependency::Inherit(w) => w._unused_keys.keys().cloned().collect(), } } pub fn normalized(&self) -> Result<&TomlDependency, UnresolvedError> { match self { InheritableDependency::Value(d) => Ok(d), InheritableDependency::Inherit(_) => Err(UnresolvedError), } } pub fn is_inherited(&self) -> bool { matches!(self, InheritableDependency::Inherit(_)) } } impl<'de> de::Deserialize<'de> for InheritableDependency { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { let value = serde_value::Value::deserialize(deserializer)?; if let Ok(w) = TomlInheritedDependency::deserialize(serde_value::ValueDeserializer::< D::Error, >::new(value.clone())) { return if w.workspace { Ok(InheritableDependency::Inherit(w)) } else { Err(de::Error::custom("`workspace` cannot be false")) }; } TomlDependency::deserialize(serde_value::ValueDeserializer::::new(value)) .map(InheritableDependency::Value) } } #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlInheritedDependency { pub workspace: bool, pub features: Option>, pub default_features: Option, #[serde(rename = "default_features")] pub default_features2: Option, pub optional: Option, pub public: Option, /// This is here to provide a way to see the "unused manifest keys" when deserializing #[serde(skip_serializing)] #[serde(flatten)] #[cfg_attr(feature = "unstable-schema", schemars(skip))] pub _unused_keys: BTreeMap, } impl TomlInheritedDependency { pub fn default_features(&self) -> Option { self.default_features.or(self.default_features2) } } #[derive(Clone, Debug, Serialize)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum TomlDependency { /// In the simple format, only a version is specified, eg. /// `package = ""` Simple(String), /// The simple format is equivalent to a detailed dependency /// specifying only a version, eg. /// `package = { version = "" }` Detailed(TomlDetailedDependency

), } impl TomlDependency { pub fn is_version_specified(&self) -> bool { match self { TomlDependency::Detailed(d) => d.version.is_some(), TomlDependency::Simple(..) => true, } } pub fn is_optional(&self) -> bool { match self { TomlDependency::Detailed(d) => d.optional.unwrap_or(false), TomlDependency::Simple(..) => false, } } pub fn is_public(&self) -> bool { match self { TomlDependency::Detailed(d) => d.public.unwrap_or(false), TomlDependency::Simple(..) => false, } } pub fn default_features(&self) -> Option { match self { TomlDependency::Detailed(d) => d.default_features(), TomlDependency::Simple(..) => None, } } pub fn unused_keys(&self) -> Vec { match self { TomlDependency::Simple(_) => vec![], TomlDependency::Detailed(detailed) => detailed._unused_keys.keys().cloned().collect(), } } } impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency

{ fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { use serde::de::Error as _; let expected = "a version string like \"0.9.8\" or a \ detailed dependency like { version = \"0.9.8\" }"; UntaggedEnumVisitor::new() .expecting(expected) .string(|value| Ok(TomlDependency::Simple(value.to_owned()))) .bool(|value| { let expected = format!("invalid type: boolean `{value}`, expected {expected}"); let err = if value { format!( "{expected}\n\ note: if you meant to use a workspace member, you can write\n \ dep.workspace = {value}" ) } else { expected }; Err(serde_untagged::de::Error::custom(err)) }) .map(|value| value.deserialize().map(TomlDependency::Detailed)) .deserialize(deserializer) } } #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlDetailedDependency { pub version: Option, #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub registry: Option, /// The URL of the `registry` field. /// This is an internal implementation detail. When Cargo creates a /// package, it replaces `registry` with `registry-index` so that the /// manifest contains the correct URL. All users won't have the same /// registry names configured, so Cargo can't rely on just the name for /// crates published by other users. pub registry_index: Option, // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file. pub path: Option

, #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub base: Option, pub git: Option, pub branch: Option, pub tag: Option, pub rev: Option, pub features: Option>, pub optional: Option, pub default_features: Option, #[serde(rename = "default_features")] pub default_features2: Option, #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub package: Option, pub public: Option, /// One or more of `bin`, `cdylib`, `staticlib`, `bin:`. pub artifact: Option, /// If set, the artifact should also be a dependency pub lib: Option, /// A platform name, like `x86_64-apple-darwin` pub target: Option, /// This is here to provide a way to see the "unused manifest keys" when deserializing #[serde(skip_serializing)] #[serde(flatten)] #[cfg_attr(feature = "unstable-schema", schemars(skip))] pub _unused_keys: BTreeMap, } impl TomlDetailedDependency

{ pub fn default_features(&self) -> Option { self.default_features.or(self.default_features2) } } // Explicit implementation so we avoid pulling in P: Default impl Default for TomlDetailedDependency

{ fn default() -> Self { Self { version: Default::default(), registry: Default::default(), registry_index: Default::default(), path: Default::default(), base: Default::default(), git: Default::default(), branch: Default::default(), tag: Default::default(), rev: Default::default(), features: Default::default(), optional: Default::default(), default_features: Default::default(), default_features2: Default::default(), package: Default::default(), public: Default::default(), artifact: Default::default(), lib: Default::default(), target: Default::default(), _unused_keys: Default::default(), } } } #[derive(Deserialize, Serialize, Clone, Debug, Default)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlProfiles(pub BTreeMap); impl TomlProfiles { pub fn get_all(&self) -> &BTreeMap { &self.0 } pub fn get(&self, name: &str) -> Option<&TomlProfile> { self.0.get(name) } } #[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)] #[serde(default, rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlProfile { pub opt_level: Option, pub lto: Option, pub codegen_backend: Option, pub codegen_units: Option, pub debug: Option, pub split_debuginfo: Option, pub debug_assertions: Option, pub rpath: Option, pub panic: Option, pub overflow_checks: Option, pub incremental: Option, pub dir_name: Option, pub inherits: Option, pub strip: Option, // Note that `rustflags` is used for the cargo-feature `profile_rustflags` pub rustflags: Option>, // These two fields must be last because they are sub-tables, and TOML // requires all non-tables to be listed first. pub package: Option>, pub build_override: Option>, /// Unstable feature `-Ztrim-paths`. pub trim_paths: Option, /// Unstable feature `hint-mostly-unused` pub hint_mostly_unused: Option, } impl TomlProfile { /// Overwrite self's values with the given profile. pub fn merge(&mut self, profile: &Self) { if let Some(v) = &profile.opt_level { self.opt_level = Some(v.clone()); } if let Some(v) = &profile.lto { self.lto = Some(v.clone()); } if let Some(v) = &profile.codegen_backend { self.codegen_backend = Some(v.clone()); } if let Some(v) = profile.codegen_units { self.codegen_units = Some(v); } if let Some(v) = profile.debug { self.debug = Some(v); } if let Some(v) = profile.debug_assertions { self.debug_assertions = Some(v); } if let Some(v) = &profile.split_debuginfo { self.split_debuginfo = Some(v.clone()); } if let Some(v) = profile.rpath { self.rpath = Some(v); } if let Some(v) = &profile.panic { self.panic = Some(v.clone()); } if let Some(v) = profile.overflow_checks { self.overflow_checks = Some(v); } if let Some(v) = profile.incremental { self.incremental = Some(v); } if let Some(v) = &profile.rustflags { self.rustflags = Some(v.clone()); } if let Some(other_package) = &profile.package { match &mut self.package { Some(self_package) => { for (spec, other_pkg_profile) in other_package { match self_package.get_mut(spec) { Some(p) => p.merge(other_pkg_profile), None => { self_package.insert(spec.clone(), other_pkg_profile.clone()); } } } } None => self.package = Some(other_package.clone()), } } if let Some(other_bo) = &profile.build_override { match &mut self.build_override { Some(self_bo) => self_bo.merge(other_bo), None => self.build_override = Some(other_bo.clone()), } } if let Some(v) = &profile.inherits { self.inherits = Some(v.clone()); } if let Some(v) = &profile.dir_name { self.dir_name = Some(v.clone()); } if let Some(v) = &profile.strip { self.strip = Some(v.clone()); } if let Some(v) = &profile.trim_paths { self.trim_paths = Some(v.clone()) } if let Some(v) = profile.hint_mostly_unused { self.hint_mostly_unused = Some(v); } } } #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum ProfilePackageSpec { Spec(PackageIdSpec), All, } impl fmt::Display for ProfilePackageSpec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ProfilePackageSpec::Spec(spec) => spec.fmt(f), ProfilePackageSpec::All => f.write_str("*"), } } } impl ser::Serialize for ProfilePackageSpec { fn serialize(&self, s: S) -> Result where S: ser::Serializer, { self.to_string().serialize(s) } } impl<'de> de::Deserialize<'de> for ProfilePackageSpec { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { let string = String::deserialize(d)?; if string == "*" { Ok(ProfilePackageSpec::All) } else { PackageIdSpec::parse(&string) .map_err(de::Error::custom) .map(ProfilePackageSpec::Spec) } } } #[derive(Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlOptLevel(pub String); impl ser::Serialize for TomlOptLevel { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { match self.0.parse::() { Ok(n) => n.serialize(serializer), Err(_) => self.0.serialize(serializer), } } } impl<'de> de::Deserialize<'de> for TomlOptLevel { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { use serde::de::Error as _; UntaggedEnumVisitor::new() .expecting("an optimization level") .i64(|value| Ok(TomlOptLevel(value.to_string()))) .string(|value| { if value == "s" || value == "z" { Ok(TomlOptLevel(value.to_string())) } else { Err(serde_untagged::de::Error::custom(format!( "must be `0`, `1`, `2`, `3`, `s` or `z`, \ but found the string: \"{}\"", value ))) } }) .deserialize(d) } } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum TomlDebugInfo { None, LineDirectivesOnly, LineTablesOnly, Limited, Full, } impl Display for TomlDebugInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TomlDebugInfo::None => f.write_char('0'), TomlDebugInfo::Limited => f.write_char('1'), TomlDebugInfo::Full => f.write_char('2'), TomlDebugInfo::LineDirectivesOnly => f.write_str("line-directives-only"), TomlDebugInfo::LineTablesOnly => f.write_str("line-tables-only"), } } } impl ser::Serialize for TomlDebugInfo { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { match self { Self::None => 0.serialize(serializer), Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), Self::LineTablesOnly => "line-tables-only".serialize(serializer), Self::Limited => 1.serialize(serializer), Self::Full => 2.serialize(serializer), } } } impl<'de> de::Deserialize<'de> for TomlDebugInfo { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { use serde::de::Error as _; let expecting = "a boolean, 0, 1, 2, \"none\", \"limited\", \"full\", \"line-tables-only\", or \"line-directives-only\""; UntaggedEnumVisitor::new() .expecting(expecting) .bool(|value| { Ok(if value { TomlDebugInfo::Full } else { TomlDebugInfo::None }) }) .i64(|value| { let debuginfo = match value { 0 => TomlDebugInfo::None, 1 => TomlDebugInfo::Limited, 2 => TomlDebugInfo::Full, _ => { return Err(serde_untagged::de::Error::invalid_value( Unexpected::Signed(value), &expecting, )); } }; Ok(debuginfo) }) .string(|value| { let debuginfo = match value { "none" => TomlDebugInfo::None, "limited" => TomlDebugInfo::Limited, "full" => TomlDebugInfo::Full, "line-directives-only" => TomlDebugInfo::LineDirectivesOnly, "line-tables-only" => TomlDebugInfo::LineTablesOnly, _ => { return Err(serde_untagged::de::Error::invalid_value( Unexpected::Str(value), &expecting, )); } }; Ok(debuginfo) }) .deserialize(d) } } #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize)] #[serde(untagged, rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum TomlTrimPaths { Values(Vec), All, } impl TomlTrimPaths { pub fn none() -> Self { TomlTrimPaths::Values(Vec::new()) } pub fn is_none(&self) -> bool { match self { TomlTrimPaths::Values(v) => v.is_empty(), TomlTrimPaths::All => false, } } } impl<'de> de::Deserialize<'de> for TomlTrimPaths { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { use serde::de::Error as _; let expecting = r#"a boolean, "none", "diagnostics", "macro", "object", "all", or an array with these options"#; UntaggedEnumVisitor::new() .expecting(expecting) .bool(|value| { Ok(if value { TomlTrimPaths::All } else { TomlTrimPaths::none() }) }) .string(|v| match v { "none" => Ok(TomlTrimPaths::none()), "all" => Ok(TomlTrimPaths::All), v => { let d = v.into_deserializer(); let err = |_: D::Error| { serde_untagged::de::Error::custom(format!("expected {expecting}")) }; TomlTrimPathsValue::deserialize(d) .map_err(err) .map(|v| v.into()) } }) .seq(|seq| { let seq: Vec = seq.deserialize()?; let seq: Vec<_> = seq .into_iter() .map(|s| TomlTrimPathsValue::deserialize(s.into_deserializer())) .collect::>()?; Ok(seq.into()) }) .deserialize(d) } } impl fmt::Display for TomlTrimPaths { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TomlTrimPaths::All => write!(f, "all"), TomlTrimPaths::Values(v) if v.is_empty() => write!(f, "none"), TomlTrimPaths::Values(v) => { let mut iter = v.iter(); if let Some(value) = iter.next() { write!(f, "{value}")?; } for value in iter { write!(f, ",{value}")?; } Ok(()) } } } } impl From for TomlTrimPaths { fn from(value: TomlTrimPathsValue) -> Self { TomlTrimPaths::Values(vec![value]) } } impl From> for TomlTrimPaths { fn from(value: Vec) -> Self { TomlTrimPaths::Values(value) } } #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum TomlTrimPathsValue { Diagnostics, Macro, Object, } impl TomlTrimPathsValue { pub fn as_str(&self) -> &'static str { match self { TomlTrimPathsValue::Diagnostics => "diagnostics", TomlTrimPathsValue::Macro => "macro", TomlTrimPathsValue::Object => "object", } } } impl fmt::Display for TomlTrimPathsValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } pub type TomlLibTarget = TomlTarget; pub type TomlBinTarget = TomlTarget; pub type TomlExampleTarget = TomlTarget; pub type TomlTestTarget = TomlTarget; pub type TomlBenchTarget = TomlTarget; #[derive(Default, Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlTarget { pub name: Option, // The intention was to only accept `crate-type` here but historical // versions of Cargo also accepted `crate_type`, so look for both. pub crate_type: Option>, #[serde(rename = "crate_type")] pub crate_type2: Option>, #[cfg_attr(feature = "unstable-schema", schemars(with = "Option"))] pub path: Option, // Note that `filename` is used for the cargo-feature `different_binary_name` pub filename: Option, pub test: Option, pub doctest: Option, pub bench: Option, pub doc: Option, pub doc_scrape_examples: Option, pub proc_macro: Option, #[serde(rename = "proc_macro")] pub proc_macro2: Option, pub harness: Option, pub required_features: Option>, pub edition: Option, } impl TomlTarget { pub fn new() -> TomlTarget { TomlTarget::default() } pub fn proc_macro(&self) -> Option { self.proc_macro.or(self.proc_macro2).or_else(|| { if let Some(types) = self.crate_types() { if types.contains(&"proc-macro".to_string()) { return Some(true); } } None }) } pub fn crate_types(&self) -> Option<&Vec> { self.crate_type .as_ref() .or_else(|| self.crate_type2.as_ref()) } } macro_rules! str_newtype { ($name:ident) => { /// Verified string newtype #[derive(Serialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(transparent)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct $name = String>(T); impl> $name { pub fn into_inner(self) -> T { self.0 } } impl> AsRef for $name { fn as_ref(&self) -> &str { self.0.as_ref() } } impl> std::ops::Deref for $name { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl> std::borrow::Borrow for $name { fn borrow(&self) -> &str { self.0.as_ref() } } impl<'a> std::str::FromStr for $name { type Err = restricted_names::NameValidationError; fn from_str(value: &str) -> Result { Self::new(value.to_owned()) } } impl<'de, T: AsRef + serde::Deserialize<'de>> serde::Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let inner = T::deserialize(deserializer)?; Self::new(inner).map_err(serde::de::Error::custom) } } impl> Display for $name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.as_ref().fmt(f) } } }; } str_newtype!(PackageName); impl> PackageName { /// Validated package name pub fn new(name: T) -> Result { restricted_names::validate_package_name(name.as_ref())?; Ok(Self(name)) } } impl PackageName { /// Coerce a value to be a validate package name /// /// Replaces invalid values with `placeholder` pub fn sanitize(name: impl AsRef, placeholder: char) -> Self { PackageName(restricted_names::sanitize_package_name( name.as_ref(), placeholder, )) } } str_newtype!(RegistryName); impl> RegistryName { /// Validated registry name pub fn new(name: T) -> Result { restricted_names::validate_registry_name(name.as_ref())?; Ok(Self(name)) } } str_newtype!(ProfileName); impl> ProfileName { /// Validated profile name pub fn new(name: T) -> Result { restricted_names::validate_profile_name(name.as_ref())?; Ok(Self(name)) } } str_newtype!(FeatureName); impl> FeatureName { /// Validated feature name pub fn new(name: T) -> Result { restricted_names::validate_feature_name(name.as_ref())?; Ok(Self(name)) } } str_newtype!(PathBaseName); impl> PathBaseName { /// Validated path base name pub fn new(name: T) -> Result { restricted_names::validate_path_base_name(name.as_ref())?; Ok(Self(name)) } } /// Corresponds to a `target` entry, but `TomlTarget` is already used. #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlPlatform { pub dependencies: Option>, pub build_dependencies: Option>, #[serde(rename = "build_dependencies")] pub build_dependencies2: Option>, pub dev_dependencies: Option>, #[serde(rename = "dev_dependencies")] pub dev_dependencies2: Option>, } impl TomlPlatform { pub fn dev_dependencies(&self) -> Option<&BTreeMap> { self.dev_dependencies .as_ref() .or(self.dev_dependencies2.as_ref()) } pub fn build_dependencies(&self) -> Option<&BTreeMap> { self.build_dependencies .as_ref() .or(self.build_dependencies2.as_ref()) } } #[derive(Serialize, Debug, Clone)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct InheritableLints { #[serde(skip_serializing_if = "std::ops::Not::not")] #[cfg_attr(feature = "unstable-schema", schemars(default))] pub workspace: bool, #[serde(flatten)] pub lints: TomlLints, } impl InheritableLints { pub fn normalized(&self) -> Result<&TomlLints, UnresolvedError> { if self.workspace { Err(UnresolvedError) } else { Ok(&self.lints) } } } impl<'de> Deserialize<'de> for InheritableLints { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct InheritableLintsVisitor; impl<'de> de::Visitor<'de> for InheritableLintsVisitor { // The type that our Visitor is going to produce. type Value = InheritableLints; // Format a message stating what data this Visitor expects to receive. fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a lints table") } // Deserialize MyMap from an abstract "map" provided by the // Deserializer. The MapAccess input is a callback provided by // the Deserializer to let us see each entry in the map. fn visit_map(self, mut access: M) -> Result where M: de::MapAccess<'de>, { let mut lints = TomlLints::new(); let mut workspace = false; // While there are entries remaining in the input, add them // into our map. while let Some(key) = access.next_key()? { if key == "workspace" { workspace = match access.next_value()? { Some(WorkspaceValue) => true, None => false, }; } else { let value = access.next_value()?; lints.insert(key, value); } } Ok(InheritableLints { workspace, lints }) } } deserializer.deserialize_map(InheritableLintsVisitor) } } pub type TomlLints = BTreeMap; pub type TomlToolLints = BTreeMap; #[derive(Serialize, Debug, Clone)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum TomlLint { Level(TomlLintLevel), Config(TomlLintConfig), } impl<'de> Deserialize<'de> for TomlLint { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { UntaggedEnumVisitor::new() .string(|string| { TomlLintLevel::deserialize(string.into_deserializer()).map(TomlLint::Level) }) .map(|map| map.deserialize().map(TomlLint::Config)) .deserialize(deserializer) } } impl TomlLint { pub fn level(&self) -> TomlLintLevel { match self { Self::Level(level) => *level, Self::Config(config) => config.level, } } pub fn priority(&self) -> i8 { match self { Self::Level(_) => 0, Self::Config(config) => config.priority, } } pub fn config(&self) -> Option<&toml::Table> { match self { Self::Level(_) => None, Self::Config(config) => Some(&config.config), } } } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TomlLintConfig { pub level: TomlLintLevel, #[serde(default)] pub priority: i8, #[serde(flatten)] #[cfg_attr( feature = "unstable-schema", schemars(with = "HashMap") )] pub config: toml::Table, } #[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum TomlLintLevel { Forbid, Deny, Warn, Allow, } #[derive(Serialize, Deserialize, Debug, Default, Clone)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct Hints { #[cfg_attr( feature = "unstable-schema", schemars(with = "Option") )] pub mostly_unused: Option, } #[derive(Copy, Clone, Debug)] pub struct InvalidCargoFeatures {} impl<'de> de::Deserialize<'de> for InvalidCargoFeatures { fn deserialize(_d: D) -> Result where D: de::Deserializer<'de>, { use serde::de::Error as _; Err(D::Error::custom( "the field `cargo-features` should be set at the top of Cargo.toml before any tables", )) } } /// This can be parsed from either a TOML string or array, /// but is always stored as a vector. #[derive(Clone, Debug, Serialize, Eq, PartialEq, PartialOrd, Ord)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct StringOrVec(pub Vec); impl StringOrVec { pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> { self.0.iter() } } impl<'de> de::Deserialize<'de> for StringOrVec { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { UntaggedEnumVisitor::new() .expecting("string or list of strings") .string(|value| Ok(StringOrVec(vec![value.to_owned()]))) .seq(|value| value.deserialize().map(StringOrVec)) .deserialize(deserializer) } } #[derive(Clone, Debug, Serialize, Eq, PartialEq)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum StringOrBool { String(String), Bool(bool), } impl<'de> Deserialize<'de> for StringOrBool { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { UntaggedEnumVisitor::new() .bool(|b| Ok(StringOrBool::Bool(b))) .string(|s| Ok(StringOrBool::String(s.to_owned()))) .deserialize(deserializer) } } #[derive(Clone, Debug, Serialize, Eq, PartialEq)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum TomlPackageBuild { /// If build scripts are disabled or enabled. /// If true, `build.rs` in the root folder will be the build script. Auto(bool), /// Path of Build Script if there's just one script. SingleScript(String), /// Vector of paths if multiple build script are to be used. MultipleScript(Vec), } impl<'de> Deserialize<'de> for TomlPackageBuild { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { UntaggedEnumVisitor::new() .bool(|b| Ok(TomlPackageBuild::Auto(b))) .string(|s| Ok(TomlPackageBuild::SingleScript(s.to_owned()))) .seq(|value| value.deserialize().map(TomlPackageBuild::MultipleScript)) .deserialize(deserializer) } } #[derive(PartialEq, Clone, Debug, Serialize)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum VecStringOrBool { VecString(Vec), Bool(bool), } impl<'de> de::Deserialize<'de> for VecStringOrBool { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { UntaggedEnumVisitor::new() .expecting("a boolean or vector of strings") .bool(|value| Ok(VecStringOrBool::Bool(value))) .seq(|value| value.deserialize().map(VecStringOrBool::VecString)) .deserialize(deserializer) } } #[derive(Clone, PartialEq, Eq)] pub struct PathValue(pub PathBuf); impl fmt::Debug for PathValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl ser::Serialize for PathValue { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { self.0.serialize(serializer) } } impl<'de> de::Deserialize<'de> for PathValue { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { Ok(PathValue(String::deserialize(deserializer)?.into())) } } /// Error validating names in Cargo. #[derive(Debug, thiserror::Error)] #[error("manifest field was not resolved")] #[non_exhaustive] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct UnresolvedError; #[cfg(feature = "unstable-schema")] #[test] fn dump_manifest_schema() { let schema = schemars::schema_for!(crate::manifest::TomlManifest); let dump = serde_json::to_string_pretty(&schema).unwrap(); snapbox::assert_data_eq!(dump, snapbox::file!("../../manifest.schema.json").raw()); } ================================================ FILE: crates/cargo-util-schemas/src/manifest/rust_version.rs ================================================ use std::fmt; use std::fmt::Display; use serde_untagged::UntaggedEnumVisitor; use crate::core::PartialVersion; use crate::core::PartialVersionError; #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)] pub struct RustVersion { major: u64, minor: Option, patch: Option, } impl RustVersion { pub const fn new(major: u64, minor: u64, patch: u64) -> Self { Self { major, minor: Some(minor), patch: Some(patch), } } pub fn is_compatible_with(&self, rustc: &PartialVersion) -> bool { let msrv = self.to_partial().to_caret_req(); // Remove any pre-release identifiers for easier comparison let rustc = semver::Version { major: rustc.major, minor: rustc.minor.unwrap_or_default(), patch: rustc.patch.unwrap_or_default(), pre: Default::default(), build: Default::default(), }; msrv.matches(&rustc) } pub fn to_partial(&self) -> PartialVersion { let Self { major, minor, patch, } = *self; PartialVersion { major, minor, patch, pre: None, build: None, } } } impl std::str::FromStr for RustVersion { type Err = RustVersionError; fn from_str(value: &str) -> Result { let partial = value.parse::(); let partial = partial.map_err(RustVersionErrorKind::PartialVersion)?; partial.try_into() } } impl TryFrom for RustVersion { type Error = RustVersionError; fn try_from(version: semver::Version) -> Result { let version = PartialVersion::from(version); Self::try_from(version) } } impl TryFrom for RustVersion { type Error = RustVersionError; fn try_from(partial: PartialVersion) -> Result { let PartialVersion { major, minor, patch, pre, build, } = partial; if pre.is_some() { return Err(RustVersionErrorKind::Prerelease.into()); } if build.is_some() { return Err(RustVersionErrorKind::BuildMetadata.into()); } Ok(Self { major, minor, patch, }) } } impl serde::Serialize for RustVersion { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.collect_str(self) } } impl<'de> serde::Deserialize<'de> for RustVersion { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { UntaggedEnumVisitor::new() .expecting("SemVer version") .string(|value| value.parse().map_err(serde::de::Error::custom)) .deserialize(deserializer) } } impl Display for RustVersion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.to_partial().fmt(f) } } /// Error parsing a [`RustVersion`]. #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct RustVersionError(#[from] RustVersionErrorKind); /// Non-public error kind for [`RustVersionError`]. #[non_exhaustive] #[derive(Debug, thiserror::Error)] enum RustVersionErrorKind { #[error("unexpected prerelease field, expected a version like \"1.32\"")] Prerelease, #[error("unexpected build field, expected a version like \"1.32\"")] BuildMetadata, #[error(transparent)] PartialVersion(#[from] PartialVersionError), } #[cfg(test)] mod test { use super::*; use snapbox::prelude::*; use snapbox::str; #[test] fn is_compatible_with_rustc() { let cases = &[ ("1", "1.70.0", true), ("1.30", "1.70.0", true), ("1.30.10", "1.70.0", true), ("1.70", "1.70.0", true), ("1.70.0", "1.70.0", true), ("1.70.1", "1.70.0", false), ("1.70", "1.70.0-nightly", true), ("1.70.0", "1.70.0-nightly", true), ("1.71", "1.70.0", false), ("2", "1.70.0", false), ]; let mut passed = true; for (msrv, rustc, expected) in cases { let msrv: RustVersion = msrv.parse().unwrap(); let rustc = PartialVersion::from(semver::Version::parse(rustc).unwrap()); if msrv.is_compatible_with(&rustc) != *expected { println!("failed: {msrv} is_compatible_with {rustc} == {expected}"); passed = false; } } assert!(passed); } #[test] fn is_compatible_with_workspace_msrv() { let cases = &[ ("1", "1", true), ("1", "1.70", true), ("1", "1.70.0", true), ("1.30", "1", false), ("1.30", "1.70", true), ("1.30", "1.70.0", true), ("1.30.10", "1", false), ("1.30.10", "1.70", true), ("1.30.10", "1.70.0", true), ("1.70", "1", false), ("1.70", "1.70", true), ("1.70", "1.70.0", true), ("1.70.0", "1", false), ("1.70.0", "1.70", true), ("1.70.0", "1.70.0", true), ("1.70.1", "1", false), ("1.70.1", "1.70", false), ("1.70.1", "1.70.0", false), ("1.71", "1", false), ("1.71", "1.70", false), ("1.71", "1.70.0", false), ("2", "1.70.0", false), ]; let mut passed = true; for (dep_msrv, ws_msrv, expected) in cases { let dep_msrv: RustVersion = dep_msrv.parse().unwrap(); let ws_msrv = ws_msrv.parse::().unwrap().to_partial(); if dep_msrv.is_compatible_with(&ws_msrv) != *expected { println!("failed: {dep_msrv} is_compatible_with {ws_msrv} == {expected}"); passed = false; } } assert!(passed); } #[test] fn parse_errors() { let cases = &[ // Disallow caret ( "^1.43", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), // Valid pre-release ( "1.43.0-beta.1", str![[r#"unexpected prerelease field, expected a version like "1.32""#]], ), // Bad pre-release ( "1.43-beta.1", str![[r#"unexpected prerelease field, expected a version like "1.32""#]], ), // Weird wildcard ( "x", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), ( "1.x", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), ( "1.1.x", str![[r#"unexpected version requirement, expected a version like "1.32""#]], ), // Non-sense ("foodaddle", str![[r#"expected a version like "1.32""#]]), ]; for (input, expected) in cases { let actual: Result = input.parse(); let actual = match actual { Ok(result) => format!("didn't fail: {result:?}"), Err(err) => err.to_string(), }; snapbox::assert_data_eq!(actual, expected.clone().raw()); } } } ================================================ FILE: crates/cargo-util-schemas/src/messages.rs ================================================ //! Schemas for JSON messages emitted by Cargo. use std::collections::BTreeMap; use std::path::PathBuf; /// File information of a package archive generated by `cargo package --list`. #[derive(Debug, serde::Serialize)] #[serde(rename_all = "snake_case")] pub struct PackageList { /// The Package ID Spec of the package. pub id: crate::core::PackageIdSpec, /// A map of relative paths in the archive to their detailed file information. pub files: BTreeMap, } /// Where the file is from. #[derive(Debug, serde::Serialize)] #[serde(rename_all = "snake_case", tag = "kind")] pub enum PackageFile { /// File being copied from another location. Copy { /// An absolute path to the actual file content path: PathBuf, }, /// File being generated during packaging Generate { /// An absolute path to the original file the generated one is based on. /// if any. #[serde(skip_serializing_if = "Option::is_none")] path: Option, }, } ================================================ FILE: crates/cargo-util-schemas/src/restricted_names.rs ================================================ //! Helpers for validating and checking names like package and crate names. type Result = std::result::Result; /// Error validating names in Cargo. #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct NameValidationError(#[from] ErrorKind); /// Non-public error kind for [`NameValidationError`]. #[non_exhaustive] #[derive(Debug, thiserror::Error)] enum ErrorKind { #[error("{0} cannot be empty")] Empty(&'static str), #[error("invalid character `{ch}` in {what}: `{name}`, {reason}")] InvalidCharacter { ch: char, what: &'static str, name: String, reason: &'static str, }, #[error( "profile name `{name}` is reserved\n{help}\n\ See https://doc.rust-lang.org/cargo/reference/profiles.html \ for more on configuring profiles." )] ProfileNameReservedKeyword { name: String, help: &'static str }, #[error("feature named `{0}` is not allowed to start with `dep:`")] FeatureNameStartsWithDepColon(String), } pub(crate) fn validate_package_name(name: &str) -> Result<()> { for part in name.split("::") { validate_name(part, "package name")?; } Ok(()) } pub(crate) fn validate_registry_name(name: &str) -> Result<()> { validate_name(name, "registry name") } pub(crate) fn validate_name(name: &str, what: &'static str) -> Result<()> { if name.is_empty() { return Err(ErrorKind::Empty(what).into()); } let mut chars = name.chars(); if let Some(ch) = chars.next() { if ch.is_digit(10) { // A specific error for a potentially common case. return Err(ErrorKind::InvalidCharacter { ch, what, name: name.into(), reason: "the name cannot start with a digit", } .into()); } if !(unicode_ident::is_xid_start(ch) || ch == '_') { return Err(ErrorKind::InvalidCharacter { ch, what, name: name.into(), reason: "the first character must be a Unicode XID start character \ (most letters or `_`)", } .into()); } } for ch in chars { if !(unicode_ident::is_xid_continue(ch) || ch == '-') { return Err(ErrorKind::InvalidCharacter { ch, what, name: name.into(), reason: "characters must be Unicode XID characters \ (numbers, `-`, `_`, or most letters)", } .into()); } } Ok(()) } /// Ensure a package name is [valid][validate_package_name] pub(crate) fn sanitize_package_name(name: &str, placeholder: char) -> String { let mut slug = String::new(); for part in name.split("::") { if !slug.is_empty() { slug.push_str("::"); } slug.push_str(&sanitize_name(part, placeholder)); } slug } pub(crate) fn sanitize_name(name: &str, placeholder: char) -> String { let mut slug = String::new(); let mut chars = name.chars(); while let Some(ch) = chars.next() { if (unicode_ident::is_xid_start(ch) || ch == '_') && !ch.is_digit(10) { slug.push(ch); break; } } while let Some(ch) = chars.next() { if unicode_ident::is_xid_continue(ch) || ch == '-' { slug.push(ch); } else { slug.push(placeholder); } } if slug.is_empty() { slug.push_str("package"); } slug } /// Validate dir-names and profile names according to RFC 2678. pub(crate) fn validate_profile_name(name: &str) -> Result<()> { if let Some(ch) = name .chars() .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-') { return Err(ErrorKind::InvalidCharacter { ch, what: "profile name", name: name.into(), reason: "allowed characters are letters, numbers, underscore, and hyphen", } .into()); } let lower_name = name.to_lowercase(); if lower_name == "debug" { return Err(ErrorKind::ProfileNameReservedKeyword { name: name.into(), help: "To configure the default development profile, \ use the name `dev` as in [profile.dev]", } .into()); } if lower_name == "build-override" { return Err(ErrorKind::ProfileNameReservedKeyword { name: name.into(), help: "To configure build dependency settings, use [profile.dev.build-override] \ and [profile.release.build-override]", } .into()); } // These are some arbitrary reservations. We have no plans to use // these, but it seems safer to reserve a few just in case we want to // add more built-in profiles in the future. We can also uses special // syntax like cargo:foo if needed. But it is unlikely these will ever // be used. if matches!( lower_name.as_str(), "build" | "check" | "clean" | "config" | "fetch" | "fix" | "install" | "metadata" | "package" | "publish" | "report" | "root" | "run" | "rust" | "rustc" | "rustdoc" | "target" | "tmp" | "uninstall" ) || lower_name.starts_with("cargo") { return Err(ErrorKind::ProfileNameReservedKeyword { name: name.into(), help: "Please choose a different name.", } .into()); } Ok(()) } pub(crate) fn validate_feature_name(name: &str) -> Result<()> { let what = "feature name"; if name.is_empty() { return Err(ErrorKind::Empty(what).into()); } if name.starts_with("dep:") { return Err(ErrorKind::FeatureNameStartsWithDepColon(name.into()).into()); } if name.contains('/') { return Err(ErrorKind::InvalidCharacter { ch: '/', what, name: name.into(), reason: "feature name is not allowed to contain slashes", } .into()); } let mut chars = name.chars(); if let Some(ch) = chars.next() { if !(unicode_ident::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) { return Err(ErrorKind::InvalidCharacter { ch, what, name: name.into(), reason: "the first character must be a Unicode XID start character or digit \ (most letters or `_` or `0` to `9`)", } .into()); } } for ch in chars { if !(unicode_ident::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') { return Err(ErrorKind::InvalidCharacter { ch, what, name: name.into(), reason: "characters must be Unicode XID characters, '-', `+`, or `.` \ (numbers, `+`, `-`, `_`, `.`, or most letters)", } .into()); } } Ok(()) } pub(crate) fn validate_path_base_name(name: &str) -> Result<()> { validate_name(name, "path base name") } #[cfg(test)] mod tests { use super::*; #[test] fn valid_feature_names() { assert!(validate_feature_name("c++17").is_ok()); assert!(validate_feature_name("128bit").is_ok()); assert!(validate_feature_name("_foo").is_ok()); assert!(validate_feature_name("feat-name").is_ok()); assert!(validate_feature_name("feat_name").is_ok()); assert!(validate_feature_name("foo.bar").is_ok()); assert!(validate_feature_name("").is_err()); assert!(validate_feature_name("+foo").is_err()); assert!(validate_feature_name("-foo").is_err()); assert!(validate_feature_name(".foo").is_err()); assert!(validate_feature_name("dep:bar").is_err()); assert!(validate_feature_name("foo/bar").is_err()); assert!(validate_feature_name("foo:bar").is_err()); assert!(validate_feature_name("foo?").is_err()); assert!(validate_feature_name("?foo").is_err()); assert!(validate_feature_name("ⒶⒷⒸ").is_err()); assert!(validate_feature_name("a¼").is_err()); assert!(validate_feature_name("").is_err()); } } ================================================ FILE: crates/cargo-util-schemas/src/schema.rs ================================================ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use toml::Value as TomlValue; #[derive(Serialize, Deserialize)] pub struct TomlValueWrapper(pub TomlValue); impl JsonSchema for TomlValueWrapper { fn schema_name() -> std::borrow::Cow<'static, str> { "TomlValue".into() } fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { // HACK: this is both more and less permissive than `TomlValue` but its close generator.subschema_for::().into() } } ================================================ FILE: crates/crates-io/Cargo.toml ================================================ [package] name = "crates-io" version = "0.40.19" rust-version = "1.94" # MSRV:1 edition.workspace = true license.workspace = true repository.workspace = true description = """ Helpers for interacting with crates.io """ [lib] name = "crates_io" path = "lib.rs" [dependencies] curl.workspace = true percent-encoding.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true url.workspace = true [lints] workspace = true ================================================ FILE: crates/crates-io/README.md ================================================ > This crate is maintained by the Cargo team for use by the wider > ecosystem. This crate follows semver compatibility for its APIs. ================================================ FILE: crates/crates-io/lib.rs ================================================ //! > This crate is maintained by the Cargo team for use by the wider //! > ecosystem. This crate follows semver compatibility for its APIs. use std::collections::BTreeMap; use std::fs::File; use std::io::prelude::*; use std::io::{Cursor, SeekFrom}; use std::time::Instant; use curl::easy::{Easy, List}; use percent_encoding::{NON_ALPHANUMERIC, percent_encode}; use serde::{Deserialize, Serialize}; use url::Url; pub type Result = std::result::Result; pub struct Registry { /// The base URL for issuing API requests. host: String, /// Optional authorization token. /// If None, commands requiring authorization will fail. token: Option, /// Curl handle for issuing requests. handle: Easy, /// Whether to include the authorization token with all requests. auth_required: bool, } #[derive(PartialEq, Clone, Copy)] pub enum Auth { Authorized, Unauthorized, } #[derive(Deserialize)] pub struct Crate { pub name: String, pub description: Option, pub max_version: String, } /// This struct is serialized as JSON and sent as metadata ahead of the crate /// tarball when publishing crates to a crate registry like crates.io. /// /// see #[derive(Serialize, Deserialize)] pub struct NewCrate { pub name: String, pub vers: String, pub deps: Vec, pub features: BTreeMap>, pub authors: Vec, pub description: Option, pub documentation: Option, pub homepage: Option, pub readme: Option, pub readme_file: Option, pub keywords: Vec, pub categories: Vec, pub license: Option, pub license_file: Option, pub repository: Option, pub badges: BTreeMap>, pub links: Option, pub rust_version: Option, } #[derive(Serialize, Deserialize)] pub struct NewCrateDependency { pub optional: bool, pub default_features: bool, pub name: String, pub features: Vec, pub version_req: String, pub target: Option, pub kind: String, #[serde(skip_serializing_if = "Option::is_none")] pub registry: Option, #[serde(skip_serializing_if = "Option::is_none")] pub explicit_name_in_toml: Option, #[serde(skip_serializing_if = "Option::is_none")] pub artifact: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub bindep_target: Option, #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub lib: bool, } #[derive(Deserialize)] pub struct User { pub id: u32, pub login: String, pub avatar: Option, pub email: Option, pub name: Option, } pub struct Warnings { pub invalid_categories: Vec, pub invalid_badges: Vec, pub other: Vec, } #[derive(Deserialize)] struct R { ok: bool, } #[derive(Deserialize)] struct OwnerResponse { ok: bool, msg: String, } #[derive(Deserialize)] struct ApiErrorList { errors: Vec, } #[derive(Deserialize)] struct ApiError { detail: String, } #[derive(Serialize)] struct OwnersReq<'a> { users: &'a [&'a str], } #[derive(Deserialize)] struct Users { users: Vec, } #[derive(Deserialize)] struct TotalCrates { total: u32, } #[derive(Deserialize)] struct Crates { crates: Vec, meta: TotalCrates, } /// Error returned when interacting with a registry. #[derive(Debug, thiserror::Error)] pub enum Error { /// Error from libcurl. #[error(transparent)] Curl(#[from] curl::Error), /// Error from serializing the request payload and deserializing the /// response body (like response body didn't match expected structure). #[error(transparent)] Json(#[from] serde_json::Error), /// Error from IO. Mostly from reading the tarball to upload. #[error("failed to seek tarball")] Io(#[from] std::io::Error), /// Response body was not valid utf8. #[error("invalid response body from server")] Utf8(#[from] std::string::FromUtf8Error), /// Error from API response containing JSON field `errors.details`. #[error( "the remote server responded with an error{}: {}", status(*code), errors.join(", "), )] Api { code: u32, headers: Vec, errors: Vec, }, /// Error from API response which didn't have pre-programmed `errors.details`. #[error( "failed to get a 200 OK response, got {code}\nheaders:\n\t{}\nbody:\n{body}", headers.join("\n\t"), )] Code { code: u32, headers: Vec, body: String, }, /// Reason why the token was invalid. #[error("{0}")] InvalidToken(&'static str), /// Server was unavailable and timed out. Happened when uploading a way /// too large tarball to crates.io. #[error( "Request timed out after 30 seconds. If you're trying to \ upload a crate it may be too large. If the crate is under \ 10MB in size, you can email help@crates.io for assistance.\n\ Total size was {0}." )] Timeout(u64), } impl Registry { /// Creates a new `Registry`. /// /// ## Example /// /// ```rust /// use curl::easy::Easy; /// use crates_io::Registry; /// /// let mut handle = Easy::new(); /// // If connecting to crates.io, a user-agent is required. /// handle.useragent("my_crawler (example.com/info)"); /// let mut reg = Registry::new_handle(String::from("https://crates.io"), None, handle, true); /// ``` pub fn new_handle( host: String, token: Option, handle: Easy, auth_required: bool, ) -> Registry { Registry { host, token, handle, auth_required, } } pub fn set_token(&mut self, token: Option) { self.token = token; } fn token(&self) -> Result<&str> { let token = self.token.as_ref().ok_or_else(|| { Error::InvalidToken("no upload token found, please run `cargo login`") })?; check_token(token)?; Ok(token) } pub fn host(&self) -> &str { &self.host } pub fn host_is_crates_io(&self) -> bool { is_url_crates_io(&self.host) } pub fn add_owners(&mut self, krate: &str, owners: &[&str]) -> Result { let body = serde_json::to_string(&OwnersReq { users: owners })?; let body = self.put(&format!("/crates/{}/owners", krate), body.as_bytes())?; assert!(serde_json::from_str::(&body)?.ok); Ok(serde_json::from_str::(&body)?.msg) } pub fn remove_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> { let body = serde_json::to_string(&OwnersReq { users: owners })?; let body = self.delete(&format!("/crates/{}/owners", krate), Some(body.as_bytes()))?; assert!(serde_json::from_str::(&body)?.ok); Ok(()) } pub fn list_owners(&mut self, krate: &str) -> Result> { let body = self.get(&format!("/crates/{}/owners", krate))?; Ok(serde_json::from_str::(&body)?.users) } pub fn publish(&mut self, krate: &NewCrate, mut tarball: &File) -> Result { let json = serde_json::to_string(krate)?; // Prepare the body. The format of the upload request is: // // // (metadata for the package) // // // NOTE: This can be replaced with `stream_len` if it is ever stabilized. // // This checks the length using seeking instead of metadata, because // on some filesystems, getting the metadata will fail because // the file was renamed in ops::package. let tarball_len = tarball.seek(SeekFrom::End(0))?; tarball.seek(SeekFrom::Start(0))?; let header = { let mut w = Vec::new(); w.extend(&(json.len() as u32).to_le_bytes()); w.extend(json.as_bytes().iter().cloned()); w.extend(&(tarball_len as u32).to_le_bytes()); w }; let size = tarball_len as usize + header.len(); let mut body = Cursor::new(header).chain(tarball); let url = format!("{}/api/v1/crates/new", self.host); self.handle.put(true)?; self.handle.url(&url)?; self.handle.in_filesize(size as u64)?; let mut headers = List::new(); headers.append("Accept: application/json")?; headers.append(&format!("Authorization: {}", self.token()?))?; self.handle.http_headers(headers)?; let started = Instant::now(); let body = self .handle(&mut |buf| body.read(buf).unwrap_or(0)) .map_err(|e| match e { Error::Code { code, .. } if code == 503 && started.elapsed().as_secs() >= 29 && self.host_is_crates_io() => { Error::Timeout(tarball_len) } _ => e.into(), })?; let response = if body.is_empty() { "{}".parse()? } else { body.parse::()? }; let invalid_categories: Vec = response .get("warnings") .and_then(|j| j.get("invalid_categories")) .and_then(|j| j.as_array()) .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect()) .unwrap_or_else(Vec::new); let invalid_badges: Vec = response .get("warnings") .and_then(|j| j.get("invalid_badges")) .and_then(|j| j.as_array()) .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect()) .unwrap_or_else(Vec::new); let other: Vec = response .get("warnings") .and_then(|j| j.get("other")) .and_then(|j| j.as_array()) .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect()) .unwrap_or_else(Vec::new); Ok(Warnings { invalid_categories, invalid_badges, other, }) } pub fn search(&mut self, query: &str, limit: u32) -> Result<(Vec, u32)> { let formatted_query = percent_encode(query.as_bytes(), NON_ALPHANUMERIC); let body = self.req( &format!("/crates?q={}&per_page={}", formatted_query, limit), None, Auth::Unauthorized, )?; let crates = serde_json::from_str::(&body)?; Ok((crates.crates, crates.meta.total)) } pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> { let body = self.delete(&format!("/crates/{}/{}/yank", krate, version), None)?; assert!(serde_json::from_str::(&body)?.ok); Ok(()) } pub fn unyank(&mut self, krate: &str, version: &str) -> Result<()> { let body = self.put(&format!("/crates/{}/{}/unyank", krate, version), &[])?; assert!(serde_json::from_str::(&body)?.ok); Ok(()) } fn put(&mut self, path: &str, b: &[u8]) -> Result { self.handle.put(true)?; self.req(path, Some(b), Auth::Authorized) } fn get(&mut self, path: &str) -> Result { self.handle.get(true)?; self.req(path, None, Auth::Authorized) } fn delete(&mut self, path: &str, b: Option<&[u8]>) -> Result { self.handle.custom_request("DELETE")?; self.req(path, b, Auth::Authorized) } fn req(&mut self, path: &str, body: Option<&[u8]>, authorized: Auth) -> Result { self.handle.url(&format!("{}/api/v1{}", self.host, path))?; let mut headers = List::new(); headers.append("Accept: application/json")?; if body.is_some() { headers.append("Content-Type: application/json")?; } if self.auth_required || authorized == Auth::Authorized { headers.append(&format!("Authorization: {}", self.token()?))?; } self.handle.http_headers(headers)?; match body { Some(mut body) => { self.handle.upload(true)?; self.handle.in_filesize(body.len() as u64)?; self.handle(&mut |buf| body.read(buf).unwrap_or(0)) .map_err(|e| e.into()) } None => self.handle(&mut |_| 0).map_err(|e| e.into()), } } fn handle(&mut self, read: &mut dyn FnMut(&mut [u8]) -> usize) -> Result { let mut headers = Vec::new(); let mut body = Vec::new(); { let mut handle = self.handle.transfer(); handle.read_function(|buf| Ok(read(buf)))?; handle.write_function(|data| { body.extend_from_slice(data); Ok(data.len()) })?; handle.header_function(|data| { // Headers contain trailing \r\n, trim them to make it easier // to work with. let s = String::from_utf8_lossy(data).trim().to_string(); // Don't let server sneak extra lines anywhere. if s.contains('\n') { return true; } headers.push(s); true })?; handle.perform()?; } let body = String::from_utf8(body)?; let errors = serde_json::from_str::(&body) .ok() .map(|s| s.errors.into_iter().map(|s| s.detail).collect::>()); match (self.handle.response_code()?, errors) { (0, None) => Ok(body), (code, None) if is_success(code) => Ok(body), (code, Some(errors)) => Err(Error::Api { code, headers, errors, }), (code, None) => Err(Error::Code { code, headers, body, }), } } } fn is_success(code: u32) -> bool { code >= 200 && code < 300 } fn status(code: u32) -> String { if is_success(code) { String::new() } else { let reason = reason(code); format!(" (status {code} {reason})") } } fn reason(code: u32) -> &'static str { // Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Status match code { 100 => "Continue", 101 => "Switching Protocol", 103 => "Early Hints", 200 => "OK", 201 => "Created", 202 => "Accepted", 203 => "Non-Authoritative Information", 204 => "No Content", 205 => "Reset Content", 206 => "Partial Content", 300 => "Multiple Choice", 301 => "Moved Permanently", 302 => "Found", 303 => "See Other", 304 => "Not Modified", 307 => "Temporary Redirect", 308 => "Permanent Redirect", 400 => "Bad Request", 401 => "Unauthorized", 402 => "Payment Required", 403 => "Forbidden", 404 => "Not Found", 405 => "Method Not Allowed", 406 => "Not Acceptable", 407 => "Proxy Authentication Required", 408 => "Request Timeout", 409 => "Conflict", 410 => "Gone", 411 => "Length Required", 412 => "Precondition Failed", 413 => "Payload Too Large", 414 => "URI Too Long", 415 => "Unsupported Media Type", 416 => "Request Range Not Satisfiable", 417 => "Expectation Failed", 429 => "Too Many Requests", 431 => "Request Header Fields Too Large", 500 => "Internal Server Error", 501 => "Not Implemented", 502 => "Bad Gateway", 503 => "Service Unavailable", 504 => "Gateway Timeout", _ => "", } } /// Returns `true` if the host of the given URL is "crates.io". pub fn is_url_crates_io(url: &str) -> bool { Url::parse(url) .map(|u| u.host_str() == Some("crates.io")) .unwrap_or(false) } /// Checks if a token is valid or malformed. /// /// This check is necessary to prevent sending tokens which create an invalid HTTP request. /// It would be easier to check just for alphanumeric tokens, but we can't be sure that all /// registries only create tokens in that format so that is as less restricted as possible. pub fn check_token(token: &str) -> Result<()> { if token.is_empty() { return Err(Error::InvalidToken("please provide a non-empty token")); } if token.bytes().all(|b| { // This is essentially the US-ASCII limitation of // https://www.rfc-editor.org/rfc/rfc9110#name-field-values. That is, // visible ASCII characters (0x21-0x7e), space, and tab. We want to be // able to pass this in an HTTP header without encoding. b >= 32 && b < 127 || b == b'\t' }) { Ok(()) } else { Err(Error::InvalidToken( "token contains invalid characters.\nOnly printable ISO-8859-1 characters \ are allowed as it is sent in a HTTPS header.", )) } } ================================================ FILE: crates/home/CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.5.11 - 2024-12-16 Note: 0.5.10 was not published. - Updated package metadata. [#13184](https://github.com/rust-lang/cargo/pull/13184) - Updated minimum Rust version to 1.81. [#13266](https://github.com/rust-lang/cargo/pull/13266) [#13324](https://github.com/rust-lang/cargo/pull/13324) [#14871](https://github.com/rust-lang/cargo/pull/14871) - Updated windows-sys to 0.59. [#14335](https://github.com/rust-lang/cargo/pull/14335) - Clarified support level of this crate (not intended for external use). [#14600](https://github.com/rust-lang/cargo/pull/14600) - Docs cleanup. [#14823]() - Add notice that this crate should not be used, and to use the standard library's `home_dir` instead. [#14939](https://github.com/rust-lang/cargo/pull/14939) ## 0.5.9 - 2023-12-15 - Replace SHGetFolderPathW with SHGetKnownFolderPath [#13173](https://github.com/rust-lang/cargo/pull/13173) - Update windows-sys to 0.52 [#13089](https://github.com/rust-lang/cargo/pull/13089) - Set MSRV to 1.70.0 [#12654](https://github.com/rust-lang/cargo/pull/12654) - Fixed & enhanced documentation. [#12047](https://github.com/rust-lang/cargo/pull/12047) ## 0.5.5 - 2023-04-25 - The `home` crate has migrated to the repository. [#11359](https://github.com/rust-lang/cargo/pull/11359) - Replaced the winapi dependency with windows-sys. [#11656](https://github.com/rust-lang/cargo/pull/11656) ## [0.5.4] - 2022-10-10 - Add `_with_env` variants of functions to support in-process threaded tests for rustup. ## [0.5.3] - 2020-01-07 Use Rust 1.36.0 as minimum Rust version. ## [0.5.2] - 2020-01-05 *YANKED since it cannot be built on Rust 1.36.0* ### Changed - Check for emptiness of `CARGO_HOME` and `RUSTUP_HOME` environment variables. - Windows: Use `SHGetFolderPath` to replace `GetUserProfileDirectory` syscall. * Remove `scopeguard` dependency. ## [0.5.1] - 2019-10-12 ### Changed - Disable unnecessary features for `scopeguard`. Thanks @mati865. ## [0.5.0] - 2019-08-21 ### Added - Add `home_dir` implementation for Windows UWP platforms. ### Fixed - Fix `rustup_home` implementation when `RUSTUP_HOME` is an absolute directory. - Fix `cargo_home` implementation when `CARGO_HOME` is an absolute directory. ### Removed - Remove support for `multirust` folder used in old version of `rustup`. [0.5.4]: https://github.com/brson/home/compare/v0.5.3...v0.5.4 [0.5.3]: https://github.com/brson/home/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/brson/home/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/brson/home/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/brson/home/compare/0.4.2...v0.5.0 ================================================ FILE: crates/home/Cargo.toml ================================================ [package] name = "home" version = "0.5.13" authors = ["Brian Anderson "] rust-version.workspace = true documentation = "https://docs.rs/home" edition.workspace = true include = [ "/src", "/Cargo.toml", "/CHANGELOG", "/LICENSE-*", "/README.md", ] license.workspace = true repository.workspace = true description = "Shared definitions of home directories." [target.'cfg(windows)'.dependencies] windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_System_Com"] } [lints] workspace = true ================================================ FILE: crates/home/README.md ================================================ [![Documentation](https://docs.rs/home/badge.svg)](https://docs.rs/home) [![crates.io](https://img.shields.io/crates/v/home.svg)](https://crates.io/crates/home) Canonical definitions of `home_dir`, `cargo_home`, and `rustup_home`. This provides the definition of `home_dir` used by Cargo and rustup, as well functions to find the correct value of `CARGO_HOME` and `RUSTUP_HOME`. The definition of [`home_dir`] provided by the standard library is incorrect because it considers the `HOME` environment variable on Windows. This causes surprising situations where a Rust program will behave differently depending on whether it is run under a Unix emulation environment like Cygwin or MinGW. Neither Cargo nor rustup use the standard library's definition - they use the definition here. **Note:** This has been fixed in Rust 1.85 to no longer use the `HOME` environment variable on Windows. If you are still using this crate for the purpose of getting a home directory, you are strongly encouraged to switch to using the standard library's [`home_dir`] instead. It is planned to have the deprecation notice removed in 1.87. This crate further provides two functions, `cargo_home` and `rustup_home`, which are the canonical way to determine the location that Cargo and rustup store their data. See [rust-lang/rust#43321]. > This crate is maintained by the Cargo team, primarily for use by Cargo and Rustup > and not intended for external use. This > crate may make major changes to its APIs or be deprecated without warning. [rust-lang/rust#43321]: https://github.com/rust-lang/rust/issues/43321 [`home_dir`]: https://doc.rust-lang.org/nightly/std/env/fn.home_dir.html ## License MIT OR Apache-2.0 ================================================ FILE: crates/home/src/env.rs ================================================ //! Lower-level utilities for mocking the process environment. use std::{ ffi::OsString, io, path::{Path, PathBuf}, }; /// Permits parameterizing the home functions via the _from variants - used for /// in-process unit testing by rustup. pub trait Env { /// Return the path to the users home dir, or None if any error occurs: /// see `home_inner`. fn home_dir(&self) -> Option; /// Return the current working directory. fn current_dir(&self) -> io::Result; /// Get an environment variable, as per `std::env::var_os`. fn var_os(&self, key: &str) -> Option; } /// Implements Env for the OS context, both Unix style and Windows. /// /// This is trait permits in-process testing by providing a control point to /// allow in-process divergence on what is normally process wide state. /// /// Implementations should be provided by whatever testing framework the caller /// is using. Code that is not performing in-process threaded testing requiring /// isolated rustup/cargo directories does not need this trait or the _from /// functions. pub struct OsEnv; impl Env for OsEnv { fn home_dir(&self) -> Option { crate::home_dir_inner() } fn current_dir(&self) -> io::Result { std::env::current_dir() } fn var_os(&self, key: &str) -> Option { std::env::var_os(key) } } pub const OS_ENV: OsEnv = OsEnv {}; /// Returns the path of the current user's home directory from [`Env::home_dir`]. pub fn home_dir_with_env(env: &dyn Env) -> Option { env.home_dir() } /// Variant of `cargo_home` where the environment source is parameterized. /// /// This is /// specifically to support in-process testing scenarios as environment /// variables and user home metadata are normally process global state. See the /// [`Env`] trait. pub fn cargo_home_with_env(env: &dyn Env) -> io::Result { let cwd = env.current_dir()?; cargo_home_with_cwd_env(env, &cwd) } /// Variant of `cargo_home_with_cwd` where the environment source is /// parameterized. /// /// This is specifically to support in-process testing scenarios /// as environment variables and user home metadata are normally process global /// state. See the `OsEnv` trait. pub fn cargo_home_with_cwd_env(env: &dyn Env, cwd: &Path) -> io::Result { match env.var_os("CARGO_HOME").filter(|h| !h.is_empty()) { Some(home) => { let home = PathBuf::from(home); if home.is_absolute() { Ok(home) } else { Ok(cwd.join(&home)) } } _ => home_dir_with_env(env) .map(|p| p.join(".cargo")) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "could not find cargo home dir")), } } /// Variant of `cargo_home_with_cwd` where the environment source is /// parameterized. /// /// This is specifically to support in-process testing scenarios /// as environment variables and user home metadata are normally process global /// state. See the `OsEnv` trait. pub fn rustup_home_with_env(env: &dyn Env) -> io::Result { let cwd = env.current_dir()?; rustup_home_with_cwd_env(env, &cwd) } /// Variant of `cargo_home_with_cwd` where the environment source is /// parameterized. /// /// This is specifically to support in-process testing scenarios /// as environment variables and user home metadata are normally process global /// state. See the `OsEnv` trait. pub fn rustup_home_with_cwd_env(env: &dyn Env, cwd: &Path) -> io::Result { match env.var_os("RUSTUP_HOME").filter(|h| !h.is_empty()) { Some(home) => { let home = PathBuf::from(home); if home.is_absolute() { Ok(home) } else { Ok(cwd.join(&home)) } } _ => home_dir_with_env(env) .map(|d| d.join(".rustup")) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "could not find rustup home dir")), } } ================================================ FILE: crates/home/src/lib.rs ================================================ //! Canonical definitions of `home_dir`, `cargo_home`, and `rustup_home`. //! //! The definition of `home_dir` provided by the standard library is //! incorrect because it considers the `HOME` environment variable on //! Windows. This causes surprising situations where a Rust program //! will behave differently depending on whether it is run under a //! Unix emulation environment like Cygwin or MinGW. Neither Cargo nor //! rustup use the standard libraries definition - they use the //! definition here. //! //! This crate provides two additional functions, `cargo_home` and //! `rustup_home`, which are the canonical way to determine the //! location that Cargo and rustup use to store their data. //! The `env` module contains utilities for mocking the process environment //! by Cargo and rustup. //! //! See also this [discussion]. //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo and Rustup //! > and not intended for external use. This //! > crate may make major changes to its APIs or be deprecated without warning. //! //! [discussion]: https://github.com/rust-lang/rust/pull/46799#issuecomment-361156935 #![allow(clippy::disallowed_methods)] pub mod env; #[cfg(target_os = "windows")] mod windows; use std::io; use std::path::{Path, PathBuf}; /// Returns the path of the current user's home directory using environment /// variables or OS-specific APIs. /// /// # Unix /// /// Returns the value of the `HOME` environment variable if it is set /// **even** if it is an empty string. Otherwise, it tries to determine the /// home directory by invoking the [`getpwuid_r`][getpwuid] function with /// the UID of the current user. /// /// [getpwuid]: https://linux.die.net/man/3/getpwuid_r /// /// # Windows /// /// Returns the value of the `USERPROFILE` environment variable if it is set /// **and** it is not an empty string. Otherwise, it tries to determine the /// home directory by invoking the [`SHGetKnownFolderPath`][shgkfp] function with /// [`FOLDERID_Profile`][knownfolderid]. /// /// [shgkfp]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath /// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid /// /// # Examples /// /// ``` /// match home::home_dir() { /// Some(path) if !path.as_os_str().is_empty() => println!("{}", path.display()), /// _ => println!("Unable to get your home dir!"), /// } /// ``` pub fn home_dir() -> Option { env::home_dir_with_env(&env::OS_ENV) } #[cfg(windows)] use windows::home_dir_inner; #[cfg(unix)] fn home_dir_inner() -> Option { #[allow(deprecated)] std::env::home_dir() } /// Returns the storage directory used by Cargo, often known as /// `.cargo` or `CARGO_HOME`. /// /// It returns one of the following values, in this order of /// preference: /// /// - The value of the `CARGO_HOME` environment variable, if it is /// an absolute path. /// - The value of the current working directory joined with the value /// of the `CARGO_HOME` environment variable, if `CARGO_HOME` is a /// relative directory. /// - The `.cargo` directory in the user's home directory, as reported /// by the `home_dir` function. /// /// # Errors /// /// This function fails if it fails to retrieve the current directory, /// or if the home directory cannot be determined. /// /// # Examples /// /// ``` /// match home::cargo_home() { /// Ok(path) => println!("{}", path.display()), /// Err(err) => eprintln!("Cannot get your cargo home dir: {:?}", err), /// } /// ``` pub fn cargo_home() -> io::Result { env::cargo_home_with_env(&env::OS_ENV) } /// Returns the storage directory used by Cargo within `cwd`. /// For more details, see [`cargo_home`](fn.cargo_home.html). pub fn cargo_home_with_cwd(cwd: &Path) -> io::Result { env::cargo_home_with_cwd_env(&env::OS_ENV, cwd) } /// Returns the storage directory used by rustup, often known as /// `.rustup` or `RUSTUP_HOME`. /// /// It returns one of the following values, in this order of /// preference: /// /// - The value of the `RUSTUP_HOME` environment variable, if it is /// an absolute path. /// - The value of the current working directory joined with the value /// of the `RUSTUP_HOME` environment variable, if `RUSTUP_HOME` is a /// relative directory. /// - The `.rustup` directory in the user's home directory, as reported /// by the `home_dir` function. /// /// # Errors /// /// This function fails if it fails to retrieve the current directory, /// or if the home directory cannot be determined. /// /// # Examples /// /// ``` /// match home::rustup_home() { /// Ok(path) => println!("{}", path.display()), /// Err(err) => eprintln!("Cannot get your rustup home dir: {:?}", err), /// } /// ``` pub fn rustup_home() -> io::Result { env::rustup_home_with_env(&env::OS_ENV) } /// Returns the storage directory used by rustup within `cwd`. /// For more details, see [`rustup_home`](fn.rustup_home.html). pub fn rustup_home_with_cwd(cwd: &Path) -> io::Result { env::rustup_home_with_cwd_env(&env::OS_ENV, cwd) } ================================================ FILE: crates/home/src/windows.rs ================================================ use std::env; use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; use std::path::PathBuf; use std::ptr; use std::slice; use windows_sys::Win32::Foundation::S_OK; use windows_sys::Win32::System::Com::CoTaskMemFree; use windows_sys::Win32::UI::Shell::{FOLDERID_Profile, KF_FLAG_DONT_VERIFY, SHGetKnownFolderPath}; pub fn home_dir_inner() -> Option { env::var_os("USERPROFILE") .filter(|s| !s.is_empty()) .map(PathBuf::from) .or_else(home_dir_crt) } #[cfg(not(target_vendor = "uwp"))] fn home_dir_crt() -> Option { unsafe { let mut path = ptr::null_mut(); match SHGetKnownFolderPath( &FOLDERID_Profile, KF_FLAG_DONT_VERIFY as u32, std::ptr::null_mut(), &mut path, ) { S_OK => { let path_slice = slice::from_raw_parts(path, wcslen(path)); let s = OsString::from_wide(&path_slice); CoTaskMemFree(path.cast()); Some(PathBuf::from(s)) } _ => { // Free any allocated memory even on failure. A null ptr is a no-op for `CoTaskMemFree`. CoTaskMemFree(path.cast()); None } } } } #[cfg(target_vendor = "uwp")] fn home_dir_crt() -> Option { None } unsafe extern "C" { fn wcslen(buf: *const u16) -> usize; } #[cfg(not(target_vendor = "uwp"))] #[cfg(test)] mod tests { use super::home_dir_inner; use std::env; use std::ops::Deref; use std::path::{Path, PathBuf}; #[test] fn test_with_without() { let olduserprofile = env::var_os("USERPROFILE").unwrap(); unsafe { env::remove_var("HOME"); env::remove_var("USERPROFILE"); } assert_eq!(home_dir_inner(), Some(PathBuf::from(olduserprofile))); let home = Path::new(r"C:\Users\foo tar baz"); unsafe { env::set_var("HOME", home.as_os_str()); } assert_ne!(home_dir_inner().as_ref().map(Deref::deref), Some(home)); unsafe { env::set_var("USERPROFILE", home.as_os_str()); } assert_eq!(home_dir_inner().as_ref().map(Deref::deref), Some(home)); } } ================================================ FILE: crates/mdman/Cargo.toml ================================================ [package] name = "mdman" version = "0.0.0" edition.workspace = true license.workspace = true description = "Creates a man page from markdown." publish = false [dependencies] anyhow.workspace = true handlebars.workspace = true pulldown-cmark.workspace = true same-file.workspace = true serde_json.workspace = true url.workspace = true [dev-dependencies] snapbox.workspace = true [lints] workspace = true ================================================ FILE: crates/mdman/README.md ================================================ # mdman mdman is a small utility for creating man pages from markdown text files. > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use (except as a transitive dependency). This > crate may make major changes to its APIs or be deprecated without warning. ## Usage See the [man page](doc/out/mdman.md) generated by this tool. ================================================ FILE: crates/mdman/doc/mdman.md ================================================ # mdman(1) ## NAME mdman - Converts markdown to a man page ## SYNOPSIS `mdman` [_options_] `-t` _type_ `-o` _outdir_ _sources..._ ## DESCRIPTION Converts a markdown file to a man page. The source file is first processed as a [handlebars](https://handlebarsjs.com/) template. Then, it is processed as markdown into the target format. This supports different output formats, such as troff or plain text. Every man page should start with a level-1 header with the man name and section, such as `# mdman(1)`. The handlebars template has several special tags to assist with generating the man page: {{{{raw}}}} - Every block of command-line options must be wrapped between `{{#options}}` and `{{/options}}` tags. This tells the processor where the options start and end. - Each option must be expressed with a `{{#option}}` block. The parameters to the block are a sequence of strings indicating the option. For example, ```{{#option "`-p` _spec_..." "`--package` _spec_..."}}``` is an option that has two different forms. The text within the string is processed as markdown. It is recommended to use formatting similar to this example. The content of the `{{#option}}` block should contain a detailed description of the option. Use the `{{/option}}` tag to end the option block. - References to other man pages should use the `{{man name section}}` expression. For example, `{{man "mdman" 1}}` will generate a reference to the `mdman(1)` man page. For non-troff output, the `--man` option will tell `mdman` how to create links to the man page. If there is no matching `--man` option, then it links to a file named _name_`.md` in the same directory. - Variables can be set with `{{*set name="value"}}`. These variables can then be referenced with `{{name}}` expressions. - Partial templates should be placed in a directory named `includes` next to the source file. Templates can be included with an expression like `{{> template-name}}`. - Other helpers include: - `{{lower value}}` Converts the given value to lowercase. {{{{/raw}}}} ## OPTIONS {{#options}} {{#option "`-t` _type_"}} Specifies the output type. The following output types are supported: - `man` — A troff-style man page. Outputs with a numbered extension (like `.1`) matching the man page section. - `md` — A markdown file, after all handlebars processing has been finished. Outputs with the `.md` extension. - `txt` — A text file, rendered for situations where a man page viewer isn't available. Outputs with the `.txt` extension. {{/option}} {{#option "`-o` _outdir_"}} Specifies the directory where to save the output. {{/option}} {{#option "`--url` _base_url_"}} Specifies a base URL to use for relative URLs within the document. Any relative URL will be joined with this URL. {{/option}} {{#option "`--man` _name_`:`_section_`=`_url_"}} Specifies a URL to use for the given man page. When the `\{{man name section}}` expression is used, the given URL will be inserted as a link. This may be specified multiple times. If a man page reference does not have a matching `--man` entry, then a relative link to a file named _name_`.md` will be used. {{/option}} {{#option "_sources..._"}} The source input filename, may be specified multiple times. {{/option}} {{/options}} ## EXAMPLES 1. Convert the given documents to man pages: mdman -t man -o doc doc/mdman.md ================================================ FILE: crates/mdman/doc/out/mdman.1 ================================================ '\" t .TH "MDMAN" "1" .nh .ad l .ss \n[.ss] 0 .SH "NAME" mdman \- Converts markdown to a man page .SH "SYNOPSIS" \fBmdman\fR [\fIoptions\fR] \fB\-t\fR \fItype\fR \fB\-o\fR \fIoutdir\fR \fIsources\[u2026]\fR .SH "DESCRIPTION" Converts a markdown file to a man page. .sp The source file is first processed as a \fIhandlebars\fR template. Then, it is processed as markdown into the target format. This supports different output formats, such as troff or plain text. .sp Every man page should start with a level\-1 header with the man name and section, such as \fB# mdman(1)\fR\&. .sp The handlebars template has several special tags to assist with generating the man page: .sp .RS 4 \h'-04'\(bu\h'+03'Every block of command\-line options must be wrapped between \fB{{#options}}\fR and \fB{{/options}}\fR tags. This tells the processor where the options start and end. .RE .sp .RS 4 \h'-04'\(bu\h'+03'Each option must be expressed with a \fB{{#option}}\fR block. The parameters to the block are a sequence of strings indicating the option. For example, \fB{{#option "`\-p` _spec_..." "`\-\-package` _spec_..."}}\fR is an option that has two different forms. The text within the string is processed as markdown. It is recommended to use formatting similar to this example. .sp The content of the \fB{{#option}}\fR block should contain a detailed description of the option. .sp Use the \fB{{/option}}\fR tag to end the option block. .RE .sp .RS 4 \h'-04'\(bu\h'+03'References to other man pages should use the \fB{{man name section}}\fR expression. For example, \fB{{man "mdman" 1}}\fR will generate a reference to the \fBmdman(1)\fR man page. For non\-troff output, the \fB\-\-man\fR option will tell \fBmdman\fR how to create links to the man page. If there is no matching \fB\-\-man\fR option, then it links to a file named \fIname\fR\fB\&.md\fR in the same directory. .RE .sp .RS 4 \h'-04'\(bu\h'+03'Variables can be set with \fB{{*set name="value"}}\fR\&. These variables can then be referenced with \fB{{name}}\fR expressions. .RE .sp .RS 4 \h'-04'\(bu\h'+03'Partial templates should be placed in a directory named \fBincludes\fR next to the source file. Templates can be included with an expression like \fB{{> template\-name}}\fR\&. .RE .sp .RS 4 \h'-04'\(bu\h'+03'Other helpers include: .sp .RS 4 \h'-04'\(bu\h'+03'\fB{{lower value}}\fR Converts the given value to lowercase. .RE .RE .SH "OPTIONS" .sp \fB\-t\fR \fItype\fR .RS 4 Specifies the output type. The following output types are supported: .sp .RS 4 \h'-04'\(bu\h'+03'\fBman\fR \[em] A troff\-style man page. Outputs with a numbered extension (like \fB\&.1\fR) matching the man page section. .RE .sp .RS 4 \h'-04'\(bu\h'+03'\fBmd\fR \[em] A markdown file, after all handlebars processing has been finished. Outputs with the \fB\&.md\fR extension. .RE .sp .RS 4 \h'-04'\(bu\h'+03'\fBtxt\fR \[em] A text file, rendered for situations where a man page viewer isn\[cq]t available. Outputs with the \fB\&.txt\fR extension. .RE .RE .sp \fB\-o\fR \fIoutdir\fR .RS 4 Specifies the directory where to save the output. .RE .sp \fB\-\-url\fR \fIbase_url\fR .RS 4 Specifies a base URL to use for relative URLs within the document. Any relative URL will be joined with this URL. .RE .sp \fB\-\-man\fR \fIname\fR\fB:\fR\fIsection\fR\fB=\fR\fIurl\fR .RS 4 Specifies a URL to use for the given man page. When the \fB{{man name section}}\fR expression is used, the given URL will be inserted as a link. This may be specified multiple times. If a man page reference does not have a matching \fB\-\-man\fR entry, then a relative link to a file named \fIname\fR\fB\&.md\fR will be used. .RE .sp \fIsources\[u2026]\fR .RS 4 The source input filename, may be specified multiple times. .RE .SH "EXAMPLES" .sp .RS 4 \h'-04' 1.\h'+01'Convert the given documents to man pages: .sp .RS 4 .nf mdman \-t man \-o doc doc/mdman.md .fi .RE .RE ================================================ FILE: crates/mdman/doc/out/mdman.md ================================================ # mdman(1) ## NAME mdman - Converts markdown to a man page ## SYNOPSIS `mdman` [_options_] `-t` _type_ `-o` _outdir_ _sources..._ ## DESCRIPTION Converts a markdown file to a man page. The source file is first processed as a [handlebars](https://handlebarsjs.com/) template. Then, it is processed as markdown into the target format. This supports different output formats, such as troff or plain text. Every man page should start with a level-1 header with the man name and section, such as `# mdman(1)`. The handlebars template has several special tags to assist with generating the man page: - Every block of command-line options must be wrapped between `{{#options}}` and `{{/options}}` tags. This tells the processor where the options start and end. - Each option must be expressed with a `{{#option}}` block. The parameters to the block are a sequence of strings indicating the option. For example, ```{{#option "`-p` _spec_..." "`--package` _spec_..."}}``` is an option that has two different forms. The text within the string is processed as markdown. It is recommended to use formatting similar to this example. The content of the `{{#option}}` block should contain a detailed description of the option. Use the `{{/option}}` tag to end the option block. - References to other man pages should use the `{{man name section}}` expression. For example, `{{man "mdman" 1}}` will generate a reference to the `mdman(1)` man page. For non-troff output, the `--man` option will tell `mdman` how to create links to the man page. If there is no matching `--man` option, then it links to a file named _name_`.md` in the same directory. - Variables can be set with `{{*set name="value"}}`. These variables can then be referenced with `{{name}}` expressions. - Partial templates should be placed in a directory named `includes` next to the source file. Templates can be included with an expression like `{{> template-name}}`. - Other helpers include: - `{{lower value}}` Converts the given value to lowercase. ## OPTIONS

-t type

Specifies the output type. The following output types are supported:

  • man — A troff-style man page. Outputs with a numbered extension (like .1) matching the man page section.
  • md — A markdown file, after all handlebars processing has been finished. Outputs with the .md extension.
  • txt — A text file, rendered for situations where a man page viewer isn’t available. Outputs with the .txt extension.
-o outdir

Specifies the directory where to save the output.

--url base_url

Specifies a base URL to use for relative URLs within the document. Any relative URL will be joined with this URL.

--man name:section=url

Specifies a URL to use for the given man page. When the {{man name section}} expression is used, the given URL will be inserted as a link. This may be specified multiple times. If a man page reference does not have a matching --man entry, then a relative link to a file named name.md will be used.

sources…

The source input filename, may be specified multiple times.

## EXAMPLES 1. Convert the given documents to man pages: mdman -t man -o doc doc/mdman.md ================================================ FILE: crates/mdman/doc/out/mdman.txt ================================================ MDMAN(1) NAME mdman - Converts markdown to a man page SYNOPSIS mdman [options] -t type -o outdir sources… DESCRIPTION Converts a markdown file to a man page. The source file is first processed as a handlebars template. Then, it is processed as markdown into the target format. This supports different output formats, such as troff or plain text. Every man page should start with a level-1 header with the man name and section, such as # mdman(1). The handlebars template has several special tags to assist with generating the man page: o Every block of command-line options must be wrapped between {{#options}} and {{/options}} tags. This tells the processor where the options start and end. o Each option must be expressed with a {{#option}} block. The parameters to the block are a sequence of strings indicating the option. For example, {{#option "`-p` _spec_..." "`--package` _spec_..."}} is an option that has two different forms. The text within the string is processed as markdown. It is recommended to use formatting similar to this example. The content of the {{#option}} block should contain a detailed description of the option. Use the {{/option}} tag to end the option block. o References to other man pages should use the {{man name section}} expression. For example, {{man "mdman" 1}} will generate a reference to the mdman(1) man page. For non-troff output, the --man option will tell mdman how to create links to the man page. If there is no matching --man option, then it links to a file named name.md in the same directory. o Variables can be set with {{*set name="value"}}. These variables can then be referenced with {{name}} expressions. o Partial templates should be placed in a directory named includes next to the source file. Templates can be included with an expression like {{> template-name}}. o Other helpers include: o {{lower value}} Converts the given value to lowercase. OPTIONS -t type Specifies the output type. The following output types are supported: o man — A troff-style man page. Outputs with a numbered extension (like .1) matching the man page section. o md — A markdown file, after all handlebars processing has been finished. Outputs with the .md extension. o txt — A text file, rendered for situations where a man page viewer isn’t available. Outputs with the .txt extension. -o outdir Specifies the directory where to save the output. --url base_url Specifies a base URL to use for relative URLs within the document. Any relative URL will be joined with this URL. --man name:section=url Specifies a URL to use for the given man page. When the {{man name section}} expression is used, the given URL will be inserted as a link. This may be specified multiple times. If a man page reference does not have a matching --man entry, then a relative link to a file named name.md will be used. sources… The source input filename, may be specified multiple times. EXAMPLES 1. Convert the given documents to man pages: mdman -t man -o doc doc/mdman.md ================================================ FILE: crates/mdman/src/format/man.rs ================================================ //! Man-page formatter. use crate::EventIter; use crate::util::{header_text, parse_name_and_section}; use anyhow::{Error, bail}; use pulldown_cmark::{Alignment, Event, HeadingLevel, LinkType, Tag, TagEnd}; use std::fmt::Write; use url::Url; pub struct ManFormatter { url: Option, } impl ManFormatter { pub fn new(url: Option) -> ManFormatter { ManFormatter { url } } } impl super::Formatter for ManFormatter { fn render(&self, input: &str) -> Result { ManRenderer::render(input, self.url.clone()) } fn render_options_start(&self) -> &'static str { // Tell pulldown_cmark to ignore this. // This will be stripped out later. " &'static str { "]]>" } fn render_option( &self, params: &[&str], block: &str, _man_name: &str, ) -> Result { let rendered_options = params .iter() .map(|param| { let r = self.render(param)?; Ok(r.trim().trim_start_matches(".sp").to_string()) }) .collect::, Error>>()?; let rendered_block = self.render(block)?; let rendered_block = rendered_block.trim().trim_start_matches(".sp").trim(); // .RS = move left margin to right 4. // .RE = move margin back one level. Ok(format!( "\n.sp\n{}\n.RS 4\n{}\n.RE\n", rendered_options.join(", "), rendered_block )) } fn linkify_man_to_md(&self, name: &str, section: u8) -> Result { Ok(format!("`{}`({})", name, section)) } } #[derive(Copy, Clone)] enum Font { Bold, Italic, } impl Font { fn str_from_stack(font_stack: &[Font]) -> &'static str { let has_bold = font_stack.iter().any(|font| matches!(font, Font::Bold)); let has_italic = font_stack.iter().any(|font| matches!(font, Font::Italic)); match (has_bold, has_italic) { (false, false) => "\\fR", // roman (normal) (false, true) => "\\fI", // italic (true, false) => "\\fB", // bold (true, true) => "\\f(BI", // bold italic } } } struct ManRenderer<'e> { output: String, parser: EventIter<'e>, font_stack: Vec, } impl<'e> ManRenderer<'e> { fn render(input: &str, url: Option) -> Result { let parser = crate::md_parser(input, url); let output = String::with_capacity(input.len() * 3 / 2); let mut mr = ManRenderer { parser, output, font_stack: Vec::new(), }; mr.push_man()?; Ok(mr.output) } fn push_man(&mut self) -> Result<(), Error> { // If this is true, this is inside a cdata block used for hiding // content from pulldown_cmark. let mut in_cdata = false; // The current list stack. None if unordered, Some if ordered with the // given number as the current index. let mut list: Vec> = Vec::new(); // Used in some cases where spacing isn't desired. let mut suppress_paragraph = false; let mut table_cell_index = 0; let mut last_seen_link_data = None; while let Some((event, range)) = self.parser.next() { let this_suppress_paragraph = suppress_paragraph; suppress_paragraph = false; match event { Event::Start(tag) => { match tag { Tag::Paragraph => { if !this_suppress_paragraph { self.flush(); self.output.push_str(".sp\n"); } } Tag::Heading { level, .. } => { if level == HeadingLevel::H1 { self.push_top_header()?; } else if level == HeadingLevel::H2 { // Section header let text = header_text(&mut self.parser)?; self.flush(); write!(self.output, ".SH \"{}\"\n", text)?; suppress_paragraph = true; } else { // Subsection header let text = header_text(&mut self.parser)?; self.flush(); write!(self.output, ".SS \"{}\"\n", text)?; suppress_paragraph = true; } } Tag::BlockQuote(..) => { self.flush(); // .RS = move left margin over 3 // .ll = shrink line length self.output.push_str(".RS 3\n.ll -5\n.sp\n"); suppress_paragraph = true; } Tag::CodeBlock(_kind) => { // space down, indent 4, no-fill mode self.flush(); self.output.push_str(".sp\n.RS 4\n.nf\n"); } Tag::List(start) => list.push(start), Tag::Item => { // Note: This uses explicit movement instead of .IP // because the spacing on .IP looks weird to me. // space down, indent 4 self.flush(); self.output.push_str(".sp\n.RS 4\n"); match list.last_mut().expect("item must have list start") { // Ordered list. Some(n) => { // move left 4, output the list index number, move right 1. write!(self.output, "\\h'-04' {}.\\h'+01'", n)?; *n += 1; } // Unordered list. None => self.output.push_str("\\h'-04'\\(bu\\h'+03'"), } suppress_paragraph = true; } Tag::FootnoteDefinition(_label) => unimplemented!(), Tag::Table(alignment) => { // Table start // allbox = draw a box around all the cells // tab(:) = Use `:` to separate cell data (instead of tab) // ; = end of options self.output.push_str( "\n.TS\n\ allbox tab(:);\n", ); let alignments: Vec<_> = alignment .iter() .map(|a| match a { Alignment::Left | Alignment::None => "lt", Alignment::Center => "ct", Alignment::Right => "rt", }) .collect(); self.output.push_str(&alignments.join(" ")); self.output.push_str(".\n"); table_cell_index = 0; } Tag::TableHead => { table_cell_index = 0; } Tag::TableRow => { table_cell_index = 0; self.output.push('\n'); } Tag::TableCell => { if table_cell_index != 0 { // Separator between columns. self.output.push(':'); } // Start a text block. self.output.push_str("T{\n"); table_cell_index += 1 } Tag::Emphasis => self.push_font(Font::Italic), Tag::Strong => self.push_font(Font::Bold), // Strikethrough isn't usually supported for TTY. Tag::Strikethrough => self.output.push_str("~~"), Tag::Link { link_type, dest_url, .. } => { last_seen_link_data = Some((link_type.clone(), dest_url.to_owned())); if dest_url.starts_with('#') { // In a man page, page-relative anchors don't // have much meaning. continue; } match link_type { LinkType::Autolink | LinkType::Email => { // The text is a copy of the URL, which is not needed. match self.parser.next() { Some((Event::Text(_), _range)) => {} _ => bail!("expected text after autolink"), } } LinkType::Inline | LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => { self.push_font(Font::Italic); } // This is currently unused. This is only // emitted with a broken link callback, but I // felt it is too annoying to escape `[` in // option descriptions. LinkType::ReferenceUnknown | LinkType::CollapsedUnknown | LinkType::ShortcutUnknown => { bail!( "link with missing reference `{}` located at offset {}", dest_url, range.start ); } LinkType::WikiLink { .. } => { panic!("wikilink unsupported"); } } } Tag::Image { .. } => { bail!("images are not currently supported") } Tag::HtmlBlock { .. } | Tag::MetadataBlock { .. } | Tag::DefinitionList | Tag::DefinitionListTitle | Tag::DefinitionListDefinition | Tag::Superscript | Tag::Subscript => {} } } Event::End(tag_end) => { match &tag_end { TagEnd::Paragraph => self.flush(), TagEnd::Heading(..) => {} TagEnd::BlockQuote(..) => { self.flush(); // restore left margin, restore line length self.output.push_str(".br\n.RE\n.ll\n"); } TagEnd::CodeBlock => { self.flush(); // Restore fill mode, move margin back one level. self.output.push_str(".fi\n.RE\n"); } TagEnd::List(_) => { list.pop(); } TagEnd::Item => { self.flush(); // Move margin back one level. self.output.push_str(".RE\n"); } TagEnd::FootnoteDefinition => {} TagEnd::Table => { // Table end // I don't know why, but the .sp is needed to provide // space with the following content. self.output.push_str("\n.TE\n.sp\n"); } TagEnd::TableHead => {} TagEnd::TableRow => {} TagEnd::TableCell => { // End text block. self.output.push_str("\nT}"); } TagEnd::Emphasis | TagEnd::Strong => self.pop_font(), TagEnd::Strikethrough => self.output.push_str("~~"), TagEnd::Link => { if let Some((link_type, ref dest_url)) = last_seen_link_data { if dest_url.starts_with('#') { continue; } match link_type { LinkType::Autolink | LinkType::Email => {} LinkType::Inline | LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => { self.pop_font(); self.output.push(' '); } _ => { panic!("unexpected tag {:?}", tag_end); } } write!(self.output, "<{}>", escape(&dest_url)?)?; } } TagEnd::Image | TagEnd::HtmlBlock | TagEnd::MetadataBlock(..) | TagEnd::DefinitionListDefinition | TagEnd::DefinitionListTitle | TagEnd::DefinitionList | TagEnd::Superscript | TagEnd::Subscript => {} } } Event::Text(t) => { self.output.push_str(&escape(&t)?); } Event::Code(t) => { self.push_font(Font::Bold); self.output.push_str(&escape(&t)?); self.pop_font(); } Event::Html(t) => { if t.starts_with("") { in_cdata = false; } else if !t.trim().is_empty() { self.output.push_str(&t); } } else { self.output.push_str(&escape(&t)?); } } Event::FootnoteReference(_t) => {} Event::SoftBreak => self.output.push('\n'), Event::HardBreak => { self.flush(); self.output.push_str(".br\n"); } Event::Rule => { self.flush(); // \l' **length** ' Draw horizontal line (default underscore). // \n(.lu Gets value from register "lu" (current line length) self.output.push_str("\\l'\\n(.lu'\n"); } Event::TaskListMarker(_b) => unimplemented!(), Event::InlineHtml(..) => unimplemented!(), Event::InlineMath(..) => unimplemented!(), Event::DisplayMath(..) => unimplemented!(), } } Ok(()) } fn flush(&mut self) { if !self.output.ends_with('\n') { self.output.push('\n'); } } /// Switch to the given font. /// /// Because the troff sequence `\fP` for switching to the "previous" font /// doesn't support nesting, this needs to emulate it here. This is needed /// for situations like **hi _there_**. fn push_font(&mut self, font: Font) { self.font_stack.push(font); self.output.push_str(Font::str_from_stack(&self.font_stack)); } fn pop_font(&mut self) { self.font_stack.pop(); self.output.push_str(Font::str_from_stack(&self.font_stack)); } /// Parse and render the first top-level header of the document. fn push_top_header(&mut self) -> Result<(), Error> { // This enables the tbl preprocessor for tables. // This seems to be enabled by default on every modern system I could // find, but it doesn't seem to hurt to enable this. self.output.push_str("'\\\" t\n"); // Extract the name of the man page. let text = header_text(&mut self.parser)?; let (name, section) = parse_name_and_section(&text)?; // .TH = Table header // .nh = disable hyphenation // .ad l = Left-adjust mode (disable justified). // .ss sets sentence_space_size to 0 (prevents double spaces after . // if . is last on the line) write!( self.output, ".TH \"{}\" \"{}\"\n\ .nh\n\ .ad l\n\ .ss \\n[.ss] 0\n", escape(&name.to_uppercase())?, section )?; Ok(()) } } #[allow(clippy::collapsible_str_replace)] fn escape(s: &str) -> Result { // Note: Possible source on output escape sequences: https://man7.org/linux/man-pages/man7/groff_char.7.html. // Otherwise, use generic escaping in the form `\[u1EE7]` or `\[u1F994]`. let mut replaced = s .replace('\\', "\\(rs") .replace('-', "\\-") .replace('\u{00A0}', "\\ ") // non-breaking space (non-stretchable) .replace('–', "\\[en]") // \u{2013} en-dash .replace('—', "\\[em]") // \u{2014} em-dash .replace('‘', "\\[oq]") // \u{2018} left single quote .replace('’', "\\[cq]") // \u{2019} right single quote or apostrophe .replace('“', "\\[lq]") // \u{201C} left double quote .replace('”', "\\[rq]") // \u{201D} right double quote .replace('…', "\\[u2026]") // \u{2026} ellipsis .replace('│', "|") // \u{2502} box drawing light vertical (could use \[br]) .replace('├', "|") // \u{251C} box drawings light vertical and right .replace('└', "`") // \u{2514} box drawings light up and right .replace('─', "\\-") // \u{2500} box drawing light horizontal ; if replaced.starts_with('.') { replaced = format!("\\&.{}", &replaced[1..]); } if let Some(ch) = replaced.chars().find(|ch| { !matches!(ch, '\n' | ' ' | '!'..='/' | '0'..='9' | ':'..='@' | 'A'..='Z' | '['..='`' | 'a'..='z' | '{'..='~') }) { bail!( "character {:?} is not allowed (update the translation table if needed)", ch ); } Ok(replaced) } ================================================ FILE: crates/mdman/src/format/md.rs ================================================ //! Markdown formatter. use crate::ManMap; use crate::util::unwrap; use anyhow::{Error, bail, format_err}; use std::fmt::Write; pub struct MdFormatter { man_map: ManMap, } impl MdFormatter { pub fn new(man_map: ManMap) -> MdFormatter { MdFormatter { man_map } } } impl MdFormatter { fn render_html(&self, input: &str) -> Result { let parser = crate::md_parser(input, None); let mut html_output: String = String::with_capacity(input.len() * 3 / 2); pulldown_cmark::html::push_html(&mut html_output, parser.map(|(e, _r)| e)); Ok(html_output) } } impl super::Formatter for MdFormatter { fn render(&self, input: &str) -> Result { Ok(input.replace("\r\n", "\n")) } fn render_options_start(&self) -> &'static str { "
\n" } fn render_options_end(&self) -> &'static str { "
\n" } fn render_option(&self, params: &[&str], block: &str, man_name: &str) -> Result { let mut result = String::new(); fn unwrap_p(t: &str) -> &str { unwrap(t, "

", "

") } for param in params { let rendered = self.render_html(param)?; let no_p = unwrap_p(&rendered); // split out first term to use as the id. let first = no_p .split_whitespace() .next() .ok_or_else(|| format_err!("did not expect option `{}` to be empty", param))?; let no_tags = trim_tags(first); if no_tags.is_empty() { bail!("unexpected empty option with no tags `{}`", param); } let id = format!("option-{}-{}", man_name, no_tags); write!( result, "
\ {no_p}
\n", )?; } let rendered_block = self.render_html(block)?; write!( result, "
{rendered_block}
\n\n", )?; Ok(result) } fn linkify_man_to_md(&self, name: &str, section: u8) -> Result { let s = match self.man_map.get(&(name.to_string(), section)) { Some(link) => format!("[{}({})]({})", name, section, link), None => format!("[{}({})]({}.html)", name, section, name), }; Ok(s) } } fn trim_tags(s: &str) -> String { // This is a hack. It removes all HTML tags. let mut in_tag = false; let mut in_char_ref = false; s.chars() .filter(|&ch| match ch { '<' if in_tag => panic!("unexpected nested tag"), '&' if in_char_ref => panic!("unexpected nested char ref"), '<' => { in_tag = true; false } '&' => { in_char_ref = true; false } '>' if in_tag => { in_tag = false; false } ';' if in_char_ref => { in_char_ref = false; false } _ => !in_tag && !in_char_ref, }) .collect() } ================================================ FILE: crates/mdman/src/format/mod.rs ================================================ use anyhow::Error; pub mod man; pub mod md; pub mod text; pub trait Formatter { /// Renders the given markdown to the formatter's output. fn render(&self, input: &str) -> Result; /// Renders the start of a block of options (triggered by `{{#options}}`). fn render_options_start(&self) -> &'static str; /// Renders the end of a block of options (triggered by `{{/options}}`). fn render_options_end(&self) -> &'static str; /// Renders an option (triggered by `{{#option}}`). fn render_option(&self, params: &[&str], block: &str, man_name: &str) -> Result; /// Converts a man page reference into markdown that is appropriate for this format. /// /// Triggered by `{{man name section}}`. fn linkify_man_to_md(&self, name: &str, section: u8) -> Result; } ================================================ FILE: crates/mdman/src/format/text.rs ================================================ //! Text formatter. use crate::EventIter; use crate::util::{header_text, unwrap}; use anyhow::{Error, bail}; use pulldown_cmark::{Alignment, Event, HeadingLevel, LinkType, Tag, TagEnd}; use std::fmt::Write; use std::mem; use url::Url; pub struct TextFormatter { url: Option, } impl TextFormatter { pub fn new(url: Option) -> TextFormatter { TextFormatter { url } } } impl super::Formatter for TextFormatter { fn render(&self, input: &str) -> Result { TextRenderer::render(input, self.url.clone(), 0) } fn render_options_start(&self) -> &'static str { // Tell pulldown_cmark to ignore this. // This will be stripped out later. " &'static str { "]]>\n" } fn render_option( &self, params: &[&str], block: &str, _man_name: &str, ) -> Result { let rendered_options = params .iter() .map(|param| TextRenderer::render(param, self.url.clone(), 0)) .collect::, Error>>()?; let trimmed: Vec<_> = rendered_options.iter().map(|o| o.trim()).collect(); // Wrap in HTML tags, they will be stripped out during rendering. Ok(format!( "
{}
\n
\n{}
\n
\n", trimmed.join(", "), block )) } fn linkify_man_to_md(&self, name: &str, section: u8) -> Result { Ok(format!("`{}`({})", name, section)) } } struct TextRenderer<'e> { output: String, indent: usize, /// The current line being written. Once a line break is encountered (such /// as starting a new paragraph), this will be written to `output` via /// `flush`. line: String, /// The current word being written. Once a break is encountered (such as a /// space) this will be written to `line` via `flush_word`. word: String, parser: EventIter<'e>, /// The base URL used for relative URLs. url: Option, table: Table, } impl<'e> TextRenderer<'e> { fn render(input: &str, url: Option, indent: usize) -> Result { let parser = crate::md_parser(input, url.clone()); let output = String::with_capacity(input.len() * 3 / 2); let mut mr = TextRenderer { output, indent, line: String::new(), word: String::new(), parser, url, table: Table::new(), }; mr.push_md()?; Ok(mr.output) } fn push_md(&mut self) -> Result<(), Error> { // If this is true, this is inside a cdata block used for hiding // content from pulldown_cmark. let mut in_cdata = false; // The current list stack. None if unordered, Some if ordered with the // given number as the current index. let mut list: Vec> = Vec::new(); // Used in some cases where spacing isn't desired. let mut suppress_paragraph = false; // Whether or not word-wrapping is enabled. let mut wrap_text = true; let mut last_seen_link_data = None; while let Some((event, range)) = self.parser.next() { let this_suppress_paragraph = suppress_paragraph; // Always reset suppression, even if the next event isn't a // paragraph. This is in essence, a 1-token lookahead where the // suppression is only enabled if the next event is a paragraph. suppress_paragraph = false; match event { Event::Start(tag) => { match tag { Tag::Paragraph => { if !this_suppress_paragraph { self.flush(); } } Tag::Heading { level, .. } => { self.flush(); if level == HeadingLevel::H1 { let text = header_text(&mut self.parser)?; self.push_to_line(&text.to_uppercase()); self.hard_break(); self.hard_break(); } else if level == HeadingLevel::H2 { let text = header_text(&mut self.parser)?; self.push_to_line(&text.to_uppercase()); self.flush(); self.indent = 7; } else { let text = header_text(&mut self.parser)?; self.push_indent((level as usize - 2) * 3); self.push_to_line(&text); self.flush(); self.indent = (level as usize - 1) * 3 + 1; } } Tag::BlockQuote(_kind) => { self.indent += 3; } Tag::CodeBlock(_kind) => { self.flush(); wrap_text = false; self.indent += 4; } Tag::List(start) => list.push(start), Tag::Item => { self.flush(); match list.last_mut().expect("item must have list start") { // Ordered list. Some(n) => { self.push_indent(self.indent); write!(self.line, "{}.", n)?; *n += 1; } // Unordered list. None => { self.push_indent(self.indent); self.push_to_line("o ") } } self.indent += 3; suppress_paragraph = true; } Tag::FootnoteDefinition(_label) => unimplemented!(), Tag::Table(alignment) => { assert!(self.table.alignment.is_empty()); self.flush(); self.table.alignment.extend(alignment); let table = self.table.process(&mut self.parser, self.indent)?; self.output.push_str(&table); self.hard_break(); self.table = Table::new(); } Tag::TableHead | Tag::TableRow | Tag::TableCell => { bail!("unexpected table element") } Tag::Emphasis => {} Tag::Strong => {} // Strikethrough isn't usually supported for TTY. Tag::Strikethrough => self.word.push_str("~~"), Tag::Link { link_type, dest_url, .. } => { last_seen_link_data = Some((link_type.clone(), dest_url.to_owned())); if dest_url.starts_with('#') { // In a man page, page-relative anchors don't // have much meaning. continue; } match link_type { LinkType::Autolink | LinkType::Email => { // The text is a copy of the URL, which is not needed. match self.parser.next() { Some((Event::Text(_), _range)) => {} _ => bail!("expected text after autolink"), } } LinkType::Inline | LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => {} // This is currently unused. This is only // emitted with a broken link callback, but I // felt it is too annoying to escape `[` in // option descriptions. LinkType::ReferenceUnknown | LinkType::CollapsedUnknown | LinkType::ShortcutUnknown => { bail!( "link with missing reference `{}` located at offset {}", dest_url, range.start ); } LinkType::WikiLink { .. } => { panic!("wikilink unsupported"); } } } Tag::Image { .. } => { bail!("images are not currently supported") } Tag::HtmlBlock { .. } | Tag::MetadataBlock { .. } | Tag::DefinitionList | Tag::DefinitionListTitle | Tag::DefinitionListDefinition | Tag::Superscript | Tag::Subscript => {} } } Event::End(tag_end) => match &tag_end { TagEnd::Paragraph => { self.flush(); self.hard_break(); } TagEnd::Heading(..) => {} TagEnd::BlockQuote(..) => { self.indent -= 3; } TagEnd::CodeBlock => { self.hard_break(); wrap_text = true; self.indent -= 4; } TagEnd::List(..) => { list.pop(); } TagEnd::Item => { self.flush(); self.indent -= 3; self.hard_break(); } TagEnd::FootnoteDefinition => {} TagEnd::Table => {} TagEnd::TableHead => {} TagEnd::TableRow => {} TagEnd::TableCell => {} TagEnd::Emphasis => {} TagEnd::Strong => {} TagEnd::Strikethrough => self.word.push_str("~~"), TagEnd::Link => { if let Some((link_type, ref dest_url)) = last_seen_link_data { if dest_url.starts_with('#') { continue; } match link_type { LinkType::Autolink | LinkType::Email => {} LinkType::Inline | LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => self.flush_word(), _ => { panic!("unexpected tag {:?}", tag_end); } } self.flush_word(); write!(self.word, "<{}>", dest_url)?; } } TagEnd::HtmlBlock { .. } | TagEnd::MetadataBlock { .. } | TagEnd::DefinitionList | TagEnd::DefinitionListTitle | TagEnd::Image | TagEnd::DefinitionListDefinition | TagEnd::Superscript | TagEnd::Subscript => {} }, Event::Text(t) | Event::Code(t) => { if wrap_text { let chunks = split_chunks(&t); for chunk in chunks { if chunk == " " { self.flush_word(); } else { self.word.push_str(chunk); } } } else { for line in t.lines() { self.push_indent(self.indent); self.push_to_line(line); self.flush(); } } } Event::Html(t) => { if t.starts_with("") { in_cdata = false; } else { let trimmed = t.trim(); if trimmed.is_empty() { continue; } if trimmed == "
" { self.hard_break(); } else if trimmed.starts_with("
") { let opts = unwrap(trimmed, "
", "
"); self.push_indent(self.indent); self.push_to_line(opts); self.flush(); } else if trimmed.starts_with("
") { let mut def = String::new(); while let Some((Event::Html(t), _range)) = self.parser.next() { if t.starts_with("
") { break; } def.push_str(&t); } let rendered = TextRenderer::render(&def, self.url.clone(), self.indent + 4)?; self.push_to_line(rendered.trim_end()); self.flush(); } else { self.push_to_line(&t); self.flush(); } } } else { self.push_to_line(&t); self.flush(); } } Event::FootnoteReference(_t) => {} Event::SoftBreak => self.flush_word(), Event::HardBreak => self.flush(), Event::Rule => { self.flush(); self.push_indent(self.indent); self.push_to_line(&"_".repeat(79 - self.indent * 2)); self.flush(); } Event::TaskListMarker(_b) => unimplemented!(), Event::InlineHtml(..) => unimplemented!(), Event::InlineMath(..) => unimplemented!(), Event::DisplayMath(..) => unimplemented!(), } } Ok(()) } fn flush(&mut self) { self.flush_word(); if !self.line.is_empty() { self.output.push_str(&self.line); self.output.push('\n'); self.line.clear(); } } fn hard_break(&mut self) { self.flush(); if !self.output.ends_with("\n\n") { self.output.push('\n'); } } fn flush_word(&mut self) { if self.word.is_empty() { return; } if self.line.len() + self.word.len() >= 79 { self.output.push_str(&self.line); self.output.push('\n'); self.line.clear(); } if self.line.is_empty() { self.push_indent(self.indent); self.line.push_str(&self.word); } else { self.line.push(' '); self.line.push_str(&self.word); } self.word.clear(); } fn push_indent(&mut self, indent: usize) { for _ in 0..indent { self.line.push(' '); } } fn push_to_line(&mut self, text: &str) { self.flush_word(); self.line.push_str(text); } } /// Splits the text on whitespace. /// /// Consecutive whitespace is collapsed to a single ' ', and is included as a /// separate element in the result. fn split_chunks(text: &str) -> Vec<&str> { let mut result = Vec::new(); let mut start = 0; while start < text.len() { match text[start..].find(' ') { Some(i) => { if i != 0 { result.push(&text[start..start + i]); } result.push(" "); // Skip past whitespace. match text[start + i..].find(|c| c != ' ') { Some(n) => { start = start + i + n; } None => { break; } } } None => { result.push(&text[start..]); break; } } } result } struct Table { alignment: Vec, rows: Vec>, row: Vec, cell: String, } impl Table { fn new() -> Table { Table { alignment: Vec::new(), rows: Vec::new(), row: Vec::new(), cell: String::new(), } } /// Processes table events and generates a text table. fn process(&mut self, parser: &mut EventIter<'_>, indent: usize) -> Result { while let Some((event, _range)) = parser.next() { match event { Event::Start(tag) => match tag { Tag::TableHead | Tag::TableRow | Tag::TableCell | Tag::Emphasis | Tag::Strong => {} Tag::Strikethrough => self.cell.push_str("~~"), // Links not yet supported, they usually won't fit. Tag::Link { .. } => {} _ => bail!("unexpected tag in table: {:?}", tag), }, Event::End(tag_end) => match tag_end { TagEnd::Table => return self.render(indent), TagEnd::TableCell => { let cell = mem::replace(&mut self.cell, String::new()); self.row.push(cell); } TagEnd::TableHead | TagEnd::TableRow => { let row = mem::replace(&mut self.row, Vec::new()); self.rows.push(row); } TagEnd::Strikethrough => self.cell.push_str("~~"), _ => {} }, Event::Text(t) | Event::Code(t) => { self.cell.push_str(&t); } Event::Html(t) => bail!("html unsupported in tables: {:?}", t), _ => bail!("unexpected event in table: {:?}", event), } } bail!("table end not reached"); } fn render(&self, indent: usize) -> Result { // This is an extremely primitive layout routine. // First compute the potential maximum width of each cell. // 2 for 1 space margin on left and right. let width_acc = vec![2; self.alignment.len()]; let mut col_widths = self .rows .iter() .map(|row| row.iter().map(|cell| cell.len())) .fold(width_acc, |mut acc, row| { acc.iter_mut() .zip(row) // +3 for left/right margin and | symbol .for_each(|(a, b)| *a = (*a).max(b + 3)); acc }); // Shrink each column until it fits the total width, proportional to // the columns total percent width. let max_width = 78 - indent; // Include total len for | characters, and +1 for final |. let total_width = col_widths.iter().sum::() + col_widths.len() + 1; if total_width > max_width { let to_shrink = total_width - max_width; // Compute percentage widths, and shrink each column based on its // total percentage. for width in &mut col_widths { let percent = *width as f64 / total_width as f64; *width -= (to_shrink as f64 * percent).ceil() as usize; } } // Start rendering. let mut result = String::new(); // Draw the horizontal line separating each row. let mut row_line = String::new(); row_line.push_str(&" ".repeat(indent)); row_line.push('+'); let lines = col_widths .iter() .map(|width| "-".repeat(*width)) .collect::>(); row_line.push_str(&lines.join("+")); row_line.push('+'); row_line.push('\n'); // Draw top of the table. result.push_str(&row_line); // Draw each row. for row in &self.rows { // Word-wrap and fill each column as needed. let filled = fill_row(row, &col_widths, &self.alignment); // Need to transpose the cells across rows for cells that span // multiple rows. let height = filled.iter().map(|c| c.len()).max().unwrap(); for row_i in 0..height { result.push_str(&" ".repeat(indent)); result.push('|'); for filled_row in &filled { let cell = &filled_row[row_i]; result.push_str(cell); result.push('|'); } result.push('\n'); } result.push_str(&row_line); } Ok(result) } } /// Formats a row, filling cells with spaces and word-wrapping text. /// /// Returns a vec of cells, where each cell is split into multiple lines. fn fill_row(row: &[String], col_widths: &[usize], alignment: &[Alignment]) -> Vec> { let mut cell_lines = row .iter() .zip(col_widths) .zip(alignment) .map(|((cell, width), alignment)| fill_cell(cell, *width - 2, *alignment)) .collect::>(); // Fill each cell to match the maximum vertical height of the tallest cell. let max_lines = cell_lines.iter().map(|cell| cell.len()).max().unwrap(); for (cell, width) in cell_lines.iter_mut().zip(col_widths) { if cell.len() < max_lines { cell.extend(std::iter::repeat(" ".repeat(*width)).take(max_lines - cell.len())); } } cell_lines } /// Formats a cell. Word-wraps based on width, and adjusts based on alignment. /// /// Returns a vec of lines for the cell. fn fill_cell(text: &str, width: usize, alignment: Alignment) -> Vec { let fill_width = |text: &str| match alignment { Alignment::None | Alignment::Left => format!(" {: format!(" {:^width$} ", text, width = width), Alignment::Right => format!(" {:>width$} ", text, width = width), }; if text.len() < width { // No wrapping necessary, just format. vec![fill_width(text)] } else { // Word-wrap the cell. let mut result = Vec::new(); let mut line = String::new(); for word in text.split_whitespace() { if line.len() + word.len() >= width { // todo: word.len() > width result.push(fill_width(&line)); line.clear(); } if line.is_empty() { line.push_str(word); } else { line.push(' '); line.push_str(&word); } } if !line.is_empty() { result.push(fill_width(&line)); } result } } ================================================ FILE: crates/mdman/src/hbs.rs ================================================ //! Handlebars template processing. use std::collections::HashMap; use std::path::Path; use anyhow::Error; use handlebars::{ Context, Decorator, DirectorySourceOptions, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderError, RenderErrorReason, Renderable, handlebars_helper, }; use crate::format::Formatter; type FormatterRef<'a> = &'a (dyn Formatter + Send + Sync); /// Processes the handlebars template at the given file. pub fn expand(file: &Path, formatter: FormatterRef<'_>) -> Result { let mut handlebars = Handlebars::new(); handlebars.set_strict_mode(true); handlebars.register_helper("lower", Box::new(lower)); handlebars.register_helper("options", Box::new(OptionsHelper { formatter })); handlebars.register_helper("option", Box::new(OptionHelper { formatter })); handlebars.register_helper("man", Box::new(ManLinkHelper { formatter })); handlebars.register_decorator("set", Box::new(set_decorator)); handlebars.register_template_file("template", file)?; let includes = file.parent().unwrap().join("includes"); let mut options = DirectorySourceOptions::default(); options.tpl_extension = ".md".to_string(); handlebars.register_templates_directory(includes, options)?; let man_name = file .file_stem() .expect("expected filename") .to_str() .expect("utf8 filename") .to_string(); let data = HashMap::from([("man_name", man_name)]); let expanded = handlebars.render("template", &data)?; Ok(expanded) } /// Helper for `{{#options}}` block. struct OptionsHelper<'a> { formatter: FormatterRef<'a>, } impl HelperDef for OptionsHelper<'_> { fn call<'reg: 'rc, 'rc>( &self, h: &Helper<'rc>, r: &'reg Handlebars<'reg>, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> HelperResult { if in_options(rc) { return Err( RenderErrorReason::Other("options blocks cannot be nested".to_string()).into(), ); } // Prevent nested {{#options}}. set_in_context(rc, "__MDMAN_IN_OPTIONS", serde_json::Value::Bool(true)); let s = self.formatter.render_options_start(); out.write(&s)?; let t = match h.template() { Some(t) => t, None => { return Err(RenderErrorReason::Other( "options block must not be empty".to_string(), ) .into()); } }; let block = t.renders(r, ctx, rc)?; out.write(&block)?; let s = self.formatter.render_options_end(); out.write(&s)?; remove_from_context(rc, "__MDMAN_IN_OPTIONS"); Ok(()) } } /// Whether or not the context is currently inside a `{{#options}}` block. fn in_options(rc: &RenderContext<'_, '_>) -> bool { rc.context() .map_or(false, |ctx| ctx.data().get("__MDMAN_IN_OPTIONS").is_some()) } /// Helper for `{{#option}}` block. struct OptionHelper<'a> { formatter: FormatterRef<'a>, } impl HelperDef for OptionHelper<'_> { fn call<'reg: 'rc, 'rc>( &self, h: &Helper<'rc>, r: &'reg Handlebars<'reg>, gctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> HelperResult { if !in_options(rc) { return Err( RenderErrorReason::Other("option must be in options block".to_string()).into(), ); } let params = h.params(); if params.is_empty() { return Err(RenderErrorReason::Other( "option block must have at least one param".to_string(), ) .into()); } // Convert params to strings. let params = params .iter() .map(|param| { param .value() .as_str() .ok_or_else(|| { RenderErrorReason::Other("option params must be strings".to_string()) }) .into() }) .collect::, RenderErrorReason>>()?; let t = match h.template() { Some(t) => t, None => { return Err( RenderErrorReason::Other("option block must not be empty".to_string()).into(), ); } }; // Render the block. let block = t.renders(r, gctx, rc)?; // Windows newlines can break some rendering, so normalize. let block = block.replace("\r\n", "\n"); // Get the name of this page. let man_name = gctx .data() .get("man_name") .expect("expected man_name in context") .as_str() .expect("expect man_name str"); // Ask the formatter to convert this option to its format. let option = self .formatter .render_option(¶ms, &block, man_name) .map_err(|e| RenderErrorReason::Other(format!("option render failed: {}", e)))?; out.write(&option)?; Ok(()) } } /// Helper for `{{man name section}}` expression. struct ManLinkHelper<'a> { formatter: FormatterRef<'a>, } impl HelperDef for ManLinkHelper<'_> { fn call<'reg: 'rc, 'rc>( &self, h: &Helper<'rc>, _r: &'reg Handlebars<'reg>, _ctx: &'rc Context, _rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> HelperResult { let params = h.params(); if params.len() != 2 { return Err( RenderErrorReason::Other("{{man}} must have two arguments".to_string()).into(), ); } let name = params[0].value().as_str().ok_or_else(|| { RenderErrorReason::Other("man link name must be a string".to_string()) })?; let section = params[1].value().as_u64().ok_or_else(|| { RenderErrorReason::Other("man link section must be an integer".to_string()) })?; let section = u8::try_from(section) .map_err(|_e| RenderErrorReason::Other("section number too large".to_string()))?; let link = self .formatter .linkify_man_to_md(name, section) .map_err(|e| RenderErrorReason::Other(format!("failed to linkify man: {}", e)))?; out.write(&link)?; Ok(()) } } /// `{{*set var=value}}` decorator. /// /// This sets a variable to a value within the template context. fn set_decorator( d: &Decorator<'_>, _: &Handlebars<'_>, _ctx: &Context, rc: &mut RenderContext<'_, '_>, ) -> Result<(), RenderError> { let data_to_set = d.hash(); for (k, v) in data_to_set { set_in_context(rc, k, v.value().clone()); } Ok(()) } /// Sets a variable to a value within the context. fn set_in_context(rc: &mut RenderContext<'_, '_>, key: &str, value: serde_json::Value) { let mut gctx = match rc.context() { Some(c) => (*c).clone(), None => Context::wraps(serde_json::Value::Object(serde_json::Map::new())).unwrap(), }; if let serde_json::Value::Object(m) = gctx.data_mut() { m.insert(key.to_string(), value); rc.set_context(gctx); } else { panic!("expected object in context"); } } /// Removes a variable from the context. fn remove_from_context(rc: &mut RenderContext<'_, '_>, key: &str) { let gctx = rc.context().expect("cannot remove from null context"); let mut gctx = (*gctx).clone(); if let serde_json::Value::Object(m) = gctx.data_mut() { m.remove(key); rc.set_context(gctx); } else { panic!("expected object in context"); } } handlebars_helper!(lower: |s: str| s.to_lowercase()); ================================================ FILE: crates/mdman/src/lib.rs ================================================ //! mdman markdown to man converter. //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use (except as a transitive dependency). This //! > crate may make major changes to its APIs or be deprecated without warning. use anyhow::{Context, Error, bail}; use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag, TagEnd}; use std::collections::HashMap; use std::fs; use std::io::{self, BufRead}; use std::ops::Range; use std::path::Path; use url::Url; mod format; mod hbs; mod util; use format::Formatter; /// Mapping of `(name, section)` of a man page to a URL. pub type ManMap = HashMap<(String, u8), String>; /// A man section. pub type Section = u8; /// The output formats supported by mdman. #[derive(Copy, Clone)] pub enum Format { Man, Md, Text, } impl Format { /// The filename extension for the format. pub fn extension(&self, section: Section) -> String { match self { Format::Man => section.to_string(), Format::Md => "md".to_string(), Format::Text => "txt".to_string(), } } } /// Converts the handlebars markdown file at the given path into the given /// format, returning the translated result. pub fn convert( file: &Path, format: Format, url: Option, man_map: ManMap, ) -> Result { let formatter: Box = match format { Format::Man => Box::new(format::man::ManFormatter::new(url)), Format::Md => Box::new(format::md::MdFormatter::new(man_map)), Format::Text => Box::new(format::text::TextFormatter::new(url)), }; let expanded = hbs::expand(file, &*formatter)?; // pulldown-cmark can behave a little differently with Windows newlines, // just normalize it. let expanded = expanded.replace("\r\n", "\n"); formatter.render(&expanded) } /// Pulldown-cmark iterator yielding an `(event, range)` tuple. type EventIter<'a> = Box, Range)> + 'a>; /// Creates a new markdown parser with the given input. pub(crate) fn md_parser(input: &str, url: Option) -> EventIter<'_> { let mut options = Options::empty(); options.insert(Options::ENABLE_TABLES); options.insert(Options::ENABLE_FOOTNOTES); options.insert(Options::ENABLE_STRIKETHROUGH); options.insert(Options::ENABLE_SMART_PUNCTUATION); let parser = Parser::new_ext(input, options); let parser = parser.into_offset_iter(); // Translate all links to include the base url. let parser = parser.map(move |(event, range)| match event { Event::Start(Tag::Link { link_type, dest_url, title, id, }) if !matches!(link_type, LinkType::Email) => ( Event::Start(Tag::Link { link_type, dest_url: join_url(url.as_ref(), dest_url), title, id, }), range, ), Event::End(TagEnd::Link) => (Event::End(TagEnd::Link), range), _ => (event, range), }); Box::new(parser) } fn join_url<'a>(base: Option<&Url>, dest: CowStr<'a>) -> CowStr<'a> { match base { Some(base_url) => { // Absolute URL or page-relative anchor doesn't need to be translated. if dest.contains(':') || dest.starts_with('#') { dest } else { let joined = base_url.join(&dest).unwrap_or_else(|e| { panic!("failed to join URL `{}` to `{}`: {}", dest, base_url, e) }); String::from(joined).into() } } None => dest, } } pub fn extract_section(file: &Path) -> Result { let f = fs::File::open(file).with_context(|| format!("could not open `{}`", file.display()))?; let mut f = io::BufReader::new(f); let mut line = String::new(); f.read_line(&mut line)?; if !line.starts_with("# ") { bail!("expected input file to start with # header"); } let (_name, section) = util::parse_name_and_section(&line[2..].trim()).with_context(|| { format!( "expected input file to have header with the format `# command-name(1)`, found: `{}`", line ) })?; Ok(section) } ================================================ FILE: crates/mdman/src/main.rs ================================================ #![allow(clippy::print_stderr)] use anyhow::{Context, Error, bail, format_err}; use mdman::{Format, ManMap}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use url::Url; /// Command-line options. struct Options { format: Format, output_dir: PathBuf, sources: Vec, url: Option, man_map: ManMap, } fn main() { if let Err(e) = run() { eprintln!("error: {}", e); for cause in e.chain().skip(1) { eprintln!("\nCaused by:"); for line in cause.to_string().lines() { if line.is_empty() { eprintln!(); } else { eprintln!(" {}", line); } } } std::process::exit(1); } } fn run() -> Result<(), Error> { let opts = process_args()?; if !opts.output_dir.exists() { std::fs::create_dir_all(&opts.output_dir).with_context(|| { format!( "failed to create output directory {}", opts.output_dir.display() ) })?; } for source in &opts.sources { let section = mdman::extract_section(source)?; let filename = Path::new(source.file_name().unwrap()).with_extension(opts.format.extension(section)); let out_path = opts.output_dir.join(filename); if same_file::is_same_file(source, &out_path).unwrap_or(false) { bail!("cannot output to the same file as the source"); } eprintln!("Converting {} -> {}", source.display(), out_path.display()); let result = mdman::convert(&source, opts.format, opts.url.clone(), opts.man_map.clone()) .with_context(|| format!("failed to translate {}", source.display()))?; std::fs::write(out_path, result)?; } Ok(()) } fn process_args() -> Result { let mut format = None; let mut output = None; let mut url = None; let mut man_map: ManMap = HashMap::new(); let mut sources = Vec::new(); let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { match arg.as_str() { "-t" => { format = match args.next().as_deref() { Some("man") => Some(Format::Man), Some("md") => Some(Format::Md), Some("txt") => Some(Format::Text), Some(s) => bail!("unknown output format: {}", s), None => bail!("-t requires a value (man, md, txt)"), }; } "-o" => { output = match args.next() { Some(s) => Some(PathBuf::from(s)), None => bail!("-o requires a value"), }; } "--url" => { url = match args.next() { Some(s) => { let url = Url::parse(&s) .with_context(|| format!("could not convert `{}` to a url", s))?; if !url.path().ends_with('/') { bail!("url `{}` should end with a /", url); } Some(url) } None => bail!("--url requires a value"), } } "--man" => { let man = args .next() .ok_or_else(|| format_err!("--man requires a value"))?; let parts = man.split_once('=').ok_or_else(|| { anyhow::format_err!("--man expected value with form name:1=link") })?; let key_parts = parts.0.split_once(':').ok_or_else(|| { anyhow::format_err!("--man expected value with form name:1=link") })?; let section: u8 = key_parts.1.parse().with_context(|| { format!("expected unsigned integer for section, got `{}`", parts.1) })?; man_map.insert((key_parts.0.to_string(), section), parts.1.to_string()); } s => { sources.push(PathBuf::from(s)); } } } if format.is_none() { bail!("-t must be specified (man, md, txt)"); } if output.is_none() { bail!("-o must be specified (output directory)"); } if sources.is_empty() { bail!("at least one source must be specified"); } let opts = Options { format: format.unwrap(), output_dir: output.unwrap(), sources, url, man_map, }; Ok(opts) } ================================================ FILE: crates/mdman/src/util.rs ================================================ ///! General utilities. use crate::EventIter; use anyhow::{Context, Error, bail, format_err}; use pulldown_cmark::{CowStr, Event, TagEnd}; /// Splits the text `foo(1)` into "foo" and `1`. pub fn parse_name_and_section(text: &str) -> Result<(&str, u8), Error> { let mut i = text.split_terminator(&['(', ')'][..]); let name = i .next() .ok_or_else(|| format_err!("man reference must have a name"))?; let section = i .next() .ok_or_else(|| format_err!("man reference must have a section such as mycommand(1)"))?; if let Some(s) = i.next() { bail!( "man reference must have the form mycommand(1), got extra part `{}`", s ); } let section: u8 = section .parse() .with_context(|| format!("section must be a number, got {}", section))?; Ok((name, section)) } /// Extracts the text from a header after `Tag::Heading` has been received. pub fn header_text<'e>(parser: &mut EventIter<'e>) -> Result, Error> { let text = match parser.next() { Some((Event::Text(t), _range)) => t, e => bail!("expected plain text in man header, got {:?}", e), }; match parser.next() { Some((Event::End(TagEnd::Heading(..)), _range)) => { return Ok(text); } e => bail!("expected plain text in man header, got {:?}", e), } } /// Removes tags from the front and back of a string. pub fn unwrap<'t>(text: &'t str, front: &str, back: &str) -> &'t str { text.trim().trim_start_matches(front).trim_end_matches(back) } ================================================ FILE: crates/mdman/tests/compare/expected/formatting.1 ================================================ '\" t .TH "FORMATTING" "1" .nh .ad l .ss \n[.ss] 0 .sp This is \fBnested \f(BIformatting\fB \fBtext\fB\fR\&. .SH "SECOND HEADING" Some text at second level. .SS "Third heading" Some text at third level. .SS "Fourth heading" Some text at fourth level. .SH "Quotes and blocks." Here are some quotes and blocks. .RS 3 .ll -5 .sp This is a block quote. Ambidextrously koala apart that prudent blindly alas far amid dear goodness turgid so exact inside oh and alas much fanciful that dark on spoon\-fed adequately insolent walking crud. .br .RE .ll .sp .RS 4 .nf This is a code block. Groundhog watchfully sudden firefly some self\-consciously hotly jeepers satanic after that this parrot this at virtuous some mocking the leaned jeez nightingale as much mallard so because jeez turned dear crud grizzly strenuously. Indented and should be unmodified. .fi .RE .sp .RS 4 .nf This is an indented code block. Egregiously yikes animatedly since outside beseechingly a badger hey shakily giraffe a one wow one this goodness regarding reindeer so astride before. Doubly indented .fi .RE .SH "Lists" .sp .RS 4 \h'-04' 1.\h'+01'Ordered list .sp .RS 4 \h'-04'\(bu\h'+03'Unordered list .sp With a second paragraph inside it .sp .RS 4 \h'-04' 1.\h'+01'Inner ordered list .RE .sp .RS 4 \h'-04' 2.\h'+01'Another .RE .RE .sp .RS 4 \h'-04'\(bu\h'+03'Eggs .RE .sp .RS 4 \h'-04'\(bu\h'+03'Milk .sp .RS 4 \h'-04' 5.\h'+01'Don\[cq]t start at one. .RE .sp .RS 4 \h'-04' 6.\h'+01'tamarind .RE .RE .RE .sp .RS 4 \h'-04' 2.\h'+01'Second element .RE .sp .RS 4 \h'-04' 3.\h'+01'Third element .RE .SH "Breaks" This has a .br hard break in it and a soft one. .SH "Horizontal rule" This should contain a line: \l'\n(.lu' .sp Nice! .SH "Strange characters" Handles escaping for characters .sp \&.dot at the start of a line. .sp \(rsfBnot really troff .sp Various characters \(rs \- \[en] \[em] \- | | ` .sp .RS 4 .nf tree `\-\- example |\-\- salamander | |\-\- honey | `\-\- some |\-\- fancifully `\-\- trout .fi .RE .sp \ \ \ \ non\-breaking space. ================================================ FILE: crates/mdman/tests/compare/expected/formatting.md ================================================ # formatting(1) This is **nested _formatting_ `text`**. ## SECOND HEADING Some text at second level. ### Third heading Some text at third level. #### Fourth heading Some text at fourth level. ## Quotes and blocks. Here are some quotes and blocks. > This is a block quote. Ambidextrously koala apart that prudent blindly alas > far amid dear goodness turgid so exact inside oh and alas much fanciful that > dark on spoon-fed adequately insolent walking crud. ``` This is a code block. Groundhog watchfully sudden firefly some self-consciously hotly jeepers satanic after that this parrot this at virtuous some mocking the leaned jeez nightingale as much mallard so because jeez turned dear crud grizzly strenuously. Indented and should be unmodified. ``` This is an indented code block. Egregiously yikes animatedly since outside beseechingly a badger hey shakily giraffe a one wow one this goodness regarding reindeer so astride before. Doubly indented ## Lists 1. Ordered list * Unordered list With a second paragraph inside it 1. Inner ordered list 1. Another * Eggs * Milk 5. Don't start at one. 6. tamarind 1. Second element 1. Third element ## Breaks This has a\ hard break in it and a soft one. ## Horizontal rule This should contain a line: --- Nice! ## Strange characters Handles escaping for characters .dot at the start of a line. \fBnot really troff Various characters \ - – — ─ │ ├ └ ``` tree └── example ├── salamander │ ├── honey │ └── some ├── fancifully └── trout ```     non-breaking space. ================================================ FILE: crates/mdman/tests/compare/expected/formatting.txt ================================================ FORMATTING(1) This is nested formatting text. SECOND HEADING Some text at second level. Third heading Some text at third level. Fourth heading Some text at fourth level. QUOTES AND BLOCKS. Here are some quotes and blocks. This is a block quote. Ambidextrously koala apart that prudent blindly alas far amid dear goodness turgid so exact inside oh and alas much fanciful that dark on spoon-fed adequately insolent walking crud. This is a code block. Groundhog watchfully sudden firefly some self-consciously hotly jeepers satanic after that this parrot this at virtuous some mocking the leaned jeez nightingale as much mallard so because jeez turned dear crud grizzly strenuously. Indented and should be unmodified. This is an indented code block. Egregiously yikes animatedly since outside beseechingly a badger hey shakily giraffe a one wow one this goodness regarding reindeer so astride before. Doubly indented LISTS 1. Ordered list o Unordered list With a second paragraph inside it 1. Inner ordered list 2. Another o Eggs o Milk 5. Don’t start at one. 6. tamarind 2. Second element 3. Third element BREAKS This has a hard break in it and a soft one. HORIZONTAL RULE This should contain a line: _________________________________________________________________ Nice! STRANGE CHARACTERS Handles escaping for characters .dot at the start of a line. \fBnot really troff Various characters \ - – — ─ │ ├ └ tree └── example ├── salamander │ ├── honey │ └── some ├── fancifully └── trout     non-breaking space. ================================================ FILE: crates/mdman/tests/compare/expected/links.1 ================================================ '\" t .TH "LINKS" "1" .nh .ad l .ss \n[.ss] 0 .SH "NAME" links \- Test of different link kinds .SH "DESCRIPTION" Inline link: \fIinline link\fR .sp Reference link: \fIthis is a link\fR .sp Collapsed: \fIcollapsed\fR .sp Shortcut: \fIshortcut\fR .sp Autolink: .sp Email: .sp Relative link: \fIrelative link\fR .sp Collapsed unknown: [collapsed unknown][] .sp Reference unknown: [foo][unknown] .sp Shortcut unknown: [shortcut unknown] .sp \fBother\-cmd\fR(1) .sp \fBlocal\-cmd\fR(1) .sp \fISome link\fR .sp \fB\-\-include\fR .RS 4 Testing an \fIincluded link\fR \&. .RE .SH "OPTIONS" .sp \fB\-\-foo\-bar\fR .RS 4 Example \fIlink\fR \&. See \fBother\-cmd\fR(1), \fBlocal\-cmd\fR(1) .RE ================================================ FILE: crates/mdman/tests/compare/expected/links.md ================================================ # links(1) ## NAME links - Test of different link kinds ## DESCRIPTION Inline link: [inline link](https://example.com/inline) Reference link: [this is a link][bar] Collapsed: [collapsed][] Shortcut: [shortcut] Autolink: Email: Relative link: [relative link](foo/bar.html) Collapsed unknown: [collapsed unknown][] Reference unknown: [foo][unknown] Shortcut unknown: [shortcut unknown] [other-cmd(1)](https://example.org/commands/other-cmd.html) [local-cmd(1)](local-cmd.html) [Some link](foo.html)

Testing an included link.

## OPTIONS

Example link. See other-cmd(1), local-cmd(1)

[bar]: https://example.com/bar [collapsed]: https://example.com/collapsed [shortcut]: https://example.com/shortcut ================================================ FILE: crates/mdman/tests/compare/expected/links.txt ================================================ LINKS(1) NAME links - Test of different link kinds DESCRIPTION Inline link: inline link Reference link: this is a link Collapsed: collapsed Shortcut: shortcut Autolink: Email: Relative link: relative link Collapsed unknown: [collapsed unknown][] Reference unknown: [foo][unknown] Shortcut unknown: [shortcut unknown] other-cmd(1) local-cmd(1) Some link --include Testing an included link . OPTIONS --foo-bar Example link . See other-cmd(1), local-cmd(1) ================================================ FILE: crates/mdman/tests/compare/expected/options.1 ================================================ '\" t .TH "MY\-COMMAND" "1" .nh .ad l .ss \n[.ss] 0 .SH "NAME" my\-command \- A brief description .SH "SYNOPSIS" \fBmy\-command\fR [\fB\-\-abc\fR | \fB\-\-xyz\fR] \fIname\fR .br \fBmy\-command\fR [\fB\-f\fR \fIfile\fR] .br \fBmy\-command\fR (\fB\-m\fR | \fB\-M\fR) [\fIoldbranch\fR] \fInewbranch\fR .br \fBmy\-command\fR (\fB\-d\fR | \fB\-D\fR) [\fB\-r\fR] \fIbranchname\fR\[u2026] .SH "DESCRIPTION" A description of the command. .sp .RS 4 \h'-04'\(bu\h'+03'One .sp .RS 4 \h'-04'\(bu\h'+03'Sub one .RE .sp .RS 4 \h'-04'\(bu\h'+03'Sub two .RE .RE .sp .RS 4 \h'-04'\(bu\h'+03'Two .RE .sp .RS 4 \h'-04'\(bu\h'+03'Three .RE .SH "OPTIONS" .SS "Command options" .sp \fB\-\-foo\-bar\fR .RS 4 Demo \fIemphasis\fR, \fBstrong\fR, ~~strike~~ .RE .sp \fB\-p\fR \fIspec\fR, \fB\-\-package\fR \fIspec\fR .RS 4 This has multiple flags. .RE .sp \fInamed\-arg\[u2026]\fR .RS 4 A named argument. .RE .sp \fB\-\-complex\fR .RS 4 This option has a list. .sp .RS 4 \h'-04'\(bu\h'+03'alpha .RE .sp .RS 4 \h'-04'\(bu\h'+03'beta .RE .sp .RS 4 \h'-04'\(bu\h'+03'gamma .RE .sp Then text continues here. .RE .SS "Common Options" .sp \fB@\fR\fIfilename\fR .RS 4 Load from filename. .RE .sp \fB\-\-foo\fR [\fIbar\fR] .RS 4 Flag with optional value. .RE .sp \fB\-\-foo\fR[\fB=\fR\fIbar\fR] .RS 4 Alternate syntax for optional value (with required = for disambiguation). .RE .sp \fB\-\-split\-block\fR .RS 4 An option where the description has a \fBblock statement that is split across multiple lines\fR .RE .SH "EXAMPLES" .sp .RS 4 \h'-04' 1.\h'+01'An example .sp .RS 4 .nf my\-command \-\-abc .fi .RE .RE .sp .RS 4 \h'-04' 2.\h'+01'Another example .sp .RS 4 .nf my\-command \-\-xyz .fi .RE .RE .SH "SEE ALSO" \fBother\-command\fR(1) \fBabc\fR(7) ================================================ FILE: crates/mdman/tests/compare/expected/options.md ================================================ # my-command(1) ## NAME my-command - A brief description ## SYNOPSIS `my-command` [`--abc` | `--xyz`] _name_\ `my-command` [`-f` _file_]\ `my-command` (`-m` | `-M`) [_oldbranch_] _newbranch_\ `my-command` (`-d` | `-D`) [`-r`] _branchname_... ## DESCRIPTION A description of the command. * One * Sub one * Sub two * Two * Three ## OPTIONS ### Command options
--foo-bar

Demo emphasis, strong, strike

-p spec
--package spec

This has multiple flags.

named-arg…

A named argument.

--complex

This option has a list.

  • alpha
  • beta
  • gamma

Then text continues here.

### Common Options
@filename

Load from filename.

--foo [bar]

Flag with optional value.

--foo[=bar]

Alternate syntax for optional value (with required = for disambiguation).

--split-block

An option where the description has a block statement that is split across multiple lines

## EXAMPLES 1. An example ``` my-command --abc ``` 1. Another example my-command --xyz ## SEE ALSO [other-command(1)](other-command.html) [abc(7)](abc.html) ================================================ FILE: crates/mdman/tests/compare/expected/options.txt ================================================ MY-COMMAND(1) NAME my-command - A brief description SYNOPSIS my-command [--abc | --xyz] name my-command [-f file] my-command (-m | -M) [oldbranch] newbranch my-command (-d | -D) [-r] branchname… DESCRIPTION A description of the command. o One o Sub one o Sub two o Two o Three OPTIONS Command options --foo-bar Demo emphasis, strong, ~~strike~~ -p spec, --package spec This has multiple flags. named-arg… A named argument. --complex This option has a list. o alpha o beta o gamma Then text continues here. Common Options @filename Load from filename. --foo [bar] Flag with optional value. --foo[=bar] Alternate syntax for optional value (with required = for disambiguation). --split-block An option where the description has a block statement that is split across multiple lines EXAMPLES 1. An example my-command --abc 2. Another example my-command --xyz SEE ALSO other-command(1) abc(7) ================================================ FILE: crates/mdman/tests/compare/expected/tables.1 ================================================ '\" t .TH "TABLES" "1" .nh .ad l .ss \n[.ss] 0 .SH "DESCRIPTION" Testing tables. .TS allbox tab(:); lt. T{ Single col T} T{ Hi! :) T} .TE .sp .TS allbox tab(:); lt lt lt. T{ Header content T}:T{ With \fBformat\fR \fItext\fR T}:T{ Another column T} T{ Some data T}:T{ More data T}:T{ T} T{ Extra long amount of text within a column T}:T{ hi T}:T{ there T} .TE .sp .TS allbox tab(:); lt ct rt. T{ Left aligned T}:T{ Center aligned T}:T{ Right aligned T} T{ abc T}:T{ def T}:T{ ghi T} .TE .sp .TS allbox tab(:); lt ct rt. T{ Left aligned T}:T{ Center aligned T}:T{ Right aligned T} T{ X T}:T{ X T}:T{ X T} T{ Extra long text 123456789012 with mixed widths. T}:T{ Extra long text 123456789012 with mixed widths. T}:T{ Extra long text 123456789012 with mixed widths. T} .TE .sp .TS allbox tab(:); lt. T{ Link check T} T{ \fIfoo\fR T} T{ T} .TE .sp ================================================ FILE: crates/mdman/tests/compare/expected/tables.md ================================================ # tables(1) ## DESCRIPTION Testing tables. | Single col | |------------| | Hi! :) | Header content | With `format` *text* | Another column ---------------|----------------------|---------------- Some data | More data | Extra long amount of text within a column | hi | there Left aligned | Center aligned | Right aligned -------------|:--------------:|--------------: abc | def | ghi Left aligned | Center aligned | Right aligned -------------|:--------------:|--------------: X | X | X Extra long text 123456789012 with mixed widths. | Extra long text 123456789012 with mixed widths. | Extra long text 123456789012 with mixed widths. | Link check | |------------| | [foo] | | | [foo]: https://example.com/ ================================================ FILE: crates/mdman/tests/compare/expected/tables.txt ================================================ TABLES(1) DESCRIPTION Testing tables. +-------------+ | Single col | +-------------+ | Hi! :) | +-------------+ +-------------------------------------+----------------+--------------+ | Header content | With format | Another | | | text | column | +-------------------------------------+----------------+--------------+ | Some data | More data | | +-------------------------------------+----------------+--------------+ | Extra long amount of text within a | hi | there | | column | | | +-------------------------------------+----------------+--------------+ +---------------+-----------------+----------------+ | Left aligned | Center aligned | Right aligned | +---------------+-----------------+----------------+ | abc | def | ghi | +---------------+-----------------+----------------+ +-----------------------+-----------------------+-----------------------+ | Left aligned | Center aligned | Right aligned | +-----------------------+-----------------------+-----------------------+ | X | X | X | +-----------------------+-----------------------+-----------------------+ | Extra long text | Extra long text | Extra long text | | 123456789012 with | 123456789012 with | 123456789012 with | | mixed widths. | mixed widths. | mixed widths. | +-----------------------+-----------------------+-----------------------+ +-----------------------+ | Link check | +-----------------------+ | foo | +-----------------------+ | https://example.com/ | +-----------------------+ ================================================ FILE: crates/mdman/tests/compare/expected/vars.7 ================================================ '\" t .TH "VARS" "7" .nh .ad l .ss \n[.ss] 0 .sp Bar .sp bar ================================================ FILE: crates/mdman/tests/compare/expected/vars.md ================================================ # vars(7) Bar bar ================================================ FILE: crates/mdman/tests/compare/expected/vars.txt ================================================ VARS(7) Bar bar ================================================ FILE: crates/mdman/tests/compare/formatting.md ================================================ # formatting(1) This is **nested _formatting_ `text`**. ## SECOND HEADING Some text at second level. ### Third heading Some text at third level. #### Fourth heading Some text at fourth level. ## Quotes and blocks. Here are some quotes and blocks. > This is a block quote. Ambidextrously koala apart that prudent blindly alas > far amid dear goodness turgid so exact inside oh and alas much fanciful that > dark on spoon-fed adequately insolent walking crud. ``` This is a code block. Groundhog watchfully sudden firefly some self-consciously hotly jeepers satanic after that this parrot this at virtuous some mocking the leaned jeez nightingale as much mallard so because jeez turned dear crud grizzly strenuously. Indented and should be unmodified. ``` This is an indented code block. Egregiously yikes animatedly since outside beseechingly a badger hey shakily giraffe a one wow one this goodness regarding reindeer so astride before. Doubly indented ## Lists 1. Ordered list * Unordered list With a second paragraph inside it 1. Inner ordered list 1. Another * Eggs * Milk 5. Don't start at one. 6. tamarind 1. Second element 1. Third element ## Breaks This has a\ hard break in it and a soft one. ## Horizontal rule This should contain a line: --- Nice! ## Strange characters Handles escaping for characters .dot at the start of a line. \fBnot really troff Various characters \ - – — ─ │ ├ └ ``` tree └── example ├── salamander │ ├── honey │ └── some ├── fancifully └── trout ```     non-breaking space. ================================================ FILE: crates/mdman/tests/compare/includes/links-include.md ================================================ [Some link](foo.html) {{#options}} {{#option "`--include`"}} Testing an [included link](included_link.html). {{/option}} {{/options}} ================================================ FILE: crates/mdman/tests/compare/includes/options-common.md ================================================ {{#options}} {{#option "`@`_filename_"}} Load from filename. {{/option}} {{#option "`--foo` [_bar_]"}} Flag with optional value. {{/option}} {{#option "`--foo`[`=`_bar_]"}} Alternate syntax for optional value (with required = for disambiguation). {{/option}} {{#option "`--split-block`"}} An option where the description has a `block statement that is split across multiple lines` {{/option}} {{/options}} ================================================ FILE: crates/mdman/tests/compare/links.md ================================================ # links(1) ## NAME links - Test of different link kinds ## DESCRIPTION Inline link: [inline link](https://example.com/inline) Reference link: [this is a link][bar] Collapsed: [collapsed][] Shortcut: [shortcut] Autolink: Email: Relative link: [relative link](foo/bar.html) Collapsed unknown: [collapsed unknown][] Reference unknown: [foo][unknown] Shortcut unknown: [shortcut unknown] {{man "other-cmd" 1}} {{man "local-cmd" 1}} {{> links-include}} ## OPTIONS {{#options}} {{#option "`--foo-bar`"}} Example [link](bar.html). See {{man "other-cmd" 1}}, {{man "local-cmd" 1}} {{/option}} {{/options}} [bar]: https://example.com/bar [collapsed]: https://example.com/collapsed [shortcut]: https://example.com/shortcut ================================================ FILE: crates/mdman/tests/compare/options.md ================================================ # my-command(1) ## NAME my-command - A brief description ## SYNOPSIS `my-command` [`--abc` | `--xyz`] _name_\ `my-command` [`-f` _file_]\ `my-command` (`-m` | `-M`) [_oldbranch_] _newbranch_\ `my-command` (`-d` | `-D`) [`-r`] _branchname_... ## DESCRIPTION A description of the command. * One * Sub one * Sub two * Two * Three ## OPTIONS ### Command options {{#options}} {{#option "`--foo-bar`"}} Demo *emphasis*, **strong**, ~~strike~~ {{/option}} {{#option "`-p` _spec_" "`--package` _spec_"}} This has multiple flags. {{/option}} {{#option "_named-arg..._"}} A named argument. {{/option}} {{#option "`--complex`"}} This option has a list. - alpha - beta - gamma Then text continues here. {{/option}} {{/options}} ### Common Options {{> options-common}} ## EXAMPLES 1. An example ``` my-command --abc ``` 1. Another example my-command --xyz ## SEE ALSO {{man "other-command" 1}} {{man "abc" 7}} ================================================ FILE: crates/mdman/tests/compare/tables.md ================================================ # tables(1) ## DESCRIPTION Testing tables. | Single col | |------------| | Hi! :) | Header content | With `format` *text* | Another column ---------------|----------------------|---------------- Some data | More data | Extra long amount of text within a column | hi | there Left aligned | Center aligned | Right aligned -------------|:--------------:|--------------: abc | def | ghi Left aligned | Center aligned | Right aligned -------------|:--------------:|--------------: X | X | X Extra long text 123456789012 with mixed widths. | Extra long text 123456789012 with mixed widths. | Extra long text 123456789012 with mixed widths. | Link check | |------------| | [foo] | | | [foo]: https://example.com/ ================================================ FILE: crates/mdman/tests/compare/vars.md ================================================ # vars(7) {{*set foo="Bar"}} {{foo}} {{lower foo}} ================================================ FILE: crates/mdman/tests/compare.rs ================================================ //! Compares input to expected output. use std::path::PathBuf; use mdman::{Format, ManMap}; use url::Url; fn run(name: &str) { let input = PathBuf::from(format!("tests/compare/{}.md", name)); let url = Some(Url::parse("https://example.org/").unwrap()); let mut map = ManMap::new(); map.insert( ("other-cmd".to_string(), 1), "https://example.org/commands/other-cmd.html".to_string(), ); for &format in &[Format::Man, Format::Md, Format::Text] { let section = mdman::extract_section(&input).unwrap(); let result = mdman::convert(&input, format, url.clone(), map.clone()).unwrap(); let expected_path = PathBuf::from(format!( "tests/compare/expected/{}.{}", name, format.extension(section) )); snapbox::assert_data_eq!(result, snapbox::Data::read_from(&expected_path, None).raw()); } } macro_rules! test( ($name:ident) => ( #[test] fn $name() { run(stringify!($name)); } ) ); test!(formatting); test!(links); test!(options); test!(tables); test!(vars); ================================================ FILE: crates/mdman/tests/invalid/nested.md ================================================ # nested(1) {{#options}} {{#options}} {{/options}} {{/options}} ================================================ FILE: crates/mdman/tests/invalid/not-inside-options.md ================================================ # not-inside-options(1) {{#option "`-o`"}} Testing without options block. {{/option}} ================================================ FILE: crates/mdman/tests/invalid.rs ================================================ //! Tests for errors and invalid input. use std::path::PathBuf; use mdman::{Format, ManMap}; use snapbox::prelude::*; fn run(name: &str, expected_error: impl IntoData) { let input = PathBuf::from(format!("tests/invalid/{}", name)); match mdman::convert(&input, Format::Man, None, ManMap::new()) { Ok(_) => { panic!("expected {} to fail", name); } Err(e) => { snapbox::assert_data_eq!(e.to_string(), expected_error.raw()); } } } macro_rules! test( ($name:ident, $file_name:expr, $error:expr) => ( #[test] fn $name() { run($file_name, $error); } ) ); test!( nested, "nested.md", "Error rendering \"template\" line 4, col 1: options blocks cannot be nested" ); test!( not_inside_options, "not-inside-options.md", "Error rendering \"template\" line 3, col 1: option must be in options block" ); ================================================ FILE: crates/resolver-tests/Cargo.toml ================================================ [package] name = "resolver-tests" version = "0.0.0" edition.workspace = true publish = false [dependencies] cargo.workspace = true cargo-platform.workspace = true cargo-util-schemas.workspace = true cargo-util.workspace = true proptest.workspace = true varisat.workspace = true [lints] workspace = true ================================================ FILE: crates/resolver-tests/README.md ================================================ # resolver-tests ## The aim This crate aims to test the resolution of Cargo's resolver. It implements a [SAT solver](https://en.wikipedia.org/wiki/SAT_solver) to compare with resolution of Cargo's resolver. This ensures that Cargo's dependency resolution is proven valid by lowering to [SAT problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem). > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use (except as a transitive dependency). This > crate may make major changes to its APIs or be deprecated without warning. ## About the test The Cargo's resolver is very sensitive to what order it tries to evaluate constraints. This makes it incredibly difficult to be sure that a handful of tests actually covers all the important permutations of decision-making. The tests not only needs to hit all the corner cases, it needs to try all of the orders of evaluation. So we use fuzz testing to cover more permutations. ================================================ FILE: crates/resolver-tests/src/helpers.rs ================================================ use std::collections::BTreeMap; use std::fmt::Debug; use std::sync::OnceLock; use cargo::core::dependency::DepKind; use cargo::core::{Dependency, GitReference, PackageId, SourceId, Summary}; use cargo::util::IntoUrl; pub trait ToDep { fn to_dep(self) -> Dependency; fn opt(self) -> Dependency; fn with(self, features: &[&'static str]) -> Dependency; fn with_default(self) -> Dependency; fn rename(self, name: &str) -> Dependency; } impl ToDep for &'static str { fn to_dep(self) -> Dependency { Dependency::parse(self, Some("1.0.0"), registry_loc()).unwrap() } fn opt(self) -> Dependency { let mut dep = self.to_dep(); dep.set_optional(true); dep } fn with(self, features: &[&'static str]) -> Dependency { let mut dep = self.to_dep(); dep.set_default_features(false); dep.set_features(features.into_iter().copied()); dep } fn with_default(self) -> Dependency { let mut dep = self.to_dep(); dep.set_default_features(true); dep } fn rename(self, name: &str) -> Dependency { let mut dep = self.to_dep(); dep.set_explicit_name_in_toml(name); dep } } impl ToDep for Dependency { fn to_dep(self) -> Dependency { self } fn opt(mut self) -> Dependency { self.set_optional(true); self } fn with(mut self, features: &[&'static str]) -> Dependency { self.set_default_features(false); self.set_features(features.into_iter().copied()); self } fn with_default(mut self) -> Dependency { self.set_default_features(true); self } fn rename(mut self, name: &str) -> Dependency { self.set_explicit_name_in_toml(name); self } } pub trait ToPkgId { fn to_pkgid(&self) -> PackageId; } impl ToPkgId for PackageId { fn to_pkgid(&self) -> PackageId { *self } } impl<'a> ToPkgId for &'a str { fn to_pkgid(&self) -> PackageId { PackageId::try_new(*self, "1.0.0", registry_loc()).unwrap() } } impl, U: AsRef> ToPkgId for (T, U) { fn to_pkgid(&self) -> PackageId { let (name, vers) = self; PackageId::try_new(name.as_ref(), vers.as_ref(), registry_loc()).unwrap() } } #[macro_export] macro_rules! pkg { ($pkgid:expr => [$($deps:expr),* $(,)? ]) => ({ use $crate::helpers::ToDep; let d: Vec = vec![$($deps.to_dep()),*]; $crate::helpers::pkg_dep($pkgid, d) }); ($pkgid:expr) => ({ $crate::helpers::pkg($pkgid) }) } fn registry_loc() -> SourceId { static EXAMPLE_DOT_COM: OnceLock = OnceLock::new(); let example_dot = EXAMPLE_DOT_COM.get_or_init(|| { SourceId::for_registry(&"https://example.com".into_url().unwrap()).unwrap() }); *example_dot } pub fn pkg(name: T) -> Summary { pkg_dep(name, Vec::new()) } pub fn pkg_dep(name: T, dep: Vec) -> Summary { let pkgid = name.to_pkgid(); let link = if pkgid.name().ends_with("-sys") { Some(pkgid.name()) } else { None }; Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), link, None).unwrap() } pub fn pkg_dep_with( name: T, dep: Vec, features: &[(&'static str, &[&'static str])], ) -> Summary { let pkgid = name.to_pkgid(); let link = if pkgid.name().ends_with("-sys") { Some(pkgid.name()) } else { None }; let features = features .into_iter() .map(|&(name, values)| (name.into(), values.into_iter().map(|&v| v.into()).collect())) .collect(); Summary::new(name.to_pkgid(), dep, &features, link, None).unwrap() } pub fn pkg_dep_link(name: T, link: &str, dep: Vec) -> Summary { Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), Some(link), None).unwrap() } pub fn pkg_id(name: &str) -> PackageId { PackageId::try_new(name, "1.0.0", registry_loc()).unwrap() } pub fn pkg_id_source(name: &str, source: &str) -> PackageId { PackageId::try_new( name, "1.0.0", SourceId::for_registry(&source.into_url().unwrap()).unwrap(), ) .unwrap() } fn pkg_id_loc(name: &str, loc: &str) -> PackageId { let remote = loc.into_url(); let master = GitReference::Branch("master".to_string()); let source_id = SourceId::for_git(&remote.unwrap(), master).unwrap(); PackageId::try_new(name, "1.0.0", source_id).unwrap() } pub fn pkg_loc(name: &str, loc: &str) -> Summary { let link = if name.ends_with("-sys") { Some(name) } else { None }; Summary::new( pkg_id_loc(name, loc), Vec::new(), &BTreeMap::new(), link, None, ) .unwrap() } pub fn remove_dep(sum: &Summary, ind: usize) -> Summary { let mut deps = sum.dependencies().to_vec(); deps.remove(ind); // note: more things will need to be copied over in the future, but it works for now. Summary::new(sum.package_id(), deps, &BTreeMap::new(), sum.links(), None).unwrap() } pub fn dep(name: &str) -> Dependency { dep_req(name, "*") } pub fn dep_req(name: &str, req: &str) -> Dependency { Dependency::parse(name, Some(req), registry_loc()).unwrap() } pub fn dep_req_kind(name: &str, req: &str, kind: DepKind) -> Dependency { let mut dep = dep_req(name, req); dep.set_kind(kind); dep } pub fn dep_req_platform(name: &str, req: &str, platform: &str) -> Dependency { let mut dep = dep_req(name, req); dep.set_platform(Some(platform.parse().unwrap())); dep } pub fn dep_loc(name: &str, location: &str) -> Dependency { let url = location.into_url().unwrap(); let master = GitReference::Branch("master".to_string()); let source_id = SourceId::for_git(&url, master).unwrap(); Dependency::parse(name, Some("1.0.0"), source_id).unwrap() } pub fn dep_kind(name: &str, kind: DepKind) -> Dependency { let mut dep = dep(name); dep.set_kind(kind); dep } pub fn dep_platform(name: &str, platform: &str) -> Dependency { let mut dep = dep(name); dep.set_platform(Some(platform.parse().unwrap())); dep } pub fn registry(pkgs: Vec) -> Vec { pkgs } pub fn names(names: &[P]) -> Vec { names.iter().map(|name| name.to_pkgid()).collect() } pub fn loc_names(names: &[(&'static str, &'static str)]) -> Vec { names .iter() .map(|&(name, loc)| pkg_id_loc(name, loc)) .collect() } /// Assert `xs` contains `elems` #[track_caller] pub fn assert_contains(xs: &[A], elems: &[A]) { for elem in elems { assert!( xs.contains(elem), "missing element\nset: {xs:?}\nmissing: {elem:?}" ); } } #[track_caller] pub fn assert_same(a: &[A], b: &[A]) { assert_eq!(a.len(), b.len(), "not equal\n{a:?}\n{b:?}"); assert_contains(b, a); } ================================================ FILE: crates/resolver-tests/src/lib.rs ================================================ //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use (except as a transitive dependency). This //! > crate may make major changes to its APIs or be deprecated without warning. #![allow(clippy::print_stderr)] pub mod helpers; pub mod sat; use std::cmp::{max, min}; use std::collections::{BTreeMap, HashSet}; use std::fmt; use std::task::Poll; use std::time::Instant; use cargo::core::Resolve; use cargo::core::ResolveVersion; use cargo::core::SourceId; use cargo::core::dependency::DepKind; use cargo::core::resolver::{self, ResolveOpts, VersionOrdering, VersionPreferences}; use cargo::core::{Dependency, PackageId, Registry, Summary}; use cargo::sources::IndexSummary; use cargo::sources::source::QueryKind; use cargo::util::interning::InternedString; use cargo::util::{CargoResult, GlobalContext}; use crate::helpers::{ToPkgId, dep_req, dep_req_kind, pkg_dep, pkg_id}; use crate::sat::SatResolver; use proptest::collection::{btree_map, vec}; use proptest::prelude::*; use proptest::sample::Index; use proptest::string::string_regex; pub fn resolve(deps: Vec, registry: &[Summary]) -> CargoResult> { Ok( resolve_with_global_context(deps, registry, &GlobalContext::default().unwrap())? .into_iter() .map(|(pkg, _)| pkg) .collect(), ) } pub fn resolve_and_validated( deps: Vec, registry: &[Summary], sat_resolver: &mut SatResolver, ) -> CargoResult)>> { resolve_and_validated_raw(deps, registry, pkg_id("root"), sat_resolver) } // Verify that the resolution of cargo resolver can pass the verification of SAT pub fn resolve_and_validated_raw( deps: Vec, registry: &[Summary], root_pkg_id: PackageId, sat_resolver: &mut SatResolver, ) -> CargoResult)>> { let resolve = resolve_with_global_context_raw( deps.clone(), registry, root_pkg_id, &GlobalContext::default().unwrap(), ); match resolve { Err(e) => { if sat_resolver.sat_resolve(&deps) { panic!( "`resolve()` returned an error but the sat resolver thinks this will work:\n{}", sat_resolver.used_packages().unwrap() ); } Err(e) } Ok(resolve) => { let mut stack = vec![root_pkg_id]; let mut used = HashSet::new(); let mut links = HashSet::new(); while let Some(p) = stack.pop() { assert!(resolve.contains(&p)); if used.insert(p) { // in the tests all `links` crates end in `-sys` if p.name().ends_with("-sys") { assert!(links.insert(p.name())); } stack.extend(resolve.deps(p).map(|(dp, deps)| { for d in deps { assert!(d.matches_id(dp)); } dp })); } } let out = collect_features(&resolve); assert_eq!(out.len(), used.len()); if !sat_resolver.sat_is_valid_solution(&out) { panic!( "`resolve()` thinks this will work, but the solution is \ invalid according to the sat resolver:\n{resolve:?}", ); } Ok(out) } } } fn collect_features(resolve: &Resolve) -> Vec<(PackageId, Vec)> { resolve .sort() .iter() .map(|&pkg| (pkg, resolve.features(pkg).to_vec())) .collect() } pub fn resolve_with_global_context( deps: Vec, registry: &[Summary], gctx: &GlobalContext, ) -> CargoResult)>> { let resolve = resolve_with_global_context_raw(deps, registry, pkg_id("root"), gctx)?; Ok(collect_features(&resolve)) } pub fn resolve_with_global_context_raw( deps: Vec, registry: &[Summary], root_pkg_id: PackageId, gctx: &GlobalContext, ) -> CargoResult { struct MyRegistry<'a> { list: &'a [Summary], used: HashSet, } impl<'a> Registry for MyRegistry<'a> { fn query( &mut self, dep: &Dependency, kind: QueryKind, f: &mut dyn FnMut(IndexSummary), ) -> Poll> { for summary in self.list.iter() { let matched = match kind { QueryKind::Exact => dep.matches(summary), QueryKind::RejectedVersions => dep.matches(summary), QueryKind::AlternativeNames => true, QueryKind::Normalized => true, }; if matched { self.used.insert(summary.package_id()); f(IndexSummary::Candidate(summary.clone())); } } Poll::Ready(Ok(())) } fn describe_source(&self, _src: SourceId) -> String { String::new() } fn is_replaced(&self, _src: SourceId) -> bool { false } fn block_until_ready(&mut self) -> CargoResult<()> { Ok(()) } } impl<'a> Drop for MyRegistry<'a> { fn drop(&mut self) { if std::thread::panicking() && self.list.len() != self.used.len() { // we found a case that causes a panic and did not use all of the input. // lets print the part of the input that was used for minimization. eprintln!( "Part used before drop: {:?}", PrettyPrintRegistry( self.list .iter() .filter(|s| { self.used.contains(&s.package_id()) }) .cloned() .collect() ) ); } } } let mut registry = MyRegistry { list: registry, used: HashSet::new(), }; let root_summary = Summary::new(root_pkg_id, deps, &BTreeMap::new(), None::<&String>, None).unwrap(); let opts = ResolveOpts::everything(); let start = Instant::now(); let mut version_prefs = VersionPreferences::default(); if gctx.cli_unstable().minimal_versions { version_prefs.version_ordering(VersionOrdering::MinimumVersionsFirst) } let resolve = resolver::resolve( &[(root_summary, opts)], &[], &mut registry, &version_prefs, ResolveVersion::with_rust_version(None), Some(gctx), ); // The largest test in our suite takes less then 30 secs. // So let's fail the test if we have been running for more than 60 secs. assert!(start.elapsed().as_secs() < 60); resolve } /// By default `Summary` and `Dependency` have a very verbose `Debug` representation. /// This replaces with a representation that uses constructors from this file. /// /// If `registry_strategy` is improved to modify more fields /// then this needs to update to display the corresponding constructor. pub struct PrettyPrintRegistry(pub Vec); impl fmt::Debug for PrettyPrintRegistry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "vec![")?; for s in &self.0 { if s.dependencies().is_empty() { write!(f, "pkg!((\"{}\", \"{}\")),", s.name(), s.version())?; } else { write!(f, "pkg!((\"{}\", \"{}\") => [", s.name(), s.version())?; for d in s.dependencies() { if d.kind() == DepKind::Normal && &d.version_req().to_string() == "*" && !d.is_public() { write!(f, "dep(\"{}\"),", d.name_in_toml())?; } else if d.kind() == DepKind::Normal && !d.is_public() { write!( f, "dep_req(\"{}\", \"{}\"),", d.name_in_toml(), d.version_req() )?; } else { write!( f, "dep_req_kind(\"{}\", \"{}\", {}, {}),", d.name_in_toml(), d.version_req(), match d.kind() { DepKind::Development => "DepKind::Development", DepKind::Build => "DepKind::Build", DepKind::Normal => "DepKind::Normal", }, d.is_public() )?; } } write!(f, "]),")?; } } write!(f, "]") } } /// This generates a random registry index. /// Unlike `vec((Name, Ver, vec((Name, VerRq), ..), ..)`, /// this strategy has a high probability of having valid dependencies. pub fn registry_strategy( max_crates: usize, max_versions: usize, shrinkage: usize, ) -> impl Strategy { let name = string_regex("[A-Za-z][A-Za-z0-9_-]*(-sys)?").unwrap(); let raw_version = ..max_versions.pow(3); let version_from_raw = move |r: usize| { let major = ((r / max_versions) / max_versions) % max_versions; let minor = (r / max_versions) % max_versions; let patch = r % max_versions; format!("{}.{}.{}", major, minor, patch) }; // If this is false then the crate will depend on the nonexistent "bad" // instead of the complex set we generated for it. let allow_deps = prop::bool::weighted(0.99); let list_of_versions = btree_map(raw_version, allow_deps, 1..=max_versions).prop_map(move |ver| { ver.into_iter() .map(|a| (version_from_raw(a.0), a.1)) .collect::>() }); let list_of_crates_with_versions = btree_map(name, list_of_versions, 1..=max_crates).prop_map(|mut vers| { // root is the name of the thing being compiled // so it would be confusing to have it in the index vers.remove("root"); // bad is a name reserved for a dep that won't work vers.remove("bad"); vers }); // each version of each crate can depend on each crate smaller than it. // In theory shrinkage should be 2, but in practice we get better trees with a larger value. let max_deps = max_versions * (max_crates * (max_crates - 1)) / shrinkage; let raw_version_range = (any::(), any::()); let raw_dependency = (any::(), any::(), raw_version_range, 0..=1); fn order_index(a: Index, b: Index, size: usize) -> (usize, usize) { let (a, b) = (a.index(size), b.index(size)); (min(a, b), max(a, b)) } let list_of_raw_dependency = vec(raw_dependency, ..=max_deps); // By default a package depends only on other packages that have a smaller name, // this helps make sure that all things in the resulting index are DAGs. // If this is true then the DAG is maintained with grater instead. let reverse_alphabetical = any::().no_shrink(); ( list_of_crates_with_versions, list_of_raw_dependency, reverse_alphabetical, ) .prop_map( |(crate_vers_by_name, raw_dependencies, reverse_alphabetical)| { let list_of_pkgid: Vec<_> = crate_vers_by_name .iter() .flat_map(|(name, vers)| vers.iter().map(move |x| ((name.as_str(), &x.0), x.1))) .collect(); let len_all_pkgid = list_of_pkgid.len(); let mut dependency_by_pkgid = vec![vec![]; len_all_pkgid]; for (a, b, (c, d), k) in raw_dependencies { let (a, b) = order_index(a, b, len_all_pkgid); let (a, b) = if reverse_alphabetical { (b, a) } else { (a, b) }; let ((dep_name, _), _) = list_of_pkgid[a]; if (list_of_pkgid[b].0).0 == dep_name { continue; } let s = &crate_vers_by_name[dep_name]; let s_last_index = s.len() - 1; let (c, d) = order_index(c, d, s.len()); dependency_by_pkgid[b].push(dep_req_kind( dep_name, &if c == 0 && d == s_last_index { "*".to_string() } else if c == 0 { format!("<={}", s[d].0) } else if d == s_last_index { format!(">={}", s[c].0) } else if c == d { format!("={}", s[c].0) } else { format!(">={}, <={}", s[c].0, s[d].0) }, match k { 0 => DepKind::Normal, 1 => DepKind::Build, // => DepKind::Development, // Development has no impact so don't gen _ => panic!("bad index for DepKind"), }, )) } let mut out: Vec = list_of_pkgid .into_iter() .zip(dependency_by_pkgid.into_iter()) .map(|(((name, ver), allow_deps), deps)| { pkg_dep( (name, ver).to_pkgid(), if !allow_deps { vec![dep_req("bad", "*")] } else { let mut deps = deps; deps.sort_by_key(|d| d.name_in_toml()); deps.dedup_by_key(|d| d.name_in_toml()); deps }, ) }) .collect(); if reverse_alphabetical { // make sure the complicated cases are at the end out.reverse(); } PrettyPrintRegistry(out) }, ) } #[cfg(test)] mod tests { use super::*; use crate::helpers::registry; #[test] fn meta_test_deep_pretty_print_registry() { assert_eq!( &format!( "{:?}", PrettyPrintRegistry(vec![ pkg!(("foo", "1.0.1") => [dep_req("bar", "1")]), pkg!(("foo", "1.0.0") => [dep_req("bar", "2")]), pkg!(("foo", "2.0.0") => [dep_req("bar", "*")]), pkg!(("bar", "1.0.0") => [dep_req("baz", "=1.0.2"), dep_req("other", "1")]), pkg!(("bar", "2.0.0") => [dep_req("baz", "=1.0.1")]), pkg!(("baz", "1.0.2") => [dep_req("other", "2")]), pkg!(("baz", "1.0.1")), pkg!(("cat", "1.0.2") => [dep_req_kind("other", "2", DepKind::Build)]), pkg!(("cat", "1.0.3") => [dep_req_kind("other", "2", DepKind::Development)]), pkg!(("dep_req", "1.0.0")), pkg!(("dep_req", "2.0.0")), ]) ), "vec![pkg!((\"foo\", \"1.0.1\") => [dep_req(\"bar\", \"^1\"),]),\ pkg!((\"foo\", \"1.0.0\") => [dep_req(\"bar\", \"^2\"),]),\ pkg!((\"foo\", \"2.0.0\") => [dep(\"bar\"),]),\ pkg!((\"bar\", \"1.0.0\") => [dep_req(\"baz\", \"=1.0.2\"),dep_req(\"other\", \"^1\"),]),\ pkg!((\"bar\", \"2.0.0\") => [dep_req(\"baz\", \"=1.0.1\"),]),\ pkg!((\"baz\", \"1.0.2\") => [dep_req(\"other\", \"^2\"),]),\ pkg!((\"baz\", \"1.0.1\")),\ pkg!((\"cat\", \"1.0.2\") => [dep_req_kind(\"other\", \"^2\", DepKind::Build, false),]),\ pkg!((\"cat\", \"1.0.3\") => [dep_req_kind(\"other\", \"^2\", DepKind::Development, false),]),\ pkg!((\"dep_req\", \"1.0.0\")),\ pkg!((\"dep_req\", \"2.0.0\")),]" ) } /// This test is to test the generator to ensure /// that it makes registries with large dependency trees #[test] fn meta_test_deep_trees_from_strategy() { use proptest::strategy::ValueTree; use proptest::test_runner::TestRunner; let mut dis = [0; 21]; let strategy = registry_strategy(50, 20, 60); let mut test_runner = TestRunner::deterministic(); for _ in 0..128 { let PrettyPrintRegistry(input) = strategy .new_tree(&mut TestRunner::new_with_rng( Default::default(), test_runner.new_rng(), )) .unwrap() .current(); let reg = registry(input.clone()); for this in input.iter().rev().take(10) { let res = resolve( vec![dep_req(&this.name(), &format!("={}", this.version()))], ®, ); dis[res .as_ref() .map(|x| min(x.len(), dis.len()) - 1) .unwrap_or(0)] += 1; if dis.iter().all(|&x| x > 0) { return; } } } panic!( "In 1280 tries we did not see a wide enough distribution \ of dependency trees! dis: {dis:?}" ); } /// This test is to test the generator to ensure /// that it makes registries that include multiple versions of the same library #[test] fn meta_test_multiple_versions_strategy() { use proptest::strategy::ValueTree; use proptest::test_runner::TestRunner; let mut dis = [0; 10]; let strategy = registry_strategy(50, 20, 60); let mut test_runner = TestRunner::deterministic(); for _ in 0..128 { let PrettyPrintRegistry(input) = strategy .new_tree(&mut TestRunner::new_with_rng( Default::default(), test_runner.new_rng(), )) .unwrap() .current(); let reg = registry(input.clone()); for this in input.iter().rev().take(10) { let res = resolve( vec![dep_req(&this.name(), &format!("={}", this.version()))], ®, ); if let Ok(mut res) = res { let res_len = res.len(); res.sort_by_key(|s| s.name()); res.dedup_by_key(|s| s.name()); dis[min(res_len - res.len(), dis.len() - 1)] += 1; } if dis.iter().all(|&x| x > 0) { return; } } } panic!( "In 1280 tries we did not see a wide enough distribution \ of multiple versions of the same library! dis: {dis:?}" ); } } ================================================ FILE: crates/resolver-tests/src/sat.rs ================================================ use std::collections::hash_map::Entry; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt::Write; use cargo::core::dependency::DepKind; use cargo::core::{Dependency, FeatureMap, FeatureValue, PackageId, Summary}; use cargo::util::interning::{INTERNED_DEFAULT, InternedString}; use cargo_platform::Platform; use varisat::ExtendFormula; const fn num_bits() -> usize { std::mem::size_of::() * 8 } fn log_bits(x: usize) -> usize { if x == 0 { return 0; } assert!(x > 0); (num_bits::() as u32 - x.leading_zeros()) as usize } /// At this point is possible to select every version of every package. /// /// So we need to mark certain versions as incompatible with each other. /// We could add a clause not A, not B for all A and B that are incompatible, fn sat_at_most_one(solver: &mut varisat::Solver<'_>, vars: &[varisat::Var]) { if vars.len() <= 1 { return; } else if vars.len() == 2 { solver.add_clause(&[vars[0].negative(), vars[1].negative()]); return; } else if vars.len() == 3 { solver.add_clause(&[vars[0].negative(), vars[1].negative()]); solver.add_clause(&[vars[0].negative(), vars[2].negative()]); solver.add_clause(&[vars[1].negative(), vars[2].negative()]); return; } // There are more efficient ways to do it for large numbers of versions. // // Use the "Binary Encoding" from // https://www.it.uu.se/research/group/astra/ModRef10/papers/Alan%20M.%20Frisch%20and%20Paul%20A.%20Giannoros.%20SAT%20Encodings%20of%20the%20At-Most-k%20Constraint%20-%20ModRef%202010.pdf let bits: Vec = solver.new_var_iter(log_bits(vars.len())).collect(); for (i, p) in vars.iter().enumerate() { for b in 0..bits.len() { solver.add_clause(&[p.negative(), bits[b].lit(((1 << b) & i) > 0)]); } } } fn sat_at_most_one_by_key( solver: &mut varisat::Solver<'_>, data: impl Iterator, ) -> HashMap> { // No two packages with the same keys set let mut by_keys: HashMap> = HashMap::new(); for (p, v) in data { by_keys.entry(p).or_default().push(v) } for key in by_keys.values() { sat_at_most_one(solver, key); } by_keys } type DependencyVarMap<'a> = HashMap), varisat::Var>>; type DependencyFeatureVarMap<'a> = HashMap< InternedString, HashMap<(DepKind, Option<&'a Platform>), HashMap>, >; fn create_dependencies_vars<'a>( solver: &mut varisat::Solver<'_>, pkg_var: varisat::Var, pkg_dependencies: &'a [Dependency], pkg_features: &FeatureMap, ) -> (DependencyVarMap<'a>, DependencyFeatureVarMap<'a>) { let mut var_for_is_dependencies_used = DependencyVarMap::new(); let mut var_for_is_dependencies_features_used = DependencyFeatureVarMap::new(); for dep in pkg_dependencies { let (name, kind, platform) = (dep.name_in_toml(), dep.kind(), dep.platform()); var_for_is_dependencies_used .entry(name) .or_default() .insert((kind, platform), solver.new_var()); let dep_feature_vars = var_for_is_dependencies_features_used .entry(name) .or_default() .entry((kind, platform)) .or_default(); for &feature_name in dep.features() { if let Entry::Vacant(entry) = dep_feature_vars.entry(feature_name) { entry.insert(solver.new_var()); } } } for feature_values in pkg_features.values() { for feature_value in feature_values { let FeatureValue::DepFeature { dep_name, dep_feature, weak: _, } = *feature_value else { continue; }; for dep_feature_vars in var_for_is_dependencies_features_used .get_mut(&dep_name) .expect("feature dep name exists") .values_mut() { if let Entry::Vacant(entry) = dep_feature_vars.entry(dep_feature) { entry.insert(solver.new_var()); } } } } // If a package dependency is used, then the package is used for dep_var_map in var_for_is_dependencies_used.values() { for dep_var in dep_var_map.values() { solver.add_clause(&[dep_var.negative(), pkg_var.positive()]); } } // If a dependency feature is used, then the dependency is used for (&dep_name, map) in &mut var_for_is_dependencies_features_used { for (&(dep_kind, dep_platform), dep_feature_var_map) in map { for dep_feature_var in dep_feature_var_map.values() { let dep_var_map = &var_for_is_dependencies_used[&dep_name]; let dep_var = dep_var_map[&(dep_kind, dep_platform)]; solver.add_clause(&[dep_feature_var.negative(), dep_var.positive()]); } } } ( var_for_is_dependencies_used, var_for_is_dependencies_features_used, ) } fn process_pkg_dependencies( solver: &mut varisat::Solver<'_>, var_for_is_dependencies_used: &DependencyVarMap<'_>, var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>, pkg_var: varisat::Var, pkg_dependencies: &[Dependency], ) { // Add clauses for package dependencies for dep in pkg_dependencies { let (name, kind, platform) = (dep.name_in_toml(), dep.kind(), dep.platform()); let dep_var_map = &var_for_is_dependencies_used[&name]; let dep_var = dep_var_map[&(kind, platform)]; if !dep.is_optional() { solver.add_clause(&[pkg_var.negative(), dep_var.positive()]); } for &feature_name in dep.features() { let dep_feature_var = &var_for_is_dependencies_features_used[&name][&(kind, platform)][&feature_name]; solver.add_clause(&[dep_var.negative(), dep_feature_var.positive()]); } } } fn process_pkg_features( solver: &mut varisat::Solver<'_>, var_for_is_dependencies_used: &DependencyVarMap<'_>, var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>, pkg_feature_var_map: &HashMap, pkg_dependencies: &[Dependency], pkg_features: &FeatureMap, ) { let optional_dependencies = pkg_dependencies .iter() .filter(|dep| dep.is_optional()) .map(|dep| (dep.kind(), dep.platform(), dep.name_in_toml())) .collect::>(); // Add clauses for package features for (&feature_name, feature_values) in pkg_features { for feature_value in feature_values { let pkg_feature_var = pkg_feature_var_map[&feature_name]; match *feature_value { FeatureValue::Feature(other_feature_name) => { solver.add_clause(&[ pkg_feature_var.negative(), pkg_feature_var_map[&other_feature_name].positive(), ]); } FeatureValue::Dep { dep_name } => { // Add a clause for each dependency with the provided name (normal/build/dev with target) for (&(dep_kind, _), &dep_var) in &var_for_is_dependencies_used[&dep_name] { if dep_kind == DepKind::Development { continue; } solver.add_clause(&[pkg_feature_var.negative(), dep_var.positive()]); } } FeatureValue::DepFeature { dep_name, dep_feature: dep_feature_name, weak, } => { // Behavior of the feature: // * if dependency `dep_name` is not optional, its feature `"dep_feature_name"` is activated. // * if dependency `dep_name` is optional: // - if this is a weak dependency feature: // - feature `"dep_feature_name"` of dependency `dep_name` is activated if `dep_name` has been activated via another feature. // - if this is not a weak dependency feature: // - feature `dep_name` is activated if it exists. // - dependency `dep_name` is activated. // - feature `"dep_feature_name"` of dependency `dep_name` is activated. // Add clauses for each dependency with the provided name (normal/build/dev with target) let dep_var_map = &var_for_is_dependencies_used[&dep_name]; for (&(dep_kind, dep_platform), &dep_var) in dep_var_map { if dep_kind == DepKind::Development { continue; } let dep_feature_var = &var_for_is_dependencies_features_used[&dep_name] [&(dep_kind, dep_platform)][&dep_feature_name]; solver.add_clause(&[ pkg_feature_var.negative(), dep_var.negative(), dep_feature_var.positive(), ]); let key = (dep_kind, dep_platform, dep_name); if !weak && optional_dependencies.contains(&key) { solver.add_clause(&[pkg_feature_var.negative(), dep_var.positive()]); if let Some(other_feature_var) = pkg_feature_var_map.get(&dep_name) { solver.add_clause(&[ pkg_feature_var.negative(), other_feature_var.positive(), ]); } } } } } } } } fn process_compatible_dep_summaries( solver: &mut varisat::Solver<'_>, var_for_is_dependencies_used: &DependencyVarMap<'_>, var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>, var_for_is_packages_used: &HashMap, var_for_is_packages_features_used: &HashMap>, by_name: &HashMap>, pkg_dependencies: &[Dependency], check_dev_dependencies: bool, ) { for dep in pkg_dependencies { if dep.kind() == DepKind::Development && !check_dev_dependencies { continue; } let (name, kind, platform) = (dep.name_in_toml(), dep.kind(), dep.platform()); let dep_var = var_for_is_dependencies_used[&name][&(kind, platform)]; let dep_feature_var_map = &var_for_is_dependencies_features_used[&name][&(kind, platform)]; let compatible_pkg_ids = by_name .get(&dep.package_name()) .into_iter() .flatten() .filter(|s| dep.matches(s)) .filter(|s| dep.features().iter().all(|f| s.features().contains_key(f))) .map(|s| s.package_id()) .collect::>(); // At least one compatible package should be activated let dep_clause = compatible_pkg_ids .iter() .map(|id| var_for_is_packages_used[&id].positive()) .chain([dep_var.negative()]) .collect::>(); solver.add_clause(&dep_clause); for (&feature_name, &dep_feature_var) in dep_feature_var_map { // At least one compatible package with the additional feature should be activated let dep_feature_clause = compatible_pkg_ids .iter() .filter_map(|id| var_for_is_packages_features_used[&id].get(&feature_name)) .map(|var| var.positive()) .chain([dep_feature_var.negative()]) .collect::>(); solver.add_clause(&dep_feature_clause); } if dep.uses_default_features() { // For the selected package for this dependency, the `"default"` feature should be activated if it exists let mut dep_default_clause = vec![dep_var.negative()]; for &id in &compatible_pkg_ids { let s_var = var_for_is_packages_used[&id]; let s_feature_var_map = &var_for_is_packages_features_used[&id]; if let Some(s_default_feature_var) = s_feature_var_map.get(&INTERNED_DEFAULT) { dep_default_clause .extend_from_slice(&[s_var.negative(), s_default_feature_var.positive()]); } else { dep_default_clause.push(s_var.positive()); } } solver.add_clause(&dep_default_clause); } } } /// Resolution can be reduced to the SAT problem. /// /// So this is an alternative implementation /// of the resolver that uses a SAT library for the hard work. This is intended to be easy to read, /// as compared to the real resolver. /// /// For the subset of functionality that are currently made by `registry_strategy`, /// this will find a valid resolution if one exists. /// /// The SAT library does not optimize for the newer version, /// so the selected packages may not match the real resolver. pub struct SatResolver { solver: varisat::Solver<'static>, old_root_vars: Vec, var_for_is_packages_used: HashMap, var_for_is_packages_features_used: HashMap>, by_name: HashMap>, } impl SatResolver { pub fn new<'a>(registry: impl IntoIterator) -> Self { let mut by_name: HashMap> = HashMap::new(); for pkg in registry { by_name.entry(pkg.name()).or_default().push(pkg.clone()); } let mut solver = varisat::Solver::new(); // Create boolean variables for packages and packages features let mut var_for_is_packages_used = HashMap::new(); let mut var_for_is_packages_features_used = HashMap::<_, HashMap<_, _>>::new(); for pkg in by_name.values().flatten() { let pkg_id = pkg.package_id(); var_for_is_packages_used.insert(pkg_id, solver.new_var()); var_for_is_packages_features_used.insert( pkg_id, (pkg.features().keys().map(|&f| (f, solver.new_var()))).collect(), ); } // If a package feature is used, then the package is used for (&pkg_id, pkg_feature_var_map) in &var_for_is_packages_features_used { for pkg_feature_var in pkg_feature_var_map.values() { let pkg_var = var_for_is_packages_used[&pkg_id]; solver.add_clause(&[pkg_feature_var.negative(), pkg_var.positive()]); } } // No two packages with the same links set sat_at_most_one_by_key( &mut solver, by_name .values() .flatten() .map(|s| (s.links(), var_for_is_packages_used[&s.package_id()])) .filter(|(l, _)| l.is_some()), ); // No two semver compatible versions of the same package sat_at_most_one_by_key( &mut solver, var_for_is_packages_used .iter() .map(|(p, &v)| (p.as_activations_key(), v)), ); for pkg in by_name.values().flatten() { let pkg_id = pkg.package_id(); let pkg_dependencies = pkg.dependencies(); let pkg_features = pkg.features(); let pkg_var = var_for_is_packages_used[&pkg_id]; // Create boolean variables for dependencies and dependencies features let (var_for_is_dependencies_used, var_for_is_dependencies_features_used) = create_dependencies_vars(&mut solver, pkg_var, pkg_dependencies, pkg_features); process_pkg_dependencies( &mut solver, &var_for_is_dependencies_used, &var_for_is_dependencies_features_used, pkg_var, pkg_dependencies, ); process_pkg_features( &mut solver, &var_for_is_dependencies_used, &var_for_is_dependencies_features_used, &var_for_is_packages_features_used[&pkg_id], pkg_dependencies, pkg_features, ); process_compatible_dep_summaries( &mut solver, &var_for_is_dependencies_used, &var_for_is_dependencies_features_used, &var_for_is_packages_used, &var_for_is_packages_features_used, &by_name, pkg_dependencies, false, ); } // We don't need to `solve` now. We know that "use nothing" will satisfy all the clauses so far. // But things run faster if we let it spend some time figuring out how the constraints interact before we add assumptions. solver .solve() .expect("docs say it can't error in default config"); SatResolver { solver, old_root_vars: Vec::new(), var_for_is_packages_used, var_for_is_packages_features_used, by_name, } } pub fn sat_resolve(&mut self, root_dependencies: &[Dependency]) -> bool { let SatResolver { solver, old_root_vars, var_for_is_packages_used, var_for_is_packages_features_used, by_name, } = self; let root_var = solver.new_var(); // Create boolean variables for dependencies and dependencies features let (var_for_is_dependencies_used, var_for_is_dependencies_features_used) = create_dependencies_vars(solver, root_var, root_dependencies, &FeatureMap::new()); process_pkg_dependencies( solver, &var_for_is_dependencies_used, &var_for_is_dependencies_features_used, root_var, root_dependencies, ); process_compatible_dep_summaries( solver, &var_for_is_dependencies_used, &var_for_is_dependencies_features_used, var_for_is_packages_used, var_for_is_packages_features_used, by_name, root_dependencies, true, ); // Root package is always used. // Root vars from previous runs are deactivated. let assumption = old_root_vars .iter() .map(|v| v.negative()) .chain([root_var.positive()]) .collect::>(); old_root_vars.push(root_var); solver.assume(&assumption); solver .solve() .expect("docs say it can't error in default config") } pub fn sat_is_valid_solution(&mut self, pkgs: &[(PackageId, Vec)]) -> bool { let contains_pkg = |pkg| pkgs.iter().any(|(p, _)| p == pkg); let contains_pkg_feature = |pkg, f| pkgs.iter().any(|(p, flist)| p == pkg && flist.contains(f)); for (p, _) in pkgs { if p.name() != "root" && !self.var_for_is_packages_used.contains_key(p) { return false; } } // Root vars from previous runs are deactivated let assumption = (self.old_root_vars.iter().map(|v| v.negative())) .chain( self.var_for_is_packages_used .iter() .map(|(p, v)| v.lit(contains_pkg(p))), ) .chain( self.var_for_is_packages_features_used .iter() .flat_map(|(p, fmap)| { fmap.iter() .map(move |(f, v)| v.lit(contains_pkg_feature(p, f))) }), ) .collect::>(); self.solver.assume(&assumption); self.solver .solve() .expect("docs say it can't error in default config") } pub fn used_packages(&self) -> Option { self.solver.model().map(|lits| { let lits: HashSet<_> = lits .iter() .filter(|l| l.is_positive()) .map(|l| l.var()) .collect(); let mut used_packages = BTreeMap::>::new(); for (&p, v) in self.var_for_is_packages_used.iter() { if lits.contains(v) { used_packages.entry(p).or_default(); } } for (&p, map) in &self.var_for_is_packages_features_used { for (&f, v) in map { if lits.contains(v) { used_packages .get_mut(&p) .expect("the feature is activated without the package being activated") .insert(f); } } } let mut out = String::from("used:\n"); for (package, feature_names) in used_packages { writeln!(&mut out, " {package}").unwrap(); for feature_name in feature_names { writeln!(&mut out, " + {feature_name}").unwrap(); } } out }) } } ================================================ FILE: crates/resolver-tests/tests/proptests.rs ================================================ use std::io::IsTerminal; use cargo::util::GlobalContext; use cargo_util::is_ci; use resolver_tests::{ PrettyPrintRegistry, helpers::{dep_req, registry, remove_dep}, registry_strategy, resolve, resolve_and_validated, resolve_with_global_context, sat::SatResolver, }; use proptest::prelude::*; // NOTE: proptest is a form of fuzz testing. It generates random input and makes sure that // certain universal truths are upheld. Therefore, it can pass when there is a problem, // but if it fails then there really is something wrong. When testing something as // complicated as the resolver, the problems can be very subtle and hard to generate. // We have had a history of these tests only failing on PRs long after a bug is introduced. // If you have one of these test fail please report it on #6258, // and if you did not change the resolver then feel free to retry without concern. proptest! { #![proptest_config(ProptestConfig { max_shrink_iters: if is_ci() || !std::io::stderr().is_terminal() { // This attempts to make sure that CI will fail fast, 0 } else { // but that local builds will give a small clear test case. u32::MAX }, result_cache: prop::test_runner::basic_result_cache, .. ProptestConfig::default() })] /// NOTE: if you think this test has failed spuriously see the note at the top of this macro. #[test] fn prop_passes_validation( PrettyPrintRegistry(input) in registry_strategy(50, 20, 60) ) { let reg = registry(input.clone()); let mut sat_resolver = SatResolver::new(®); // There is only a small chance that a crate will be interesting. // So we try some of the most complicated. for this in input.iter().rev().take(20) { let _ = resolve_and_validated( vec![dep_req(&this.name(), &format!("={}", this.version()))], ®, &mut sat_resolver, ); } } /// NOTE: if you think this test has failed spuriously see the note at the top of this macro. #[test] fn prop_minimum_version_errors_the_same( PrettyPrintRegistry(input) in registry_strategy(50, 20, 60) ) { let mut gctx = GlobalContext::default().unwrap(); gctx.nightly_features_allowed = true; gctx .configure( 1, false, None, false, false, false, &None, &["minimal-versions".to_string()], &[], ) .unwrap(); let reg = registry(input.clone()); // There is only a small chance that a crate will be interesting. // So we try some of the most complicated. for this in input.iter().rev().take(10) { let deps = vec![dep_req(&this.name(), &format!("={}", this.version()))]; let res = resolve(deps.clone(), ®); let mres = resolve_with_global_context(deps, ®, &gctx); // `minimal-versions` changes what order the candidates are tried but not the existence of a solution. prop_assert_eq!( res.is_ok(), mres.is_ok(), "minimal-versions and regular resolver disagree about whether `{} = \"={}\"` can resolve", this.name(), this.version() ) } } /// NOTE: if you think this test has failed spuriously see the note at the top of this macro. #[test] fn prop_direct_minimum_version_error_implications( PrettyPrintRegistry(input) in registry_strategy(50, 20, 60) ) { let mut gctx = GlobalContext::default().unwrap(); gctx.nightly_features_allowed = true; gctx .configure( 1, false, None, false, false, false, &None, &["direct-minimal-versions".to_string()], &[], ) .unwrap(); let reg = registry(input.clone()); // There is only a small chance that a crate will be interesting. // So we try some of the most complicated. for this in input.iter().rev().take(10) { let deps = vec![dep_req(&this.name(), &format!("={}", this.version()))]; let res = resolve(deps.clone(), ®); let mres = resolve_with_global_context(deps, ®, &gctx); // `direct-minimal-versions` reduces the number of available solutions, // so we verify that we do not come up with solutions not seen in `maximal-versions`. if res.is_err() { prop_assert!( mres.is_err(), "direct-minimal-versions should not have more solutions than the regular, maximal resolver but found one when resolving `{} = \"={}\"`", this.name(), this.version() ) } if mres.is_ok() { prop_assert!( res.is_ok(), "direct-minimal-versions should not have more solutions than the regular, maximal resolver but found one when resolving `{} = \"={}\"`", this.name(), this.version() ) } } } /// NOTE: if you think this test has failed spuriously see the note at the top of this macro. #[test] fn prop_removing_a_dep_cant_break( PrettyPrintRegistry(input) in registry_strategy(50, 20, 60), indexes_to_remove in prop::collection::vec((any::(), any::()), ..10) ) { let reg = registry(input.clone()); let mut removed_input = input.clone(); for (summary_idx, dep_idx) in indexes_to_remove { if !removed_input.is_empty() { let summary_idx = summary_idx.index(removed_input.len()); let deps = removed_input[summary_idx].dependencies(); if !deps.is_empty() { let new = remove_dep(&removed_input[summary_idx], dep_idx.index(deps.len())); removed_input[summary_idx] = new; } } } let removed_reg = registry(removed_input); // There is only a small chance that a crate will be interesting. // So we try some of the most complicated. for this in input.iter().rev().take(10) { if resolve( vec![dep_req(&this.name(), &format!("={}", this.version()))], ®, ).is_ok() { prop_assert!( resolve( vec![dep_req(&this.name(), &format!("={}", this.version()))], &removed_reg, ).is_ok(), "full index worked for `{} = \"={}\"` but removing some deps broke it!", this.name(), this.version(), ) } } } /// NOTE: if you think this test has failed spuriously see the note at the top of this macro. #[test] fn prop_limited_independence_of_irrelevant_alternatives( PrettyPrintRegistry(input) in registry_strategy(50, 20, 60), indexes_to_unpublish in prop::collection::vec(any::(), ..10) ) { let reg = registry(input.clone()); // There is only a small chance that a crate will be interesting. // So we try some of the most complicated. for this in input.iter().rev().take(10) { let res = resolve( vec![dep_req(&this.name(), &format!("={}", this.version()))], ®, ); match res { Ok(r) => { // If resolution was successful, then unpublishing a version of a crate // that was not selected should not change that. let not_selected: Vec<_> = input .iter().filter(|&x| !r.contains(&x.package_id())).cloned() .collect(); if !not_selected.is_empty() { let indexes_to_unpublish: Vec<_> = indexes_to_unpublish.iter().map(|x| x.get(¬_selected)).collect(); let new_reg = registry( input .iter().filter(|&x| !indexes_to_unpublish.contains(&x)).cloned() .collect(), ); let res = resolve( vec![dep_req(&this.name(), &format!("={}", this.version()))], &new_reg, ); // Note: that we can not assert that the two `res` are identical // as the resolver does depend on irrelevant alternatives. // It uses how constrained a dependency requirement is // to determine what order to evaluate requirements. prop_assert!( res.is_ok(), "unpublishing {:?} stopped `{} = \"={}\"` from working", indexes_to_unpublish.iter().map(|x| x.package_id()).collect::>(), this.name(), this.version() ) } } Err(_) => { // If resolution was unsuccessful, then it should stay unsuccessful // even if any version of a crate is unpublished. let indexes_to_unpublish: Vec<_> = indexes_to_unpublish.iter().map(|x| x.get(&input)).collect(); let new_reg = registry( input .iter().filter(|&x| !indexes_to_unpublish.contains(&x)).cloned() .collect(), ); let res = resolve( vec![dep_req(&this.name(), &format!("={}", this.version()))], &new_reg, ); prop_assert!( res.is_err(), "full index did not work for `{} = \"={}\"` but unpublishing {:?} fixed it!", this.name(), this.version(), indexes_to_unpublish.iter().map(|x| x.package_id()).collect::>(), ) } } } } } ================================================ FILE: crates/resolver-tests/tests/pubgrub.rs ================================================ use cargo::core::{Dependency, dependency::DepKind}; use resolver_tests::{ helpers::{ ToDep, dep, dep_kind, dep_req, dep_req_kind, pkg, pkg_dep, pkg_dep_link, pkg_dep_with, pkg_id_source, registry, }, pkg, resolve, resolve_and_validated, resolve_and_validated_raw, sat::SatResolver, }; #[test] fn test_01_renamed_package() { let reg = registry(vec![ pkg_dep_with( "a", vec!["b".opt().rename("b_package")], &[("default", &["b_package"])], ), pkg("b"), ]); let deps = vec!["a".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_02_renamed_package_no_shadowing() { let reg = registry(vec![ pkg("url"), pkg_dep("wasmi", vec!["wasmparser-nostd".rename("wasmparser")]), pkg_dep("wasmparser", vec!["url".to_dep()]), ]); let deps = vec![dep("wasmi")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_03_prerelease_semver() { let reg = registry(vec![ pkg!("parking_lot_core" => [dep_req("smallvec", "^1.6.1")]), pkg(("smallvec", "2.0.0-alpha.3")), pkg_dep_with( ("tokio", "1.35.1"), vec!["parking_lot_core".opt()], &[("default", &["parking_lot_core"])], ), ]); let deps = vec!["tokio".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_04_cyclic_features() { let reg = registry(vec![pkg_dep_with( "windows", vec![], &[ ("Win32", &["Win32_Foundation"]), ("Win32_Foundation", &["Win32"]), ], )]); let deps = vec!["windows".with(&["Win32_Foundation"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_05_cyclic_optional_dependencies() { let reg = registry(vec![ pkg_dep("async-global-executor", vec!["io-lifetimes".opt()]), pkg_dep( "io-lifetimes", vec!["test".with(&["async-global-executor"])], ), pkg_dep_with( "test", vec!["async-global-executor".opt().with(&["io-lifetimes"])], &[], ), ]); let deps = vec![dep("test")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_06_cyclic_dependencies() { let reg = registry(vec![ pkg(("a", "1.0.0")), pkg_dep(("a", "1.0.1"), vec![dep("dep")]), pkg_dep("dep", vec![dep("a")]), ]); let deps = vec![dep("dep")]; // Cyclic dependencies are not checked in the SAT resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn test_07_self_dependency() { let reg = registry(vec![pkg_dep("dep", vec![dep("dep")])]); let deps = vec![dep("dep")]; // Cyclic dependencies are not checked in the SAT resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn test_08_activated_optional_self_dependency() { let reg = registry(vec![pkg_dep("a", vec!["a".opt()])]); let deps = vec!["a".with(&["a"])]; // Cyclic dependencies are not checked in the SAT resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn test_09_build_dependency_with_same_name() { let reg = registry(vec![ pkg("memchr"), pkg_dep_with( ("regex", "1.4.6"), vec!["memchr".opt()], &[("default", &["memchr"])], ), pkg_dep("sv-parser", vec!["regex".with(&["default"])]), pkg_dep( "svlint", vec![ dep_req("regex", "^1.5"), dep_req_kind("regex", "^1", DepKind::Build), dep("sv-parser"), ], ), ]); let deps = vec![dep("svlint")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_10_root_dev_dependency_with_same_name() { let reg = registry(vec![pkg(("root", "1.0.1"))]); let deps = vec![dep_req_kind("root", "=1.0.1", DepKind::Development).rename("root101")]; let source = pkg_id_source("root", "https://root.example.com"); let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated_raw(deps, ®, source, &mut sat_resolver).is_ok()); } #[test] fn test_11_dev_dependency() { let reg = registry(vec![pkg_dep_with( "burn-core", vec![dep_kind("burn-ndarray", DepKind::Development)], &[("default", &["burn-ndarray/std"])], )]); let deps = vec!["burn-core".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_12_weak_dependencies() { let reg = registry(vec![ pkg_dep_with("borsh", vec!["borsh-derive".opt()], &[("std", &[])]), pkg_dep_with( "rust_decimal", vec!["borsh".opt().with(&["borsh-derive"])], &[("default", &["borsh?/std"])], ), ]); let deps = vec!["rust_decimal".with(&["default"])]; // Weak dependencies are not supported yet in the dependency resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn test_13_weak_dependencies() { let reg = registry(vec![ pkg_dep_with("memchr", vec!["std".opt()], &[("std", &["dep:std"])]), pkg_dep_with( "winnow", vec!["memchr".opt()], &[("default", &["memchr?/std"]), ("simd", &["dep:memchr"])], ), ]); let deps = vec!["winnow".with(&["default"])]; // Weak dependencies are not supported yet in the dependency resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn test_14_weak_dependencies() { let reg = registry(vec![ pkg_dep("a", vec![dep("bad")]), pkg_dep_with("b", vec!["a".opt()], &[("perf-literal", &["dep:a"])]), pkg_dep_with( "c", vec!["b".opt()], &[ ("perf-literal", &["b?/perf-literal"]), ("perf-literal-multisubstring", &["dep:b"]), ], ), pkg_dep_with("dep", vec![dep("c")], &[("default", &["c/perf-literal"])]), ]); let deps = vec!["dep".with(&["default"])]; // Weak dependencies are not supported yet in the dependency resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn test_15_duplicate_sys_crate() { let reg = registry(vec![ pkg_dep_link("js", "js", vec![]), pkg_dep_link("dep", "js", vec![dep("js")]), ]); let deps = vec![dep("dep")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_16_missing_optional_dependency() { let reg = registry(vec![ pkg_dep("b", vec!["c".opt()]), pkg_dep_with("dep", vec![dep("b")], &[("d", &["b/c"])]), ]); let deps = vec!["dep".with(&["d"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_17_feature_shadowing_missing_optional_dependency() { let reg = registry(vec![pkg_dep_with( "rustix", vec!["alloc".opt()], &[ ("alloc", &[]), ("default", &["alloc"]), ("rustc-dep-of-std", &["dep:alloc"]), ], )]); let deps = vec!["rustix".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_18_feature_shadowing_activated_optional_dependency() { let reg = registry(vec![ pkg_dep("alloc", vec![dep("bad")]), pkg_dep_with( "rustix", vec!["alloc".opt()], &[ ("alloc", &[]), ("default", &["dep:alloc"]), ("rustc-dep-of-std", &["alloc"]), ], ), ]); let deps = vec!["rustix".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_19_same_dep_twice_feature_unification() { let reg = registry(vec![ pkg_dep_with( "iced", vec!["iced_wgpu".opt(), "iced_wgpu".opt().with(&["webgl"])], &[("default", &["iced_wgpu"])], ), pkg_dep_with("iced_wgpu", vec![], &[("webgl", &[])]), ]); let deps = vec!["iced".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_20_no_implicit_feature() { let reg = registry(vec![ pkg("c"), pkg_dep_with("ureq", vec!["c".opt()], &[("cookies", &["dep:c"])]), pkg_dep_with("dep", vec![dep("ureq")], &[("cookies", &["ureq/c"])]), ]); let deps = vec!["dep".with(&["cookies"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_21_implicit_feature() { let reg = registry(vec![ pkg("c"), pkg_dep("ureq", vec!["c".opt()]), pkg_dep_with("dep", vec![dep("ureq")], &[("cookies", &["ureq/c"])]), ]); let deps = vec!["dep".with(&["cookies"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_22_missing_explicit_default_feature() { let reg = registry(vec![ pkg_dep_with( "fuel-tx", vec![dep("serde"), "serde_json".opt()], &[("default", &["serde/default"]), ("serde", &["serde_json"])], ), pkg("serde"), ]); let deps = vec!["fuel-tx".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_23_no_need_for_explicit_default_feature() { let reg = registry(vec![ pkg("a"), pkg_dep_with( "b", vec!["a".with_default()], &[("default", &["std"]), ("std", &[])], ), ]); let deps = vec!["b".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_24_dep_feature() { let reg = registry(vec![ pkg_dep_with("proc-macro2", vec![], &[("proc-macro", &[])]), pkg_dep_with( "syn", vec![dep("proc-macro2")], &[("proc-macro", &["proc-macro2/proc-macro"])], ), pkg_dep("serde_derive", vec!["syn".with(&["proc-macro"])]), ]); let deps = vec![dep("serde_derive")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_25_dep_feature() { let reg = registry(vec![ pkg_dep_with("proc-macro2", vec![], &[("proc-macro", &[])]), pkg_dep_with( "syn", vec![dep("proc-macro2")], &[("proc-macro", &["proc-macro2/proc-macro"])], ), ]); let deps = vec!["syn".with(&["proc-macro"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_26_implicit_feature_with_dep_feature() { let reg = registry(vec![ pkg_dep_with("quote", vec![], &[("proc-macro", &[])]), pkg_dep_with( "syn", vec!["quote".opt()], &[("default", &["quote", "quote/proc-macro"])], ), ]); let deps = vec!["syn".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn test_27_dep_feature_activating_shadowing_feature() { let reg = registry(vec![ pkg_dep_with( "a", vec!["b".opt(), "x".opt()], &[("b", &["x"]), ("default", &["b/native"])], ), pkg_dep_with("b", vec![], &[("native", &[])]), ]); let deps = vec!["a".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn test_28_dep_feature_not_activating_shadowing_feature() { let reg = registry(vec![ pkg_dep_with( "fuel-tx", vec![dep("serde"), "serde_json".opt()], &[("default", &["serde/default"]), ("serde", &["serde_json"])], ), pkg_dep_with("serde", vec![], &[("default", &[])]), ]); let deps = vec!["fuel-tx".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } ================================================ FILE: crates/resolver-tests/tests/resolve.rs ================================================ use cargo::core::Dependency; use cargo::core::dependency::DepKind; use cargo::util::GlobalContext; use resolver_tests::{ helpers::{ ToDep, ToPkgId, assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, loc_names, names, pkg, pkg_dep, pkg_dep_with, pkg_id, pkg_loc, registry, }, pkg, resolve, resolve_with_global_context, }; #[test] #[should_panic(expected = "assertion failed: !name.is_empty()")] fn test_dependency_with_empty_name() { // Bug 5229, dependency-names must not be empty dep(""); } #[test] fn test_resolving_empty_dependency_list() { let res = resolve(Vec::new(), ®istry(vec![])).unwrap(); assert_eq!(res, names(&["root"])); } #[test] fn test_resolving_only_package() { let reg = registry(vec![pkg!("foo")]); let res = resolve(vec![dep("foo")], ®).unwrap(); assert_same(&res, &names(&["root", "foo"])); } #[test] fn test_resolving_one_dep() { let reg = registry(vec![pkg!("foo"), pkg!("bar")]); let res = resolve(vec![dep("foo")], ®).unwrap(); assert_same(&res, &names(&["root", "foo"])); } #[test] fn test_resolving_multiple_deps() { let reg = registry(vec![pkg!("foo"), pkg!("bar"), pkg!("baz")]); let res = resolve(vec![dep("foo"), dep("baz")], ®).unwrap(); assert_same(&res, &names(&["root", "foo", "baz"])); } #[test] fn test_resolving_transitive_deps() { let reg = registry(vec![pkg!("foo"), pkg!("bar" => ["foo"])]); let res = resolve(vec![dep("bar")], ®).unwrap(); assert_same(&res, &names(&["root", "foo", "bar"])); } #[test] fn test_resolving_common_transitive_deps() { let reg = registry(vec![pkg!("foo" => ["bar"]), pkg!("bar")]); let res = resolve(vec![dep("foo"), dep("bar")], ®).unwrap(); assert_same(&res, &names(&["root", "foo", "bar"])); } #[test] fn test_resolving_with_same_name() { let list = vec![ pkg_loc("foo", "https://first.example.com"), pkg_loc("bar", "https://second.example.com"), ]; let reg = registry(list); let res = resolve( vec![ dep_loc("foo", "https://first.example.com"), dep_loc("bar", "https://second.example.com"), ], ®, ) .unwrap(); let mut names = loc_names(&[ ("foo", "https://first.example.com"), ("bar", "https://second.example.com"), ]); names.push(pkg_id("root")); assert_same(&res, &names); } #[test] fn test_resolving_with_dev_deps() { let reg = registry(vec![ pkg!("foo" => ["bar", dep_kind("baz", DepKind::Development)]), pkg!("baz" => ["bat", dep_kind("bam", DepKind::Development)]), pkg!("bar"), pkg!("bat"), ]); let res = resolve( vec![dep("foo"), dep_kind("baz", DepKind::Development)], ®, ) .unwrap(); assert_same(&res, &names(&["root", "foo", "bar", "baz", "bat"])); } #[test] fn resolving_with_many_versions() { let reg = registry(vec![pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.2"))]); let res = resolve(vec![dep("foo")], ®).unwrap(); assert_same(&res, &names(&[("root", "1.0.0"), ("foo", "1.0.2")])); } #[test] fn resolving_with_specific_version() { let reg = registry(vec![pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.2"))]); let res = resolve(vec![dep_req("foo", "=1.0.1")], ®).unwrap(); assert_same(&res, &names(&[("root", "1.0.0"), ("foo", "1.0.1")])); } #[test] fn test_resolving_maximum_version_with_transitive_deps() { let reg = registry(vec![ pkg!(("util", "1.2.2")), pkg!(("util", "1.0.0")), pkg!(("util", "1.1.1")), pkg!("foo" => [dep_req("util", "1.0.0")]), pkg!("bar" => [dep_req("util", ">=1.0.1")]), ]); let res = resolve(vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")], ®).unwrap(); assert_contains( &res, &names(&[ ("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "1.0.0"), ("util", "1.2.2"), ]), ); assert!(!res.contains(&("util", "1.0.1").to_pkgid())); assert!(!res.contains(&("util", "1.1.1").to_pkgid())); } #[test] fn test_resolving_minimum_version_with_transitive_deps() { let reg = registry(vec![ pkg!(("util", "1.2.2")), pkg!(("util", "1.0.0")), pkg!(("util", "1.1.1")), pkg!("foo" => [dep_req("util", "1.0.0")]), pkg!("bar" => [dep_req("util", ">=1.0.1")]), ]); let mut gctx = GlobalContext::default().unwrap(); // -Z minimal-versions // When the minimal-versions config option is specified then the lowest // possible version of a package should be selected. "util 1.0.0" can't be // selected because of the requirements of "bar", so the minimum version // must be 1.1.1. gctx.nightly_features_allowed = true; gctx.configure( 1, false, None, false, false, false, &None, &["minimal-versions".to_string()], &[], ) .unwrap(); let res = resolve_with_global_context( vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")], ®, &gctx, ) .unwrap() .into_iter() .map(|(pkg, _)| pkg) .collect::>(); assert_contains( &res, &names(&[ ("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "1.0.0"), ("util", "1.1.1"), ]), ); assert!(!res.contains(&("util", "1.2.2").to_pkgid())); assert!(!res.contains(&("util", "1.0.0").to_pkgid())); } #[test] fn resolving_incompat_versions() { let reg = registry(vec![ pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.2")), pkg!("bar" => [dep_req("foo", "=1.0.2")]), ]); assert!(resolve(vec![dep_req("foo", "=1.0.1"), dep("bar")], ®).is_err()); } #[test] fn resolving_wrong_case_from_registry() { // In the future we may #5678 allow this to happen. // For back compatibility reasons, we probably won't. // But we may want to future prove ourselves by understanding it. // This test documents the current behavior. let reg = registry(vec![pkg!(("foo", "1.0.0")), pkg!("bar" => ["Foo"])]); assert!(resolve(vec![dep("bar")], ®).is_err()); } #[test] fn resolving_mis_hyphenated_from_registry() { // In the future we may #2775 allow this to happen. // For back compatibility reasons, we probably won't. // But we may want to future prove ourselves by understanding it. // This test documents the current behavior. let reg = registry(vec![pkg!(("fo-o", "1.0.0")), pkg!("bar" => ["fo_o"])]); assert!(resolve(vec![dep("bar")], ®).is_err()); } #[test] fn resolving_backtrack() { let reg = registry(vec![ pkg!(("foo", "1.0.2") => [dep("bar")]), pkg!(("foo", "1.0.1") => [dep("baz")]), pkg!("bar" => [dep_req("foo", "=2.0.2")]), pkg!("baz"), ]); let res = resolve(vec![dep_req("foo", "^1")], ®).unwrap(); assert_contains( &res, &names(&[("root", "1.0.0"), ("foo", "1.0.1"), ("baz", "1.0.0")]), ); } #[test] fn resolving_backtrack_features() { // test for cargo/issues/4347 let mut bad = dep("bar"); bad.set_features(vec!["bad"]); let reg = registry(vec![ pkg!(("foo", "1.0.2") => [bad]), pkg!(("foo", "1.0.1") => [dep("bar")]), pkg!("bar"), ]); let res = resolve(vec![dep_req("foo", "^1")], ®).unwrap(); assert_contains( &res, &names(&[("root", "1.0.0"), ("foo", "1.0.1"), ("bar", "1.0.0")]), ); } #[test] fn resolving_allows_multiple_compatible_versions() { let reg = registry(vec![ pkg!(("foo", "1.0.0")), pkg!(("foo", "2.0.0")), pkg!(("foo", "0.1.0")), pkg!(("foo", "0.2.0")), pkg!("bar" => ["d1", "d2", "d3", "d4"]), pkg!("d1" => [dep_req("foo", "1")]), pkg!("d2" => [dep_req("foo", "2")]), pkg!("d3" => [dep_req("foo", "0.1")]), pkg!("d4" => [dep_req("foo", "0.2")]), ]); let res = resolve(vec![dep("bar")], ®).unwrap(); assert_same( &res, &names(&[ ("root", "1.0.0"), ("foo", "1.0.0"), ("foo", "2.0.0"), ("foo", "0.1.0"), ("foo", "0.2.0"), ("d1", "1.0.0"), ("d2", "1.0.0"), ("d3", "1.0.0"), ("d4", "1.0.0"), ("bar", "1.0.0"), ]), ); } #[test] fn resolving_with_deep_backtracking() { let reg = registry(vec![ pkg!(("foo", "1.0.1") => [dep_req("bar", "1")]), pkg!(("foo", "1.0.0") => [dep_req("bar", "2")]), pkg!(("bar", "1.0.0") => [dep_req("baz", "=1.0.2"), dep_req("other", "1")]), pkg!(("bar", "2.0.0") => [dep_req("baz", "=1.0.1")]), pkg!(("baz", "1.0.2") => [dep_req("other", "2")]), pkg!(("baz", "1.0.1")), pkg!(("dep_req", "1.0.0")), pkg!(("dep_req", "2.0.0")), ]); let res = resolve(vec![dep_req("foo", "1")], ®).unwrap(); assert_same( &res, &names(&[ ("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "2.0.0"), ("baz", "1.0.1"), ]), ); } #[test] fn resolving_with_sys_crates() { // This is based on issues/4902 // With `l` a normal library we get 2copies so everyone gets the newest compatible. // But `l-sys` a library with a links attribute we make sure there is only one. let reg = registry(vec![ pkg!(("l-sys", "0.9.1")), pkg!(("l-sys", "0.10.0")), pkg!(("l", "0.9.1")), pkg!(("l", "0.10.0")), pkg!(("d", "1.0.0") => [dep_req("l-sys", ">=0.8.0, <=0.10.0"), dep_req("l", ">=0.8.0, <=0.10.0")]), pkg!(("r", "1.0.0") => [dep_req("l-sys", "0.9"), dep_req("l", "0.9")]), ]); let res = resolve(vec![dep_req("d", "1"), dep_req("r", "1")], ®).unwrap(); assert_same( &res, &names(&[ ("root", "1.0.0"), ("d", "1.0.0"), ("r", "1.0.0"), ("l-sys", "0.9.1"), ("l", "0.9.1"), ("l", "0.10.0"), ]), ); } #[test] fn resolving_with_constrained_sibling_backtrack_parent() { // There is no point in considering all of the backtrack_trap{1,2} // candidates since they can't change the result of failing to // resolve 'constrained'. Cargo should (ideally) skip past them and resume // resolution once the activation of the parent, 'bar', is rolled back. // Note that the traps are slightly more constrained to make sure they // get picked first. let mut reglist = vec![ pkg!(("foo", "1.0.0") => [dep_req("bar", "1.0"), dep_req("constrained", "=1.0.0")]), pkg!(("bar", "1.0.0") => [dep_req("backtrack_trap1", "1.0.2"), dep_req("backtrack_trap2", "1.0.2"), dep_req("constrained", "1.0.0")]), pkg!(("constrained", "1.0.0")), pkg!(("backtrack_trap1", "1.0.0")), pkg!(("backtrack_trap2", "1.0.0")), ]; // Bump this to make the test harder - it adds more versions of bar that will // fail to resolve, and more versions of the traps to consider. const NUM_BARS_AND_TRAPS: usize = 50; // minimum 2 for i in 1..NUM_BARS_AND_TRAPS { let vsn = format!("1.0.{}", i); reglist.push( pkg!(("bar", vsn.clone()) => [dep_req("backtrack_trap1", "1.0.2"), dep_req("backtrack_trap2", "1.0.2"), dep_req("constrained", "1.0.1")]), ); reglist.push(pkg!(("backtrack_trap1", vsn.clone()))); reglist.push(pkg!(("backtrack_trap2", vsn.clone()))); reglist.push(pkg!(("constrained", vsn.clone()))); } let reg = registry(reglist); let res = resolve(vec![dep_req("foo", "1")], ®).unwrap(); assert_contains( &res, &names(&[ ("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "1.0.0"), ("constrained", "1.0.0"), ]), ); } #[test] fn resolving_with_many_equivalent_backtracking() { let mut reglist = Vec::new(); const DEPTH: usize = 200; const BRANCHING_FACTOR: usize = 100; // Each level depends on the next but the last level does not exist. // Without cashing we need to test every path to the last level O(BRANCHING_FACTOR ^ DEPTH) // and this test will time out. With cashing we need to discover that none of these // can be activated O(BRANCHING_FACTOR * DEPTH) for l in 0..DEPTH { let name = format!("level{}", l); let next = format!("level{}", l + 1); for i in 1..BRANCHING_FACTOR { let vsn = format!("1.0.{}", i); reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())])); } } let reg = registry(reglist.clone()); let res = resolve(vec![dep("level0")], ®); assert!(res.is_err()); // It is easy to write code that quickly returns an error. // Lets make sure we can find a good answer if it is there. reglist.push(pkg!(("level0", "1.0.0"))); let reg = registry(reglist.clone()); let res = resolve(vec![dep("level0")], ®).unwrap(); assert_contains(&res, &names(&[("root", "1.0.0"), ("level0", "1.0.0")])); // Make sure we have not special case no candidates. reglist.push(pkg!(("constrained", "1.1.0"))); reglist.push(pkg!(("constrained", "1.0.0"))); reglist.push( pkg!((format!("level{}", DEPTH).as_str(), "1.0.0") => [dep_req("constrained", "=1.0.0")]), ); let reg = registry(reglist.clone()); let res = resolve(vec![dep("level0"), dep("constrained")], ®).unwrap(); assert_contains( &res, &names(&[ ("root", "1.0.0"), ("level0", "1.0.0"), ("constrained", "1.1.0"), ]), ); let reg = registry(reglist.clone()); let res = resolve(vec![dep_req("level0", "1.0.1"), dep("constrained")], ®).unwrap(); assert_contains( &res, &names(&[ ("root", "1.0.0"), (format!("level{}", DEPTH).as_str(), "1.0.0"), ("constrained", "1.0.0"), ]), ); let reg = registry(reglist); let res = resolve( vec![dep_req("level0", "1.0.1"), dep_req("constrained", "1.1.0")], ®, ); assert!(res.is_err()); } #[test] fn resolving_with_deep_traps() { let mut reglist = Vec::new(); const DEPTH: usize = 200; const BRANCHING_FACTOR: usize = 100; // Each backtrack_trap depends on the next, and adds a backtrack frame. // None of witch is going to help with `bad`. for l in 0..DEPTH { let name = format!("backtrack_trap{}", l); let next = format!("backtrack_trap{}", l + 1); for i in 1..BRANCHING_FACTOR { let vsn = format!("1.0.{}", i); reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())])); } } { let name = format!("backtrack_trap{}", DEPTH); for i in 1..BRANCHING_FACTOR { let vsn = format!("1.0.{}", i); reglist.push(pkg!((name.as_str(), vsn.as_str()))); } } { // slightly less constrained to make sure `cloaking` gets picked last. for i in 1..(BRANCHING_FACTOR + 10) { let vsn = format!("1.0.{}", i); reglist.push(pkg!(("cloaking", vsn.as_str()) => [dep_req("bad", "1.0.1")])); } } let reg = registry(reglist); let res = resolve(vec![dep("backtrack_trap0"), dep("cloaking")], ®); assert!(res.is_err()); } #[test] fn resolving_with_constrained_cousins_backtrack() { let mut reglist = Vec::new(); const DEPTH: usize = 100; const BRANCHING_FACTOR: usize = 50; // Each backtrack_trap depends on the next. // The last depends on a specific ver of constrained. for l in 0..DEPTH { let name = format!("backtrack_trap{}", l); let next = format!("backtrack_trap{}", l + 1); for i in 1..BRANCHING_FACTOR { let vsn = format!("1.0.{}", i); reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())])); } } { let name = format!("backtrack_trap{}", DEPTH); for i in 1..BRANCHING_FACTOR { let vsn = format!("1.0.{}", i); reglist.push( pkg!((name.as_str(), vsn.as_str()) => [dep_req("constrained", ">=1.1.0, <=2.0.0")]), ); } } { // slightly less constrained to make sure `constrained` gets picked last. for i in 0..(BRANCHING_FACTOR + 10) { let vsn = format!("1.0.{}", i); reglist.push(pkg!(("constrained", vsn.as_str()))); } reglist.push(pkg!(("constrained", "1.1.0"))); reglist.push(pkg!(("constrained", "2.0.0"))); reglist.push(pkg!(("constrained", "2.0.1"))); } reglist.push(pkg!(("cloaking", "1.0.0") => [dep_req("constrained", "~1.0.0")])); let reg = registry(reglist.clone()); // `backtrack_trap0 = "*"` is a lot of ways of saying `constrained = ">=1.1.0, <=2.0.0"` // but `constrained= "2.0.1"` is already picked. // Only then to try and solve `constrained= "~1.0.0"` which is incompatible. let res = resolve( vec![ dep("backtrack_trap0"), dep_req("constrained", "2.0.1"), dep("cloaking"), ], ®, ); assert!(res.is_err()); // Each level depends on the next but the last depends on incompatible deps. // Let's make sure that we can cache that a dep has incompatible deps. for l in 0..DEPTH { let name = format!("level{}", l); let next = format!("level{}", l + 1); for i in 1..BRANCHING_FACTOR { let vsn = format!("1.0.{}", i); reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())])); } } reglist.push( pkg!((format!("level{}", DEPTH).as_str(), "1.0.0") => [dep("backtrack_trap0"), dep("cloaking") ]), ); let reg = registry(reglist); let res = resolve(vec![dep("level0"), dep_req("constrained", "2.0.1")], ®); assert!(res.is_err()); let res = resolve(vec![dep("level0"), dep_req("constrained", "2.0.0")], ®).unwrap(); assert_contains( &res, &names(&[("constrained", "2.0.0"), ("cloaking", "1.0.0")]), ); } #[test] fn resolving_with_constrained_sibling_backtrack_activation() { // It makes sense to resolve most-constrained deps first, but // with that logic the backtrack traps here come between the two // attempted resolutions of 'constrained'. When backtracking, // cargo should skip past them and resume resolution once the // number of activations for 'constrained' changes. let mut reglist = vec![ pkg!(("foo", "1.0.0") => [dep_req("bar", "=1.0.0"), dep_req("backtrack_trap1", "1.0"), dep_req("backtrack_trap2", "1.0"), dep_req("constrained", "<=1.0.60")]), pkg!(("bar", "1.0.0") => [dep_req("constrained", ">=1.0.60")]), ]; // Bump these to make the test harder, but you'll also need to // change the version constraints on `constrained` above. To correctly // exercise Cargo, the relationship between the values is: // NUM_CONSTRAINED - vsn < NUM_TRAPS < vsn // to make sure the traps are resolved between `constrained`. const NUM_TRAPS: usize = 45; // min 1 const NUM_CONSTRAINED: usize = 100; // min 1 for i in 0..NUM_TRAPS { let vsn = format!("1.0.{}", i); reglist.push(pkg!(("backtrack_trap1", vsn.clone()))); reglist.push(pkg!(("backtrack_trap2", vsn.clone()))); } for i in 0..NUM_CONSTRAINED { let vsn = format!("1.0.{}", i); reglist.push(pkg!(("constrained", vsn.clone()))); } let reg = registry(reglist); let res = resolve(vec![dep_req("foo", "1")], ®).unwrap(); assert_contains( &res, &names(&[ ("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "1.0.0"), ("constrained", "1.0.60"), ]), ); } #[test] fn resolving_with_constrained_sibling_transitive_dep_effects() { // When backtracking due to a failed dependency, if Cargo is // trying to be clever and skip irrelevant dependencies, care must // be taken to not miss the transitive effects of alternatives. E.g. // in the right-to-left resolution of the graph below, B may // affect whether D is successfully resolved. // // A // / | \ // B C D // | | // C D let reg = registry(vec![ pkg!(("A", "1.0.0") => [dep_req("B", "1.0"), dep_req("C", "1.0"), dep_req("D", "1.0.100")]), pkg!(("B", "1.0.0") => [dep_req("C", ">=1.0.0")]), pkg!(("B", "1.0.1") => [dep_req("C", ">=1.0.1")]), pkg!(("C", "1.0.0") => [dep_req("D", "1.0.0")]), pkg!(("C", "1.0.1") => [dep_req("D", ">=1.0.1,<1.0.100")]), pkg!(("C", "1.0.2") => [dep_req("D", ">=1.0.2,<1.0.100")]), pkg!(("D", "1.0.0")), pkg!(("D", "1.0.1")), pkg!(("D", "1.0.2")), pkg!(("D", "1.0.100")), pkg!(("D", "1.0.101")), pkg!(("D", "1.0.102")), pkg!(("D", "1.0.103")), pkg!(("D", "1.0.104")), pkg!(("D", "1.0.105")), ]); let res = resolve(vec![dep_req("A", "1")], ®).unwrap(); assert_same( &res, &names(&[ ("root", "1.0.0"), ("A", "1.0.0"), ("B", "1.0.0"), ("C", "1.0.0"), ("D", "1.0.105"), ]), ); } #[test] fn incomplete_information_skipping() { // When backtracking due to a failed dependency, if Cargo is // trying to be clever and skip irrelevant dependencies, care must // be taken to not miss the transitive effects of alternatives. // Fuzzing discovered that for some reason cargo was skipping based // on incomplete information in the following case: // minimized bug found in: // https://github.com/rust-lang/cargo/commit/003c29b0c71e5ea28fbe8e72c148c755c9f3f8d9 let input = vec![ pkg!(("a", "1.0.0")), pkg!(("a", "1.1.0")), pkg!("b" => [dep("a")]), pkg!(("c", "1.0.0")), pkg!(("c", "1.1.0")), pkg!("d" => [dep_req("c", "=1.0")]), pkg!(("e", "1.0.0")), pkg!(("e", "1.1.0") => [dep_req("c", "1.1")]), pkg!("to_yank"), pkg!(("f", "1.0.0") => [ dep("to_yank"), dep("d"), ]), pkg!(("f", "1.1.0") => [dep("d")]), pkg!("g" => [ dep("b"), dep("e"), dep("f"), ]), ]; let reg = registry(input.clone()); let res = resolve(vec![dep("g")], ®).unwrap(); let package_to_yank = "to_yank".to_pkgid(); // this package is not used in the resolution. assert!(!res.contains(&package_to_yank)); // so when we yank it let new_reg = registry( input .iter() .filter(|&x| package_to_yank != x.package_id()) .cloned() .collect(), ); assert_eq!(input.len(), new_reg.len() + 1); // it should still build assert!(resolve(vec![dep("g")], &new_reg).is_ok()); } #[test] fn incomplete_information_skipping_2() { // When backtracking due to a failed dependency, if Cargo is // trying to be clever and skip irrelevant dependencies, care must // be taken to not miss the transitive effects of alternatives. // Fuzzing discovered that for some reason cargo was skipping based // on incomplete information in the following case: // https://github.com/rust-lang/cargo/commit/003c29b0c71e5ea28fbe8e72c148c755c9f3f8d9 let input = vec![ pkg!(("b", "3.8.10")), pkg!(("b", "8.7.4")), pkg!(("b", "9.4.6")), pkg!(("c", "1.8.8")), pkg!(("c", "10.2.5")), pkg!(("d", "4.1.2") => [ dep_req("bad", "=6.10.9"), ]), pkg!(("d", "5.5.6")), pkg!(("d", "5.6.10")), pkg!(("to_yank", "8.0.1")), pkg!(("to_yank", "8.8.1")), pkg!(("e", "4.7.8") => [ dep_req("d", ">=5.5.6, <=5.6.10"), dep_req("to_yank", "=8.0.1"), ]), pkg!(("e", "7.4.9") => [ dep_req("bad", "=4.7.5"), ]), pkg!("f" => [ dep_req("d", ">=4.1.2, <=5.5.6"), ]), pkg!("g" => [ dep("bad"), ]), pkg!(("h", "3.8.3") => [ dep("g"), ]), pkg!(("h", "6.8.3") => [ dep("f"), ]), pkg!(("h", "8.1.9") => [ dep_req("to_yank", "=8.8.1"), ]), pkg!("i" => [ dep("b"), dep("c"), dep("e"), dep("h"), ]), ]; let reg = registry(input.clone()); let res = resolve(vec![dep("i")], ®).unwrap(); let package_to_yank = ("to_yank", "8.8.1").to_pkgid(); // this package is not used in the resolution. assert!(!res.contains(&package_to_yank)); // so when we yank it let new_reg = registry( input .iter() .filter(|&x| package_to_yank != x.package_id()) .cloned() .collect(), ); assert_eq!(input.len(), new_reg.len() + 1); // it should still build assert!(resolve(vec![dep("i")], &new_reg).is_ok()); } #[test] fn incomplete_information_skipping_3() { // When backtracking due to a failed dependency, if Cargo is // trying to be clever and skip irrelevant dependencies, care must // be taken to not miss the transitive effects of alternatives. // Fuzzing discovered that for some reason cargo was skipping based // on incomplete information in the following case: // minimized bug found in: // https://github.com/rust-lang/cargo/commit/003c29b0c71e5ea28fbe8e72c148c755c9f3f8d9 let input = vec![ pkg! {("to_yank", "3.0.3")}, pkg! {("to_yank", "3.3.0")}, pkg! {("to_yank", "3.3.1")}, pkg! {("a", "3.3.0") => [ dep_req("to_yank", "=3.0.3"), ] }, pkg! {("a", "3.3.2") => [ dep_req("to_yank", "<=3.3.0"), ] }, pkg! {("b", "0.1.3") => [ dep_req("a", "=3.3.0"), ] }, pkg! {("b", "2.0.2") => [ dep_req("to_yank", "3.3.0"), dep("a"), ] }, pkg! {("b", "2.3.3") => [ dep_req("to_yank", "3.3.0"), dep_req("a", "=3.3.0"), ] }, ]; let reg = registry(input.clone()); let res = resolve(vec![dep("b")], ®).unwrap(); let package_to_yank = ("to_yank", "3.0.3").to_pkgid(); // this package is not used in the resolution. assert!(!res.contains(&package_to_yank)); // so when we yank it let new_reg = registry( input .iter() .filter(|&x| package_to_yank != x.package_id()) .cloned() .collect(), ); assert_eq!(input.len(), new_reg.len() + 1); // it should still build assert!(resolve(vec![dep("b")], &new_reg).is_ok()); } #[test] fn resolving_but_no_exists() { let reg = registry(vec![]); let res = resolve(vec![dep_req("foo", "1")], ®); assert!(res.is_err()); assert_eq!( res.err().unwrap().to_string(), "no matching package named `foo` found\n\ location searched: registry `https://example.com/`\n\ required by package `root v1.0.0 (registry `https://example.com/`)`\ " ); } #[test] fn resolving_cycle() { let reg = registry(vec![pkg!("foo" => ["foo"])]); let _ = resolve(vec![dep_req("foo", "1")], ®); } #[test] fn hard_equality() { let reg = registry(vec![ pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.0")), pkg!(("bar", "1.0.0") => [dep_req("foo", "1.0.0")]), ]); let res = resolve(vec![dep_req("bar", "1"), dep_req("foo", "=1.0.0")], ®).unwrap(); assert_same( &res, &names(&[("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "1.0.0")]), ); } #[test] fn large_conflict_cache() { let mut input = vec![ pkg!(("last", "0.0.0") => [dep("bad")]), // just to make sure last is less constrained ]; let mut root_deps = vec![dep("last")]; const NUM_VERSIONS: u8 = 20; for name in 0..=NUM_VERSIONS { // a large number of conflicts can easily be generated by a sys crate. let sys_name = format!("{}-sys", (b'a' + name) as char); let in_len = input.len(); input.push(pkg!(("last", format!("{}.0.0", in_len)) => [dep_req(&sys_name, "=0.0.0")])); root_deps.push(dep_req(&sys_name, ">= 0.0.1")); // a large number of conflicts can also easily be generated by a major release version. let plane_name = format!("{}", (b'a' + name) as char); let in_len = input.len(); input.push(pkg!(("last", format!("{}.0.0", in_len)) => [dep_req(&plane_name, "=1.0.0")])); root_deps.push(dep_req(&plane_name, ">= 1.0.1")); for i in 0..=NUM_VERSIONS { input.push(pkg!((&sys_name, format!("{}.0.0", i)))); input.push(pkg!((&plane_name, format!("1.0.{}", i)))); } } let reg = registry(input); let _ = resolve(root_deps, ®); } #[test] fn resolving_slow_case_missing_feature() { let mut reg = Vec::new(); const LAST_CRATE_VERSION_COUNT: usize = 50; // increase in resolve time is at least cubic over `INTERMEDIATE_CRATES_VERSION_COUNT`. // it should be `>= LAST_CRATE_VERSION_COUNT` to reproduce slowdown. const INTERMEDIATE_CRATES_VERSION_COUNT: usize = LAST_CRATE_VERSION_COUNT + 5; // should be `>= 2` to reproduce slowdown const TRANSITIVE_CRATES_COUNT: usize = 3; reg.push(pkg_dep_with(("last", "1.0.0"), vec![], &[("f", &[])])); for v in 1..LAST_CRATE_VERSION_COUNT { reg.push(pkg(("last", format!("1.0.{v}")))); } reg.push(pkg_dep( ("dep", "1.0.0"), vec![ dep("last"), // <-- needed to reproduce slowdown dep_req("intermediate-1", "1.0.0"), ], )); for n in 0..INTERMEDIATE_CRATES_VERSION_COUNT { let version = format!("1.0.{n}"); for c in 1..TRANSITIVE_CRATES_COUNT { reg.push(pkg_dep( (format!("intermediate-{c}"), &version), vec![dep_req(&format!("intermediate-{}", c + 1), &version)], )); } reg.push(pkg_dep( (format!("intermediate-{TRANSITIVE_CRATES_COUNT}"), &version), vec![dep_req("last", "1.0.0").with(&["f"])], )); } let deps = vec![dep("dep")]; let _ = resolve(deps, ®); } #[test] fn cyclic_good_error_message() { let input = vec![ pkg!(("A", "0.0.0") => [dep("C")]), pkg!(("B", "0.0.0") => [dep("C")]), pkg!(("C", "0.0.0") => [dep("A")]), ]; let reg = registry(input); let error = resolve(vec![dep("A"), dep("B")], ®).unwrap_err(); println!("{}", error); assert_eq!("\ cyclic package dependency: package `A v0.0.0 (registry `https://example.com/`)` depends on itself. Cycle: package `A v0.0.0 (registry `https://example.com/`)` ... which satisfies dependency `A = \"*\"` of package `C v0.0.0 (registry `https://example.com/`)` ... which satisfies dependency `C = \"*\"` of package `A v0.0.0 (registry `https://example.com/`)`\ ", error.to_string()); } #[test] fn shortest_path_in_error_message() { let input = vec![ pkg!(("F", "0.1.2")), pkg!(("F", "0.1.1") => [dep("bad"),]), pkg!(("F", "0.1.0") => [dep("bad"),]), pkg!("E" => [dep_req("F", "^0.1.2"),]), pkg!("D" => [dep_req("F", "^0.1.2"),]), pkg!("C" => [dep("D"),]), pkg!("A" => [dep("C"),dep("E"),dep_req("F", "<=0.1.1"),]), ]; let error = resolve(vec![dep("A")], ®istry(input)).unwrap_err(); println!("{}", error); assert_eq!( "\ failed to select a version for `F`. ... required by package `A v1.0.0 (registry `https://example.com/`)` ... which satisfies dependency `A = \"*\"` of package `root v1.0.0 (registry `https://example.com/`)` versions that meet the requirements `<=0.1.1` are: 0.1.1, 0.1.0 all possible versions conflict with previously selected packages. previously selected package `F v0.1.2 (registry `https://example.com/`)` ... which satisfies dependency `F = \"^0.1.2\"` of package `E v1.0.0 (registry `https://example.com/`)` ... which satisfies dependency `E = \"*\"` of package `A v1.0.0 (registry `https://example.com/`)` ... which satisfies dependency `A = \"*\"` of package `root v1.0.0 (registry `https://example.com/`)` failed to select a version for `F` which could resolve this conflict\ ", error.to_string() ); } ================================================ FILE: crates/resolver-tests/tests/validated.rs ================================================ use cargo::core::{Dependency, dependency::DepKind}; use resolver_tests::{ helpers::{ ToDep, dep, dep_kind, dep_platform, dep_req, dep_req_kind, dep_req_platform, pkg, pkg_dep, pkg_dep_with, registry, }, pkg, resolve, resolve_and_validated, sat::SatResolver, }; #[test] fn off_by_one_bug() { let input = vec![ pkg!(("A-sys", "0.0.1")), pkg!(("A-sys", "0.0.4")), pkg!(("A-sys", "0.0.6")), pkg!(("A-sys", "0.0.7")), pkg!(("NA", "0.0.0") => [dep_req("A-sys", "<= 0.0.5"),]), pkg!(("NA", "0.0.1") => [dep_req("A-sys", ">= 0.0.6, <= 0.0.8"),]), pkg!(("a", "0.0.1")), pkg!(("a", "0.0.2")), pkg!(("aa", "0.0.0") => [dep_req("A-sys", ">= 0.0.4, <= 0.0.6"),dep_req("NA", "<= 0.0.0"),]), pkg!(("f", "0.0.3") => [dep("NA"),dep_req("a", "<= 0.0.2"),dep("aa"),]), ]; let reg = registry(input); let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(vec![dep("f")], ®, &mut sat_resolver).is_ok()); } #[test] fn conflict_store_bug() { let input = vec![ pkg!(("A", "0.0.3")), pkg!(("A", "0.0.5")), pkg!(("A", "0.0.9") => [dep("bad"),]), pkg!(("A", "0.0.10") => [dep("bad"),]), pkg!(("L-sys", "0.0.1") => [dep("bad"),]), pkg!(("L-sys", "0.0.5")), pkg!(("R", "0.0.4") => [ dep_req("L-sys", "= 0.0.5"), ]), pkg!(("R", "0.0.6")), pkg!(("a-sys", "0.0.5")), pkg!(("a-sys", "0.0.11")), pkg!(("c", "0.0.12") => [ dep_req("R", ">= 0.0.3, <= 0.0.4"), ]), pkg!(("c", "0.0.13") => [ dep_req("a-sys", ">= 0.0.8, <= 0.0.11"), ]), pkg!(("c0", "0.0.6") => [ dep_req("L-sys", "<= 0.0.2"), ]), pkg!(("c0", "0.0.10") => [ dep_req("A", ">= 0.0.9, <= 0.0.10"), dep_req("a-sys", "= 0.0.5"), ]), pkg!("j" => [ dep_req("A", ">= 0.0.3, <= 0.0.5"), dep_req("R", ">=0.0.4, <= 0.0.6"), dep_req("c", ">= 0.0.9"), dep_req("c0", ">= 0.0.6"), ]), ]; let reg = registry(input); let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(vec![dep("j")], ®, &mut sat_resolver).is_err()); } #[test] fn conflict_store_more_then_one_match() { let input = vec![ pkg!(("A", "0.0.0")), pkg!(("A", "0.0.1")), pkg!(("A-sys", "0.0.0")), pkg!(("A-sys", "0.0.1")), pkg!(("A-sys", "0.0.2")), pkg!(("A-sys", "0.0.3")), pkg!(("A-sys", "0.0.12")), pkg!(("A-sys", "0.0.16")), pkg!(("B-sys", "0.0.0")), pkg!(("B-sys", "0.0.1")), pkg!(("B-sys", "0.0.2") => [dep_req("A-sys", "= 0.0.12"),]), pkg!(("BA-sys", "0.0.0") => [dep_req("A-sys","= 0.0.16"),]), pkg!(("BA-sys", "0.0.1") => [dep("bad"),]), pkg!(("BA-sys", "0.0.2") => [dep("bad"),]), pkg!("nA" => [ dep("A"), dep_req("A-sys", "<= 0.0.3"), dep("B-sys"), dep("BA-sys"), ]), ]; let reg = registry(input); let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(vec![dep("nA")], ®, &mut sat_resolver).is_err()); } #[test] fn bad_lockfile_from_8249() { let input = vec![ pkg!(("a-sys", "0.2.0")), pkg!(("a-sys", "0.1.0")), pkg!(("b", "0.1.0") => [ dep_req("a-sys", "0.1"), // should be optional: true, but not needed for now ]), pkg!(("c", "1.0.0") => [ dep_req("b", "=0.1.0"), ]), pkg!("foo" => [ dep_req("a-sys", "=0.2.0"), { let mut b = dep_req("b", "=0.1.0"); b.set_features(vec!["a-sys"]); b }, dep_req("c", "=1.0.0"), ]), ]; let reg = registry(input); let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(vec![dep("foo")], ®, &mut sat_resolver).is_err()); } #[test] fn registry_with_features() { let reg = registry(vec![ pkg!("a"), pkg!("b"), pkg_dep_with( "image", vec!["a".opt(), "b".opt(), "jpg".to_dep()], &[("default", &["a"]), ("b", &["dep:b"])], ), pkg!("jpg"), pkg!("log"), pkg!("man"), pkg_dep_with("rgb", vec!["man".opt()], &[("man", &["dep:man"])]), pkg_dep_with( "dep", vec!["image".with(&["b"]), "log".opt(), "rgb".opt()], &[ ("default", &["log", "image/default"]), ("man", &["rgb?/man"]), ], ), ]); for deps in [ vec!["dep".with(&["default", "man", "log", "rgb"])], vec!["dep".with(&["default"])], vec!["dep".with(&[])], vec!["dep".with(&["man"])], vec!["dep".with(&["man", "rgb"])], ] { let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } } #[test] fn missing_feature() { let reg = registry(vec![pkg!("a")]); let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(vec!["a".with(&["f"])], ®, &mut sat_resolver).is_err()); } #[test] fn missing_dep_feature() { let reg = registry(vec![ pkg("a"), pkg_dep_with("dep", vec![dep("a")], &[("f", &["a/a"])]), ]); let deps = vec![dep("dep").with(&["f"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn missing_weak_dep_feature() { let reg = registry(vec![ pkg("a"), pkg_dep_with("dep1", vec![dep("a")], &[("f", &["a/a"])]), pkg_dep_with("dep2", vec!["a".opt()], &[("f", &["a/a"])]), pkg_dep_with("dep3", vec!["a".opt()], &[("f", &["a?/a"])]), pkg_dep_with("dep4", vec!["x".opt()], &[("f", &["x?/a"])]), ]); let deps = vec![dep("dep1").with(&["f"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); let deps = vec![dep("dep2").with(&["f"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); let deps = vec![dep("dep2").with(&["a", "f"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); // Weak dependencies are not supported yet in the dependency resolver let deps = vec![dep("dep3").with(&["f"])]; assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); let deps = vec![dep("dep3").with(&["a", "f"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); // Weak dependencies are not supported yet in the dependency resolver let deps = vec![dep("dep4").with(&["f"])]; assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); let deps = vec![dep("dep4").with(&["x", "f"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn conflict_feature_and_sys() { let reg = registry(vec![ pkg(("a-sys", "1.0.0")), pkg(("a-sys", "2.0.0")), pkg_dep_with( ("a", "1.0.0"), vec![dep_req("a-sys", "1.0.0")], &[("f", &[])], ), pkg_dep_with( ("a", "2.0.0"), vec![dep_req("a-sys", "2.0.0")], &[("g", &[])], ), pkg_dep("dep", vec![dep_req("a", "2.0.0")]), ]); let deps = vec![dep_req("a", "*").with(&["f"]), dep("dep")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn conflict_weak_features() { let reg = registry(vec![ pkg(("a-sys", "1.0.0")), pkg(("a-sys", "2.0.0")), pkg_dep("a1", vec![dep_req("a-sys", "1.0.0").opt()]), pkg_dep("a2", vec![dep_req("a-sys", "2.0.0").opt()]), pkg_dep_with( "dep", vec!["a1".opt(), "a2".opt()], &[("a1", &["a1?/a-sys"]), ("a2", &["a2?/a-sys"])], ), ]); let deps = vec![dep("dep").with(&["a1", "a2"])]; // Weak dependencies are not supported yet in the dependency resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn multiple_dep_kinds_and_targets() { let reg = registry(vec![ pkg(("a-sys", "1.0.0")), pkg(("a-sys", "2.0.0")), pkg_dep_with( "dep1", vec![ dep_req_platform("a-sys", "1.0.0", "cfg(all())").opt(), dep_req("a-sys", "1.0.0").opt(), dep_req_kind("a-sys", "2.0.0", DepKind::Build).opt(), ], &[("default", &["dep:a-sys"])], ), pkg_dep_with( "dep2", vec![ dep_req_platform("a-sys", "1.0.0", "cfg(all())").opt(), dep_req("a-sys", "1.0.0").opt(), dep_req_kind("a-sys", "2.0.0", DepKind::Development).rename("a-sys-dev"), ], &[("default", &["dep:a-sys", "a-sys-dev/bad"])], ), ]); let deps = vec![dep("dep1")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); let deps = vec![dep("dep2")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); let deps = vec![ dep_req("a-sys", "1.0.0"), dep_req_kind("a-sys", "2.0.0", DepKind::Build).rename("a2"), ]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); let deps = vec![ dep_req("a-sys", "1.0.0"), dep_req_kind("a-sys", "2.0.0", DepKind::Development).rename("a2"), ]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn multiple_dep_kinds_and_targets_with_different_packages() { let reg = registry(vec![ pkg_dep_with("a", vec![], &[("f", &[])]), pkg_dep_with("b", vec![], &[("f", &[])]), pkg_dep_with("c", vec![], &[("g", &[])]), pkg_dep_with( "dep1", vec![ "a".opt().rename("x").with(&["f"]), dep_platform("a", "cfg(all())").opt().rename("x"), dep_kind("b", DepKind::Build).opt().rename("x").with(&["f"]), ], &[("default", &["x"])], ), pkg_dep_with( "dep2", vec![ "a".opt().rename("x").with(&["f"]), dep_platform("a", "cfg(all())").opt().rename("x"), dep_kind("c", DepKind::Build).opt().rename("x").with(&["f"]), ], &[("default", &["x"])], ), ]); let deps = vec!["dep1".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); let deps = vec!["dep2".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn dep_feature_with_shadowing_feature() { let reg = registry(vec![ pkg_dep_with("a", vec![], &[("b", &[])]), pkg_dep_with( "dep", vec!["a".opt().rename("aa"), "c".opt()], &[("default", &["aa/b"]), ("aa", &["c"])], ), ]); let deps = vec!["dep".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn dep_feature_not_optional_with_shadowing_feature() { let reg = registry(vec![ pkg_dep_with("a", vec![], &[("b", &[])]), pkg_dep_with( "dep", vec!["a".rename("aa"), "c".opt()], &[("default", &["aa/b"]), ("aa", &["c"])], ), ]); let deps = vec!["dep".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn dep_feature_weak_with_shadowing_feature() { let reg = registry(vec![ pkg_dep_with("a", vec![], &[("b", &[])]), pkg_dep_with( "dep", vec!["a".opt().rename("aa"), "c".opt()], &[("default", &["aa?/b"]), ("aa", &["c"])], ), ]); let deps = vec!["dep".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn dep_feature_duplicate_with_shadowing_feature() { let reg = registry(vec![ pkg_dep_with("a", vec![], &[("b", &[])]), pkg_dep_with( "dep", vec![ "a".opt().rename("aa"), dep_kind("a", DepKind::Build).rename("aa"), "c".opt(), ], &[("default", &["aa/b"]), ("aa", &["c"])], ), ]); let deps = vec!["dep".with(&["default"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); } #[test] fn optional_dep_features() { let reg = registry(vec![ pkg_dep("a", vec!["bad".opt()]), pkg_dep("b", vec!["a".opt().with(&["bad"])]), pkg_dep("dep", vec![dep("a"), dep("b")]), ]); let deps = vec![dep("dep")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn optional_dep_features_with_rename() { let reg = registry(vec![ pkg("x1"), pkg_dep("a", vec!["x1".opt(), "x2".opt(), "x3".opt()]), pkg_dep( "dep1", vec![ "a".opt().with(&["x1"]), dep_kind("a", DepKind::Build).opt().with(&["x2"]), ], ), pkg_dep( "dep2", vec![ "a".opt().with(&["x1"]), "a".opt().rename("a2").with(&["x3"]), ], ), ]); let deps = vec!["dep1".with(&["a"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); let deps = vec!["dep2".with(&["a"])]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } #[test] fn optional_weak_dep_features() { let reg = registry(vec![ pkg_dep("a", vec!["bad".opt()]), pkg_dep("b", vec![dep("a")]), pkg_dep_with("dep", vec!["a".opt(), dep("b")], &[("f", &["a?/bad"])]), ]); let deps = vec!["dep".with(&["f"])]; // Weak dependencies are not supported yet in the dependency resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).sat_resolve(&deps)); } #[test] fn default_feature_multiple_major_versions() { let reg = registry(vec![ pkg_dep_with(("a", "0.2.0"), vec![], &[("default", &[])]), pkg(("a", "0.3.0")), pkg_dep_with(("a", "0.4.0"), vec![], &[("default", &[])]), pkg_dep( "dep1", vec![ dep_req("a", ">=0.2, <0.4").with_default(), dep_req("a", "0.2").rename("a2").with(&[]), ], ), pkg_dep( "dep2", vec![ dep_req("a", ">=0.2, <0.4").with_default(), dep_req("a", "0.3").rename("a2").with(&[]), ], ), pkg_dep( "dep3", vec![ dep_req("a", ">=0.2, <0.4").with_default(), dep_req("a", "0.2").rename("a1").with(&[]), dep_req("a", "0.3").rename("a2").with(&[]), ], ), pkg_dep("dep4", vec![dep_req("a", ">=0.2, <0.4").with_default()]), pkg_dep("dep5", vec![dep_req("a", ">=0.3, <0.5").with_default()]), ]); let deps = vec![dep("dep1")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); let deps = vec![dep("dep2")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); let deps = vec![dep("dep3")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); let deps = vec![dep("dep4")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); let deps = vec![dep("dep5")]; let mut sat_resolver = SatResolver::new(®); assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); } ================================================ FILE: crates/rustfix/CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.4.6] - 2019-07-16 ### Changed Internal changes: - Change example to automatically determine filename - Migrate to Rust 2018 - use `derive` feature over `serde_derive` crate ## [0.4.5] - 2019-03-26 ### Added - Implement common traits for Diagnostic and related types ### Fixed - Fix out of bounds access in parse_snippet ## [0.4.4] - 2018-12-13 ### Added - Make Diagnostic::rendered public. ### Changed - Revert faulty "Allow multiple solutions in a suggestion" ## [0.4.3] - 2018-12-09 - *yanked!* ### Added - Allow multiple solutions in a suggestion ### Changed - use `RUSTC` environment var if present ## [0.4.2] - 2018-07-31 ### Added - Expose an interface to apply fixes on-by-one ### Changed - Handle invalid snippets instead of panicking ## [0.4.1] - 2018-07-26 ### Changed - Ignore duplicate replacements ## [0.4.0] - 2018-05-23 ### Changed - Filter by machine applicability by default [Unreleased]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.6...HEAD [0.4.6]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.5...rustfix-0.4.6 [0.4.5]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.4...rustfix-0.4.5 [0.4.4]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.3...rustfix-0.4.4 [0.4.3]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.2...rustfix-0.4.3 [0.4.2]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.1...rustfix-0.4.2 [0.4.1]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.0...rustfix-0.4.1 [0.4.0]: https://github.com/rust-lang-nursery/rustfix/compare/rustfix-0.4.0 ================================================ FILE: crates/rustfix/Cargo.toml ================================================ [package] name = "rustfix" version = "0.9.5" authors = [ "Pascal Hertleif ", "Oliver Schneider ", ] rust-version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true description = "Automatically apply the suggestions made by rustc" documentation = "https://docs.rs/rustfix" exclude = [ "examples/*", "tests/*", ] [dependencies] serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true tracing.workspace = true [dev-dependencies] anyhow.workspace = true proptest.workspace = true similar.workspace = true tempfile.workspace = true tracing-subscriber.workspace = true snapbox.workspace = true [lints] workspace = true ================================================ FILE: crates/rustfix/README.md ================================================ # rustfix [![Latest Version](https://img.shields.io/crates/v/rustfix.svg)](https://crates.io/crates/rustfix) [![Rust Documentation](https://docs.rs/rustfix/badge.svg)](https://docs.rs/rustfix) Rustfix is a library defining useful structures that represent fix suggestions from rustc. This is a low-level library. You pass it the JSON output from `rustc`, and you can then use it to apply suggestions to in-memory strings. This library doesn't execute commands, or read or write from the filesystem. If you are looking for the [`cargo fix`] implementation, the core of it is located in [`cargo::ops::fix`]. > This crate is maintained by the Cargo team, primarily for use by Cargo and Rust compiler test suite > and not intended for external use (except as a transitive dependency). This > crate may make major changes to its APIs or be deprecated without warning. [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html [`cargo::ops::fix`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/fix.rs ## License Licensed under either of - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ================================================ FILE: crates/rustfix/examples/fix-json.rs ================================================ #![allow(clippy::print_stderr)] use std::io::{BufReader, Read, stdin}; use std::{collections::HashMap, collections::HashSet, env, fs}; use anyhow::Error; fn main() -> Result<(), Error> { let suggestions_file = env::args().nth(1).expect("USAGE: fix-json "); let suggestions = if suggestions_file == "--" { let mut buffer = String::new(); BufReader::new(stdin()).read_to_string(&mut buffer)?; buffer } else { fs::read_to_string(&suggestions_file)? }; let suggestions = rustfix::get_suggestions_from_json( &suggestions, &HashSet::new(), rustfix::Filter::Everything, )?; let mut files = HashMap::new(); for suggestion in suggestions { let file = suggestion.solutions[0].replacements[0] .snippet .file_name .clone(); files.entry(file).or_insert_with(Vec::new).push(suggestion); } for (source_file, suggestions) in &files { let source = fs::read_to_string(source_file)?; let mut fix = rustfix::CodeFix::new(&source); for suggestion in suggestions.iter().rev() { if let Err(e) = fix.apply(suggestion) { eprintln!("Failed to apply suggestion to {}: {}", source_file, e); } } let fixes = fix.finish()?; fs::write(source_file, fixes)?; } Ok(()) } ================================================ FILE: crates/rustfix/proptest-regressions/replace.txt ================================================ # Seeds for failure cases proptest has generated in the past. It is # automatically read and these particular cases re-run before any # novel cases are generated. # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. xs 358148376 3634975642 2528447681 3675516813 # shrinks to ref s = "" xs 3127423015 3362740891 2605681441 2390162043 # shrinks to ref data = "", ref replacements = [(0..0, [])] ================================================ FILE: crates/rustfix/src/diagnostics.rs ================================================ //! Rustc Diagnostic JSON Output. //! //! The following data types are copied from [rust-lang/rust](https://github.com/rust-lang/rust/blob/4fd68eb47bad1c121417ac4450b2f0456150db86/compiler/rustc_errors/src/json.rs). //! //! For examples of the JSON output, see JSON fixture files under `tests/` directory. use serde::Deserialize; /// The root diagnostic JSON output emitted by the compiler. #[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)] pub struct Diagnostic { /// The primary error message. pub message: String, pub code: Option, /// "error: internal compiler error", "error", "warning", "note", "help". level: String, pub spans: Vec, /// Associated diagnostic messages. pub children: Vec, /// The message as rustc would render it. pub rendered: Option, } /// Span information of a diagnostic item. #[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)] pub struct DiagnosticSpan { pub file_name: String, pub byte_start: u32, pub byte_end: u32, /// 1-based. pub line_start: usize, pub line_end: usize, /// 1-based, character offset. pub column_start: usize, pub column_end: usize, /// Is this a "primary" span -- meaning the point, or one of the points, /// where the error occurred? pub is_primary: bool, /// Source text from the start of `line_start` to the end of `line_end`. pub text: Vec, /// Label that should be placed at this location (if any) label: Option, /// If we are suggesting a replacement, this will contain text /// that should be sliced in atop this span. pub suggested_replacement: Option, /// If the suggestion is approximate pub suggestion_applicability: Option, /// Macro invocations that created the code at this span, if any. expansion: Option>, } /// Indicates the confidence in the correctness of a suggestion. /// /// All suggestions are marked with an `Applicability`. Tools use the applicability of a suggestion /// to determine whether it should be automatically applied or if the user should be consulted /// before applying the suggestion. #[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)] pub enum Applicability { /// The suggestion is definitely what the user intended, or maintains the exact meaning of the code. /// This suggestion should be automatically applied. /// /// In case of multiple `MachineApplicable` suggestions (whether as part of /// the same `multipart_suggestion` or not), all of them should be /// automatically applied. MachineApplicable, /// The suggestion may be what the user intended, but it is uncertain. The suggestion should /// result in valid Rust code if it is applied. MaybeIncorrect, /// The suggestion contains placeholders like `(...)` or `{ /* fields */ }`. The suggestion /// cannot be applied automatically because it will not result in valid Rust code. The user /// will need to fill in the placeholders. HasPlaceholders, /// The applicability of the suggestion is unknown. Unspecified, } /// Span information of a single line. #[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)] pub struct DiagnosticSpanLine { pub text: String, /// 1-based, character offset in self.text. pub highlight_start: usize, pub highlight_end: usize, } /// Span information for macro expansions. #[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)] struct DiagnosticSpanMacroExpansion { /// span where macro was applied to generate this code; note that /// this may itself derive from a macro (if /// `span.expansion.is_some()`) span: DiagnosticSpan, /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") macro_decl_name: String, /// span where macro was defined (if known) def_site_span: Option, } /// The error code emitted by the compiler. See [Rust error codes index]. /// /// [Rust error codes index]: https://doc.rust-lang.org/error_codes/error-index.html #[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)] pub struct DiagnosticCode { /// The code itself. pub code: String, /// An explanation for the code. explanation: Option, } ================================================ FILE: crates/rustfix/src/error.rs ================================================ //! Error types. use std::ops::Range; #[non_exhaustive] #[derive(Debug, thiserror::Error)] pub enum Error { #[error("invalid range {0:?}, start is larger than end")] InvalidRange(Range), #[error("invalid range {0:?}, original data is only {1} byte long")] DataLengthExceeded(Range, usize), #[non_exhaustive] #[error("cannot replace slice of data that was already replaced")] AlreadyReplaced { /// The location of the intended replacement. range: Range, /// Whether the modification exactly matches (both range and data) the one it conflicts with. /// Some clients may wish to simply ignore this condition. is_identical: bool, }, #[error(transparent)] Utf8(#[from] std::string::FromUtf8Error), } ================================================ FILE: crates/rustfix/src/lib.rs ================================================ //! Library for applying diagnostic suggestions to source code. //! //! This is a low-level library. You pass it the [JSON output] from `rustc`, //! and you can then use it to apply suggestions to in-memory strings. //! This library doesn't execute commands, or read or write from the filesystem. //! //! If you are looking for the [`cargo fix`] implementation, the core of it is //! located in [`cargo::ops::fix`]. //! //! [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html //! [`cargo::ops::fix`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/fix.rs //! [JSON output]: diagnostics //! //! The general outline of how to use this library is: //! //! 1. Call `rustc` and collect the JSON data. //! 2. Pass the json data to [`get_suggestions_from_json`]. //! 3. Create a [`CodeFix`] with the source of a file to modify. //! 4. Call [`CodeFix::apply`] to apply a change. //! 5. Call [`CodeFix::finish`] to get the result and write it back to disk. //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo and Rust compiler test suite //! > and not intended for external use (except as a transitive dependency). This //! > crate may make major changes to its APIs or be deprecated without warning. use std::collections::HashSet; use std::ops::Range; pub mod diagnostics; mod error; mod replace; use diagnostics::Diagnostic; use diagnostics::DiagnosticSpan; pub use error::Error; /// A filter to control which suggestion should be applied. #[derive(Debug, Clone, Copy)] pub enum Filter { /// For [`diagnostics::Applicability::MachineApplicable`] only. MachineApplicableOnly, /// Everything is included. YOLO! Everything, } /// Collects code [`Suggestion`]s from one or more compiler diagnostic lines. /// /// Fails if any of diagnostic line `input` is not a valid [`Diagnostic`] JSON. /// /// * `only` --- only diagnostics with code in a set of error codes would be collected. pub fn get_suggestions_from_json( input: &str, only: &HashSet, filter: Filter, ) -> serde_json::error::Result> { let mut result = Vec::new(); for cargo_msg in serde_json::Deserializer::from_str(input).into_iter::() { // One diagnostic line might have multiple suggestions result.extend(collect_suggestions(&cargo_msg?, only, filter)); } Ok(result) } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct LinePosition { pub line: usize, pub column: usize, } impl std::fmt::Display for LinePosition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}", self.line, self.column) } } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct LineRange { pub start: LinePosition, pub end: LinePosition, } impl std::fmt::Display for LineRange { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}-{}", self.start, self.end) } } /// An error/warning and possible solutions for fixing it #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Suggestion { pub message: String, pub snippets: Vec, pub solutions: Vec, } /// Solution to a diagnostic item. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Solution { /// The error message of the diagnostic item. pub message: String, /// Possible solutions to fix the error. pub replacements: Vec, } /// Represents code that will get replaced. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Snippet { pub file_name: String, pub line_range: LineRange, pub range: Range, } /// Represents a replacement of a `snippet`. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Replacement { /// Code snippet that gets replaced. pub snippet: Snippet, /// The replacement of the snippet. pub replacement: String, } /// Converts a [`DiagnosticSpan`] to a [`Snippet`]. fn span_to_snippet(span: &DiagnosticSpan) -> Snippet { Snippet { file_name: span.file_name.clone(), line_range: LineRange { start: LinePosition { line: span.line_start, column: span.column_start, }, end: LinePosition { line: span.line_end, column: span.column_end, }, }, range: (span.byte_start as usize)..(span.byte_end as usize), } } /// Converts a [`DiagnosticSpan`] into a [`Replacement`]. fn collect_span(span: &DiagnosticSpan) -> Option { let snippet = span_to_snippet(span); let replacement = span.suggested_replacement.clone()?; Some(Replacement { snippet, replacement, }) } /// Collects code [`Suggestion`]s from a single compiler diagnostic line. /// /// * `only` --- only diagnostics with code in a set of error codes would be collected. pub fn collect_suggestions( diagnostic: &Diagnostic, only: &HashSet, filter: Filter, ) -> Option { if !only.is_empty() { if let Some(ref code) = diagnostic.code { if !only.contains(&code.code) { // This is not the code we are looking for return None; } } else { // No code, probably a weird builtin warning/error return None; } } let solutions: Vec<_> = diagnostic .children .iter() .filter_map(|child| { let replacements: Vec<_> = child .spans .iter() .filter(|span| { use crate::Filter::*; use crate::diagnostics::Applicability::*; match (filter, &span.suggestion_applicability) { (MachineApplicableOnly, Some(MachineApplicable)) => true, (MachineApplicableOnly, _) => false, (Everything, _) => true, } }) .filter_map(collect_span) .collect(); if !replacements.is_empty() { Some(Solution { message: child.message.clone(), replacements, }) } else { None } }) .collect(); if solutions.is_empty() { None } else { Some(Suggestion { message: diagnostic.message.clone(), snippets: diagnostic.spans.iter().map(span_to_snippet).collect(), solutions, }) } } /// Represents a code fix. This doesn't write to disks but is only in memory. /// /// The general way to use this is: /// /// 1. Feeds the source of a file to [`CodeFix::new`]. /// 2. Calls [`CodeFix::apply`] to apply suggestions to the source code. /// 3. Calls [`CodeFix::finish`] to get the "fixed" code. #[derive(Clone)] pub struct CodeFix { data: replace::Data, /// Whether or not the data has been modified. modified: bool, } impl CodeFix { /// Creates a `CodeFix` with the source of a file to modify. pub fn new(s: &str) -> CodeFix { CodeFix { data: replace::Data::new(s.as_bytes()), modified: false, } } /// Applies a suggestion to the code. pub fn apply(&mut self, suggestion: &Suggestion) -> Result<(), Error> { for solution in &suggestion.solutions { for r in &solution.replacements { self.data .replace_range(r.snippet.range.clone(), r.replacement.as_bytes()) .inspect_err(|_| self.data.restore())?; } } self.data.commit(); self.modified = true; Ok(()) } /// Applies an individual solution from a [`Suggestion`]. pub fn apply_solution(&mut self, solution: &Solution) -> Result<(), Error> { for r in &solution.replacements { self.data .replace_range(r.snippet.range.clone(), r.replacement.as_bytes()) .inspect_err(|_| self.data.restore())?; } self.data.commit(); self.modified = true; Ok(()) } /// Gets the result of the "fixed" code. pub fn finish(&self) -> Result { Ok(String::from_utf8(self.data.to_vec())?) } /// Returns whether or not the data has been modified. pub fn modified(&self) -> bool { self.modified } } /// Applies multiple `suggestions` to the given `code`, handling certain conflicts automatically. /// /// If a replacement in a suggestion exactly matches a replacement of a previously applied solution, /// that entire suggestion will be skipped without generating an error. /// This is currently done to alleviate issues like rust-lang/rust#51211, /// although it may be removed if that's fixed deeper in the compiler. /// /// The intent of this design is that the overall application process /// should repeatedly apply non-conflicting suggestions then rëevaluate the result, /// looping until either there are no more suggestions to apply or some budget is exhausted. pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result { let mut fix = CodeFix::new(code); for suggestion in suggestions.iter().rev() { fix.apply(suggestion).or_else(|err| match err { Error::AlreadyReplaced { is_identical: true, .. } => Ok(()), _ => Err(err), })?; } fix.finish() } ================================================ FILE: crates/rustfix/src/replace.rs ================================================ //! A small module giving you a simple container that allows //! easy and cheap replacement of parts of its content, //! with the ability to commit or rollback pending changes. //! //! Create a new [`Data`] struct with some initial set of code, //! then record changes with [`Data::replace_range`], //! which will validate that the changes do not conflict with one another. //! At any time, you can "checkpoint" the current changes with [`Data::commit`] //! or roll them back (perhaps due to a conflict) with [`Data::restore`]. //! When you're done, use [`Data::to_vec`] //! to merge the original data with the changes. //! //! # Notes //! //! The [`Data::to_vec`] method includes uncommitted changes, if present. //! The reason for including uncommitted changes is that typically, once you're calling those, //! you're done with edits and will be dropping the [`Data`] struct in a moment. //! In this case, requiring an extra call to `commit` would be unnecessary work. //! Of course, there's no harm in calling `commit`---it's just not strictly necessary. //! //! Put another way, the main point of `commit` is to checkpoint a set of known-good changes //! before applying additional sets of as-of-yet unvalidated changes. //! If no future changes are expected, you aren't _required_ to pay the cost of `commit`. //! If you want to discard uncommitted changes, simply call [`Data::restore`] first. use std::ops::Range; use std::rc::Rc; use crate::error::Error; /// Data that should replace a particular range of the original. #[derive(Clone)] struct Span { /// Span of the parent data to be replaced, inclusive of the start, exclusive of the end. range: Range, /// New data to insert at the `start` position of the `original` data. data: Rc<[u8]>, /// Whether this data is committed or provisional. committed: bool, } impl std::fmt::Debug for Span { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let state = if self.is_insert() { "inserted" } else { "replaced" }; let committed = if self.committed { "committed" } else { "uncommitted" }; write!( f, "({}, {}: {state}, {committed})", self.range.start, self.range.end ) } } impl Span { fn new(range: Range, data: &[u8]) -> Self { Self { range, data: data.into(), committed: false, } } /// Returns `true` if and only if this is a "pure" insertion, /// i.e. does not remove any existing data. /// /// The insertion point is the `start` position of the range. fn is_insert(&self) -> bool { self.range.start == self.range.end } } impl PartialEq for Span { /// Returns `true` if and only if this `Span` and `other` have the same range and data, /// regardless of `committed` status. fn eq(&self, other: &Self) -> bool { self.range == other.range && self.data == other.data } } /// A container that allows easily replacing chunks of its data. #[derive(Debug, Clone, Default)] pub struct Data { /// Original data. original: Vec, /// [`Span`]s covering the full range of the original data. /// Important: it's expected that the underlying implementation maintains this in order, /// sorted ascending by start position. parts: Vec, } impl Data { /// Create a new data container from a slice of bytes pub fn new(data: &[u8]) -> Self { Data { original: data.into(), parts: vec![], } } /// Commit the current changes. pub fn commit(&mut self) { self.parts.iter_mut().for_each(|span| span.committed = true); } /// Discard uncommitted changes. pub fn restore(&mut self) { self.parts.retain(|parts| parts.committed); } /// Merge the original data with changes, **including** uncommitted changes. /// /// See the module-level documentation for more information on why uncommitted changes are included. pub fn to_vec(&self) -> Vec { let mut prev_end = 0; let mut s = self.parts.iter().fold(Vec::new(), |mut acc, span| { // Hedge against potential implementation errors. debug_assert!( prev_end <= span.range.start, "expected parts in sorted order" ); acc.extend_from_slice(&self.original[prev_end..span.range.start]); acc.extend_from_slice(&span.data); prev_end = span.range.end; acc }); // Append remaining data, if any. s.extend_from_slice(&self.original[prev_end..]); s } /// Record a provisional change. /// /// If committed, the original data in the given `range` will be replaced by the given data. /// If there already exist changes for data in the given range (committed or not), /// this method will return an error. /// It will also return an error if the beginning of the range comes before its end, /// or if the range is outside that of the original data. pub fn replace_range(&mut self, range: Range, data: &[u8]) -> Result<(), Error> { if range.start > range.end { return Err(Error::InvalidRange(range)); } if range.end > self.original.len() { return Err(Error::DataLengthExceeded(range, self.original.len())); } // Keep sorted by start position, or by end position if the start position is the same, // which has the effect of keeping a pure insertion ahead of a replacement. // That limits the kinds of conflicts that can happen, simplifying the checks below. let ins_point = self.parts.partition_point(|span| { span.range.start < range.start || (span.range.start == range.start && span.range.end < range.end) }); let incoming = Span::new(range, data.as_ref()); // Reject if the change starts before the previous one ends. if let Some(before) = ins_point.checked_sub(1).and_then(|i| self.parts.get(i)) { if incoming.range.start < before.range.end { return Err(Error::AlreadyReplaced { is_identical: incoming == *before, range: incoming.range, }); } } // Reject if the change ends after the next one starts, // or if this is an insert and there's already an insert there. if let Some(after) = self.parts.get(ins_point) { if incoming.range.end > after.range.start || incoming.range == after.range { return Err(Error::AlreadyReplaced { is_identical: incoming == *after, range: incoming.range, }); } } self.parts.insert(ins_point, incoming); Ok(()) } } #[cfg(test)] mod tests { use super::*; use proptest::prelude::*; fn str(i: &[u8]) -> &str { ::std::str::from_utf8(i).unwrap() } #[test] fn insert_at_beginning() { let mut d = Data::new(b"foo bar baz"); d.replace_range(0..0, b"oh no ").unwrap(); assert_eq!("oh no foo bar baz", str(&d.to_vec())); } #[test] fn insert_at_end() { let mut d = Data::new(b"foo bar baz"); d.replace_range(11..11, b" oh no").unwrap(); assert_eq!("foo bar baz oh no", str(&d.to_vec())); } #[test] fn replace_some_stuff() { let mut d = Data::new(b"foo bar baz"); d.replace_range(4..7, b"lol").unwrap(); assert_eq!("foo lol baz", str(&d.to_vec())); } #[test] fn replace_a_single_char() { let mut d = Data::new(b"let y = true;"); d.replace_range(4..5, b"mut y").unwrap(); assert_eq!("let mut y = true;", str(&d.to_vec())); } #[test] fn replace_multiple_lines() { let mut d = Data::new(b"lorem\nipsum\ndolor"); d.replace_range(6..11, b"lol").unwrap(); assert_eq!("lorem\nlol\ndolor", str(&d.to_vec())); d.replace_range(12..17, b"lol").unwrap(); assert_eq!("lorem\nlol\nlol", str(&d.to_vec())); } #[test] fn replace_multiple_lines_with_insert_only() { let mut d = Data::new(b"foo!"); d.replace_range(3..3, b"bar").unwrap(); assert_eq!("foobar!", str(&d.to_vec())); d.replace_range(0..3, b"baz").unwrap(); assert_eq!("bazbar!", str(&d.to_vec())); d.replace_range(3..4, b"?").unwrap(); assert_eq!("bazbar?", str(&d.to_vec())); } #[test] #[allow(clippy::reversed_empty_ranges)] fn replace_invalid_range() { let mut d = Data::new(b"foo!"); assert!(d.replace_range(2..1, b"bar").is_err()); assert!(d.replace_range(0..3, b"bar").is_ok()); } #[test] fn empty_to_vec_roundtrip() { let s = ""; assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice()); } #[test] fn replace_same_range_diff_data() { let mut d = Data::new(b"foo bar baz"); d.replace_range(4..7, b"lol").unwrap(); assert_eq!("foo lol baz", str(&d.to_vec())); assert!(matches!( d.replace_range(4..7, b"lol2").unwrap_err(), Error::AlreadyReplaced { is_identical: false, .. }, )); } #[test] fn replace_same_range_same_data() { let mut d = Data::new(b"foo bar baz"); d.replace_range(4..7, b"lol").unwrap(); assert_eq!("foo lol baz", str(&d.to_vec())); assert!(matches!( d.replace_range(4..7, b"lol").unwrap_err(), Error::AlreadyReplaced { is_identical: true, .. }, )); } #[test] fn broken_replacements() { let mut d = Data::new(b"foo"); assert!(matches!( d.replace_range(4..8, b"lol").unwrap_err(), Error::DataLengthExceeded(std::ops::Range { start: 4, end: 8 }, 3), )); } #[test] fn insert_same_twice() { let mut d = Data::new(b"foo"); d.replace_range(1..1, b"b").unwrap(); assert_eq!("fboo", str(&d.to_vec())); assert!(matches!( d.replace_range(1..1, b"b").unwrap_err(), Error::AlreadyReplaced { is_identical: true, .. }, )); assert_eq!("fboo", str(&d.to_vec())); } #[test] fn commit_restore() { let mut d = Data::new(b", "); assert_eq!(", ", str(&d.to_vec())); d.replace_range(2..2, b"world").unwrap(); d.replace_range(0..0, b"hello").unwrap(); assert_eq!("hello, world", str(&d.to_vec())); d.restore(); assert_eq!(", ", str(&d.to_vec())); d.commit(); assert_eq!(", ", str(&d.to_vec())); d.replace_range(2..2, b"world").unwrap(); assert_eq!(", world", str(&d.to_vec())); d.commit(); assert_eq!(", world", str(&d.to_vec())); d.restore(); assert_eq!(", world", str(&d.to_vec())); d.replace_range(0..0, b"hello").unwrap(); assert_eq!("hello, world", str(&d.to_vec())); d.commit(); assert_eq!("hello, world", str(&d.to_vec())); d.restore(); assert_eq!("hello, world", str(&d.to_vec())); } proptest! { #[test] fn new_to_vec_roundtrip(ref s in "\\PC*") { assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice()); } #[test] fn replace_random_chunks( ref data in "\\PC*", ref replacements in prop::collection::vec( (any::<::std::ops::Range>(), any::>()), 1..100, ) ) { let mut d = Data::new(data.as_bytes()); for &(ref range, ref bytes) in replacements { let _ = d.replace_range(range.clone(), bytes); } } } } ================================================ FILE: crates/rustfix/tests/edge-cases/empty.json ================================================ { "message": "`main` function not found in crate `empty`", "code": { "code": "E0601", "explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n" }, "level": "error", "spans": [ { "file_name": "empty.rs", "byte_start": 0, "byte_end": 0, "line_start": 0, "line_end": 0, "column_start": 1, "column_end": 1, "is_primary": true, "text": [ { "text": "", "highlight_start": 1, "highlight_end": 1 } ], "label": null, "suggested_replacement": null, "suggestion_applicability": null, "expansion": null } ], "children": [ { "message": "consider adding a `main` function to `empty.rs`", "code": null, "level": "note", "spans": [], "children": [], "rendered": null } ], "rendered": "error[E0601]: `main` function not found in crate `empty`\n |\n = note: consider adding a `main` function to `empty.rs`\n\n" } ================================================ FILE: crates/rustfix/tests/edge-cases/empty.rs ================================================ ================================================ FILE: crates/rustfix/tests/edge-cases/indented_whitespace.json ================================================ { "message": "non-ASCII whitespace symbol '\\u{a0}' is not skipped", "code": null, "level": "warning", "spans": [ { "file_name": "lib.rs", "byte_start": 26, "byte_end": 28, "line_start": 2, "line_end": 2, "column_start": 1, "column_end": 2, "is_primary": false, "text": [ { "text": " indented\";", "highlight_start": 1, "highlight_end": 2 } ], "label": "non-ASCII whitespace symbol '\\u{a0}' is not skipped", "suggested_replacement": null, "suggestion_applicability": null, "expansion": null }, { "file_name": "lib.rs", "byte_start": 24, "byte_end": 28, "line_start": 1, "line_end": 2, "column_start": 25, "column_end": 2, "is_primary": true, "text": [ { "text": "pub static FOO: &str = \"\\", "highlight_start": 25, "highlight_end": 26 }, { "text": " indented\";", "highlight_start": 1, "highlight_end": 2 } ], "label": null, "suggested_replacement": null, "suggestion_applicability": null, "expansion": null } ], "children": [], "rendered": "warning: non-ASCII whitespace symbol '\\u{a0}' is not skipped\n --> lib.rs:1:25\n |\n1 | pub static FOO: &str = \"\\\n | _________________________^\n2 | |  indented\";\n | | ^ non-ASCII whitespace symbol '\\u{a0}' is not skipped\n | |_|\n | \n\n" } ================================================ FILE: crates/rustfix/tests/edge-cases/no_main.json ================================================ { "message": "`main` function not found in crate `no_main`", "code": { "code": "E0601", "explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n" }, "level": "error", "spans": [ { "file_name": "no_main.rs", "byte_start": 26, "byte_end": 26, "line_start": 1, "line_end": 1, "column_start": 27, "column_end": 27, "is_primary": true, "text": [ { "text": "// This file has no main.", "highlight_start": 27, "highlight_end": 27 } ], "label": "consider adding a `main` function to `no_main.rs`", "suggested_replacement": null, "suggestion_applicability": null, "expansion": null } ], "children": [], "rendered": "error[E0601]: `main` function not found in crate `no_main`\n --> no_main.rs:1:27\n |\n1 | // This file has no main.\n | ^ consider adding a `main` function to `no_main.rs`\n\n" } ================================================ FILE: crates/rustfix/tests/edge-cases/no_main.rs ================================================ // This file has no main. ================================================ FILE: crates/rustfix/tests/edge-cases/out_of_bounds.recorded.json ================================================ { "message": "unterminated double quote string", "code": null, "level": "error", "spans": [ { "file_name": "./tests/everything/tab_2.rs", "byte_start": 485, "byte_end": 526, "line_start": 12, "line_end": 13, "column_start": 7, "column_end": 3, "is_primary": true, "text": [ { "text": " \"\"\"; //~ ERROR unterminated double quote", "highlight_start": 7, "highlight_end": 45 }, { "text": "}", "highlight_start": 1, "highlight_end": 3 } ], "label": null, "suggested_replacement": null, "suggestion_applicability": null, "expansion": null } ], "children": [], "rendered": "error: unterminated double quote string\n --> ./tests/everything/tab_2.rs:12:7\n |\n12 | \"\"\"; //~ ERROR unterminated double quote\n | _______^\n13 | | }\n | |__^\n\n" } { "message": "aborting due to previous error", "code": null, "level": "error", "spans": [], "children": [], "rendered": "error: aborting due to previous error\n\n" } ================================================ FILE: crates/rustfix/tests/edge-cases/utf8_idents.recorded.json ================================================ { "message": "expected one of `,`, `:`, `=`, or `>`, found `'β`", "code": null, "level": "error", "spans": [ { "file_name": "./tests/everything/utf8_idents.rs", "byte_start": 14, "byte_end": 14, "line_start": 2, "line_end": 2, "column_start": 6, "column_end": 6, "is_primary": false, "text": [ { "text": " γ //~ ERROR non-ascii idents are not fully supported", "highlight_start": 6, "highlight_end": 6 } ], "label": "expected one of `,`, `:`, `=`, or `>` here", "suggested_replacement": null, "suggestion_applicability": null, "expansion": null }, { "file_name": "./tests/everything/utf8_idents.rs", "byte_start": 145, "byte_end": 148, "line_start": 4, "line_end": 4, "column_start": 5, "column_end": 7, "is_primary": true, "text": [ { "text": " 'β, //~ ERROR non-ascii idents are not fully supported", "highlight_start": 5, "highlight_end": 7 } ], "label": "unexpected token", "suggested_replacement": null, "suggestion_applicability": null, "expansion": null } ], "children": [], "rendered": "error: expected one of `,`, `:`, `=`, or `>`, found `'β`\n --> ./tests/everything/utf8_idents.rs:4:5\n |\n2 | γ //~ ERROR non-ascii idents are not fully supported\n | - expected one of `,`, `:`, `=`, or `>` here\n3 | //~^ WARN type parameter `γ` should have an upper camel case name\n4 | 'β, //~ ERROR non-ascii idents are not fully supported\n | ^^ unexpected token\n\n" } { "message": "aborting due to previous error", "code": null, "level": "error", "spans": [], "children": [], "rendered": "error: aborting due to previous error\n\n" } ================================================ FILE: crates/rustfix/tests/edge_cases.rs ================================================ use std::collections::HashSet; use std::fs; macro_rules! expect_empty_json_test { ($name:ident, $file:expr) => { #[test] fn $name() { let json = fs::read_to_string(concat!("./tests/edge-cases/", $file)).unwrap(); let expected_suggestions = rustfix::get_suggestions_from_json( &json, &HashSet::new(), rustfix::Filter::Everything, ) .unwrap(); assert!(expected_suggestions.is_empty()); } }; } expect_empty_json_test! {out_of_bounds_test, "out_of_bounds.recorded.json"} expect_empty_json_test! {utf8_identifiers_test, "utf8_idents.recorded.json"} expect_empty_json_test! {empty, "empty.json"} expect_empty_json_test! {no_main, "no_main.json"} expect_empty_json_test! {indented_whitespace, "indented_whitespace.json"} ================================================ FILE: crates/rustfix/tests/everything/.gitignore ================================================ *.recorded.json *.recorded.rs ================================================ FILE: crates/rustfix/tests/everything/E0178.fixed.rs ================================================ #![allow(dead_code)] trait Foo {} struct Bar<'a> { w: &'a (dyn Foo + Send), } fn main() { } ================================================ FILE: crates/rustfix/tests/everything/E0178.json ================================================ { "message": "expected a path on the left-hand side of `+`, not `&'a Foo`", "code": { "code": "E0178", "explanation": "\nIn types, the `+` type operator has low precedence, so it is often necessary\nto use parentheses.\n\nFor example:\n\n```compile_fail,E0178\ntrait Foo {}\n\nstruct Bar<'a> {\n w: &'a Foo + Copy, // error, use &'a (Foo + Copy)\n x: &'a Foo + 'a, // error, use &'a (Foo + 'a)\n y: &'a mut Foo + 'a, // error, use &'a mut (Foo + 'a)\n z: fn() -> Foo + 'a, // error, use fn() -> (Foo + 'a)\n}\n```\n\nMore details can be found in [RFC 438].\n\n[RFC 438]: https://github.com/rust-lang/rfcs/pull/438\n" }, "level": "error", "spans": [ { "file_name": "./tests/everything/E0178.rs", "byte_start": 60, "byte_end": 74, "line_start": 6, "line_end": 6, "column_start": 8, "column_end": 22, "is_primary": true, "text": [ { "text": " w: &'a Foo + Send,", "highlight_start": 8, "highlight_end": 22 } ], "label": null, "suggested_replacement": null, "expansion": null } ], "children": [ { "message": "try adding parentheses", "code": null, "level": "help", "spans": [ { "file_name": "./tests/everything/E0178.rs", "byte_start": 60, "byte_end": 74, "line_start": 6, "line_end": 6, "column_start": 8, "column_end": 22, "is_primary": true, "text": [ { "text": " w: &'a Foo + Send,", "highlight_start": 8, "highlight_end": 22 } ], "label": null, "suggested_replacement": "&'a (Foo + Send)", "expansion": null } ], "children": [], "rendered": null } ], "rendered": "error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`\n --> ./tests/everything/E0178.rs:6:8\n |\n6 | w: &'a Foo + Send,\n | ^^^^^^^^^^^^^^ help: try adding parentheses: `&'a (Foo + Send)`\n\nIf you want more information on this error, try using \"rustc --explain E0178\"\n" } { "message": "aborting due to previous error", "code": null, "level": "error", "spans": [], "children": [], "rendered": "error: aborting due to previous error\n\n" } ================================================ FILE: crates/rustfix/tests/everything/E0178.rs ================================================ #![allow(dead_code)] trait Foo {} struct Bar<'a> { w: &'a dyn Foo + Send, } fn main() { } ================================================ FILE: crates/rustfix/tests/everything/closure-immutable-outer-variable.fixed.rs ================================================ // Point at the captured immutable outer variable // Suppress unrelated warnings #![allow(unused)] fn foo(mut f: Box) { f(); } fn main() { let mut y = true; foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable } ================================================ FILE: crates/rustfix/tests/everything/closure-immutable-outer-variable.json ================================================ { "$message_type": "diagnostic", "children": [ { "children": [], "code": null, "level": "help", "message": "consider changing this to be mutable", "rendered": null, "spans": [ { "byte_end": 167, "byte_start": 167, "column_end": 9, "column_start": 9, "expansion": null, "file_name": "./tests/everything/closure-immutable-outer-variable.rs", "is_primary": true, "label": null, "line_end": 11, "line_start": 11, "suggested_replacement": "mut ", "suggestion_applicability": "MachineApplicable", "text": [ { "highlight_end": 9, "highlight_start": 9, "text": " let y = true;" } ] } ] } ], "code": { "code": "E0594", "explanation": "A non-mutable value was assigned a value./n/nErroneous code example:/n/n```compile_fail,E0594/nstruct SolarSystem {/n earth: i32,/n}/n/nlet ss = SolarSystem { earth: 3 };/nss.earth = 2; // error!/n```/n/nTo fix this error, declare `ss` as mutable by using the `mut` keyword:/n/n```/nstruct SolarSystem {/n earth: i32,/n}/n/nlet mut ss = SolarSystem { earth: 3 }; // declaring `ss` as mutable/nss.earth = 2; // ok!/n```/n" }, "level": "error", "message": "cannot assign to `y`, as it is not declared as mutable", "rendered": "error[E0594]: cannot assign to `y`, as it is not declared as mutable/n --> ./tests/everything/closure-immutable-outer-variable.rs:12:26/n |/n12 | foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable/n | ^^^^^^^^^ cannot assign/n |/nhelp: consider changing this to be mutable/n |/n11 | let mut y = true;/n | +++/n/n", "spans": [ { "byte_end": 211, "byte_start": 202, "column_end": 35, "column_start": 26, "expansion": null, "file_name": "./tests/everything/closure-immutable-outer-variable.rs", "is_primary": true, "label": "cannot assign", "line_end": 12, "line_start": 12, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 35, "highlight_start": 26, "text": " foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable" } ] } ] } { "$message_type": "diagnostic", "children": [], "code": null, "level": "error", "message": "aborting due to 1 previous error", "rendered": "error: aborting due to 1 previous error/n/n", "spans": [] } { "$message_type": "diagnostic", "children": [], "code": null, "level": "failure-note", "message": "For more information about this error, try `rustc --explain E0594`.", "rendered": "For more information about this error, try `rustc --explain E0594`./n", "spans": [] } ================================================ FILE: crates/rustfix/tests/everything/closure-immutable-outer-variable.rs ================================================ // Point at the captured immutable outer variable // Suppress unrelated warnings #![allow(unused)] fn foo(mut f: Box) { f(); } fn main() { let y = true; foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable } ================================================ FILE: crates/rustfix/tests/everything/dedup-suggestions.fixed.rs ================================================ // See macro_rules! foo { () => { let x = Box::new(1); let _ = &x; }; } fn main() { foo!(); foo!(); } ================================================ FILE: crates/rustfix/tests/everything/dedup-suggestions.json ================================================ { "$message_type": "diagnostic", "children": [ { "children": [], "code": null, "level": "note", "message": "`#[warn(forgetting_references)]` on by default", "rendered": null, "spans": [] }, { "children": [], "code": null, "level": "help", "message": "use `let _ = ...` to ignore the expression or result", "rendered": null, "spans": [ { "byte_end": 142, "byte_start": 125, "column_end": 26, "column_start": 9, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 179, "byte_start": 173, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 10, "line_start": 10, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": true, "label": null, "line_end": 5, "line_start": 5, "suggested_replacement": "let _ = ", "suggestion_applicability": "MaybeIncorrect", "text": [ { "highlight_end": 26, "highlight_start": 9, "text": " std::mem::forget(&x);" } ] }, { "byte_end": 145, "byte_start": 144, "column_end": 29, "column_start": 28, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 179, "byte_start": 173, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 10, "line_start": 10, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": true, "label": null, "line_end": 5, "line_start": 5, "suggested_replacement": "", "suggestion_applicability": "MaybeIncorrect", "text": [ { "highlight_end": 29, "highlight_start": 28, "text": " std::mem::forget(&x);" } ] } ] } ], "code": { "code": "forgetting_references", "explanation": null }, "level": "warning", "message": "calls to `std::mem::forget` with a reference instead of an owned value does nothing", "rendered": "warning: calls to `std::mem::forget` with a reference instead of an owned value does nothing/n --> ./tests/everything/dedup-suggestions.rs:5:9/n |/n5 | std::mem::forget(&x);/n | ^^^^^^^^^^^^^^^^^--^/n | |/n | argument has type `&Box`/n.../n10 | foo!();/n | ------ in this macro invocation/n |/n = note: `#[warn(forgetting_references)]` on by default/n = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)/nhelp: use `let _ = ...` to ignore the expression or result/n |/n5 - std::mem::forget(&x);/n5 + let _ = &x;/n |/n/n", "spans": [ { "byte_end": 144, "byte_start": 142, "column_end": 28, "column_start": 26, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 179, "byte_start": 173, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 10, "line_start": 10, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": "argument has type `&Box`", "line_end": 5, "line_start": 5, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 28, "highlight_start": 26, "text": " std::mem::forget(&x);" } ] }, { "byte_end": 145, "byte_start": 125, "column_end": 29, "column_start": 9, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 179, "byte_start": 173, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 10, "line_start": 10, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": true, "label": null, "line_end": 5, "line_start": 5, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 29, "highlight_start": 9, "text": " std::mem::forget(&x);" } ] } ] } { "$message_type": "diagnostic", "children": [ { "children": [], "code": null, "level": "help", "message": "use `let _ = ...` to ignore the expression or result", "rendered": null, "spans": [ { "byte_end": 142, "byte_start": 125, "column_end": 26, "column_start": 9, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 191, "byte_start": 185, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 11, "line_start": 11, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": true, "label": null, "line_end": 5, "line_start": 5, "suggested_replacement": "let _ = ", "suggestion_applicability": "MaybeIncorrect", "text": [ { "highlight_end": 26, "highlight_start": 9, "text": " std::mem::forget(&x);" } ] }, { "byte_end": 145, "byte_start": 144, "column_end": 29, "column_start": 28, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 191, "byte_start": 185, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 11, "line_start": 11, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": true, "label": null, "line_end": 5, "line_start": 5, "suggested_replacement": "", "suggestion_applicability": "MaybeIncorrect", "text": [ { "highlight_end": 29, "highlight_start": 28, "text": " std::mem::forget(&x);" } ] } ] } ], "code": { "code": "forgetting_references", "explanation": null }, "level": "warning", "message": "calls to `std::mem::forget` with a reference instead of an owned value does nothing", "rendered": "warning: calls to `std::mem::forget` with a reference instead of an owned value does nothing/n --> ./tests/everything/dedup-suggestions.rs:5:9/n |/n5 | std::mem::forget(&x);/n | ^^^^^^^^^^^^^^^^^--^/n | |/n | argument has type `&Box`/n.../n11 | foo!();/n | ------ in this macro invocation/n |/n = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)/nhelp: use `let _ = ...` to ignore the expression or result/n |/n5 - std::mem::forget(&x);/n5 + let _ = &x;/n |/n/n", "spans": [ { "byte_end": 144, "byte_start": 142, "column_end": 28, "column_start": 26, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 191, "byte_start": 185, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 11, "line_start": 11, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": "argument has type `&Box`", "line_end": 5, "line_start": 5, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 28, "highlight_start": 26, "text": " std::mem::forget(&x);" } ] }, { "byte_end": 145, "byte_start": 125, "column_end": 29, "column_start": 9, "expansion": { "def_site_span": { "byte_end": 73, "byte_start": 57, "column_end": 17, "column_start": 1, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 17, "highlight_start": 1, "text": "macro_rules! foo {" } ] }, "macro_decl_name": "foo!", "span": { "byte_end": 191, "byte_start": 185, "column_end": 11, "column_start": 5, "expansion": null, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": false, "label": null, "line_end": 11, "line_start": 11, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 11, "highlight_start": 5, "text": " foo!();" } ] } }, "file_name": "./tests/everything/dedup-suggestions.rs", "is_primary": true, "label": null, "line_end": 5, "line_start": 5, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 29, "highlight_start": 9, "text": " std::mem::forget(&x);" } ] } ] } { "$message_type": "diagnostic", "children": [], "code": null, "level": "warning", "message": "2 warnings emitted", "rendered": "warning: 2 warnings emitted/n/n", "spans": [] } ================================================ FILE: crates/rustfix/tests/everything/dedup-suggestions.rs ================================================ // See macro_rules! foo { () => { let x = Box::new(1); std::mem::forget(&x); }; } fn main() { foo!(); foo!(); } ================================================ FILE: crates/rustfix/tests/everything/handle-insert-only.fixed.rs ================================================ fn main() { // insert only fix, adds `,` to first match arm // why doesnt this replace 1 with 1,? match &Some(3) { &None => 1, &Some(x) => x, }; } ================================================ FILE: crates/rustfix/tests/everything/handle-insert-only.json ================================================ { "message": "expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`", "code": null, "level": "error", "spans": [ { "file_name": "./tests/everything/handle-insert-only.rs", "byte_start": 163, "byte_end": 165, "line_start": 6, "line_end": 6, "column_start": 18, "column_end": 20, "is_primary": true, "text": [ { "text": " &Some(x) => x,", "highlight_start": 18, "highlight_end": 20 } ], "label": "expected one of `,`, `.`, `?`, `}`, or an operator here", "suggested_replacement": null, "expansion": null } ], "children": [ { "message": "missing a comma here to end this `match` arm", "code": null, "level": "help", "spans": [ { "file_name": "./tests/everything/handle-insert-only.rs", "byte_start": 145, "byte_end": 145, "line_start": 5, "line_end": 5, "column_start": 19, "column_end": 19, "is_primary": true, "text": [ { "text": " &None => 1", "highlight_start": 19, "highlight_end": 19 } ], "label": null, "suggested_replacement": ",", "suggestion_applicability": "Unspecified", "expansion": null } ], "children": [], "rendered": null } ], "rendered": "error: expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`\n --> ./tests/everything/handle-insert-only.rs:6:18\n |\n5 | &None => 1\n | - help: missing a comma here to end this `match` arm\n6 | &Some(x) => x,\n | ^^ expected one of `,`, `.`, `?`, `}`, or an operator here\n\n" } { "message": "aborting due to previous error", "code": null, "level": "error", "spans": [], "children": [], "rendered": "error: aborting due to previous error\n\n" } ================================================ FILE: crates/rustfix/tests/everything/handle-insert-only.rs ================================================ fn main() { // insert only fix, adds `,` to first match arm // why doesnt this replace 1 with 1,? match &Some(3) { &None => 1 &Some(x) => x, }; } ================================================ FILE: crates/rustfix/tests/everything/lt-generic-comp.fixed.rs ================================================ fn main() { let x = 5i64; if (x as u32) < 4 { println!("yay"); } } ================================================ FILE: crates/rustfix/tests/everything/lt-generic-comp.json ================================================ { "message": "`<` is interpreted as a start of generic arguments for `u32`, not a comparison", "code": null, "level": "error", "spans": [ { "file_name": "./tests/everything/lt-generic-comp.rs", "byte_start": 49, "byte_end": 50, "line_start": 4, "line_end": 4, "column_start": 19, "column_end": 20, "is_primary": false, "text": [ { "text": " if x as u32 < 4 {", "highlight_start": 19, "highlight_end": 20 } ], "label": "interpreted as generic arguments", "suggested_replacement": null, "expansion": null }, { "file_name": "./tests/everything/lt-generic-comp.rs", "byte_start": 47, "byte_end": 48, "line_start": 4, "line_end": 4, "column_start": 17, "column_end": 18, "is_primary": true, "text": [ { "text": " if x as u32 < 4 {", "highlight_start": 17, "highlight_end": 18 } ], "label": "not interpreted as comparison", "suggested_replacement": null, "expansion": null } ], "children": [ { "message": "try comparing the cast value", "code": null, "level": "help", "spans": [ { "file_name": "./tests/everything/lt-generic-comp.rs", "byte_start": 38, "byte_end": 46, "line_start": 4, "line_end": 4, "column_start": 8, "column_end": 16, "is_primary": true, "text": [ { "text": " if x as u32 < 4 {", "highlight_start": 8, "highlight_end": 16 } ], "label": null, "suggested_replacement": "(x as u32)", "expansion": null } ], "children": [], "rendered": null } ], "rendered": "error: `<` is interpreted as a start of generic arguments for `u32`, not a comparison\n --> ./tests/everything/lt-generic-comp.rs:4:17\n |\n4 | if x as u32 < 4 {\n | -------- ^ - interpreted as generic arguments\n | | |\n | | not interpreted as comparison\n | help: try comparing the cast value: `(x as u32)`\n\n" } { "message": "aborting due to previous error", "code": null, "level": "error", "spans": [], "children": [], "rendered": "error: aborting due to previous error\n\n" } ================================================ FILE: crates/rustfix/tests/everything/lt-generic-comp.rs ================================================ fn main() { let x = 5i64; if x as u32 < 4 { println!("yay"); } } ================================================ FILE: crates/rustfix/tests/everything/multiple-solutions.fixed.rs ================================================ use std::collections::HashSet; fn main() { let _: HashSet<()>; } ================================================ FILE: crates/rustfix/tests/everything/multiple-solutions.json ================================================ { "$message_type": "diagnostic", "children": [ { "children": [], "code": null, "level": "note", "message": "`#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default", "rendered": null, "spans": [] }, { "children": [], "code": null, "level": "help", "message": "remove the unused imports", "rendered": null, "spans": [ { "byte_end": 32, "byte_start": 23, "column_end": 33, "column_start": 24, "expansion": null, "file_name": "./tests/everything/multiple-solutions.rs", "is_primary": true, "label": null, "line_end": 1, "line_start": 1, "suggested_replacement": "", "suggestion_applicability": "MachineApplicable", "text": [ { "highlight_end": 33, "highlight_start": 24, "text": "use std::collections::{HashMap, HashSet, VecDeque};" } ] }, { "byte_end": 49, "byte_start": 39, "column_end": 50, "column_start": 40, "expansion": null, "file_name": "./tests/everything/multiple-solutions.rs", "is_primary": true, "label": null, "line_end": 1, "line_start": 1, "suggested_replacement": "", "suggestion_applicability": "MachineApplicable", "text": [ { "highlight_end": 50, "highlight_start": 40, "text": "use std::collections::{HashMap, HashSet, VecDeque};" } ] }, { "byte_end": 23, "byte_start": 22, "column_end": 24, "column_start": 23, "expansion": null, "file_name": "./tests/everything/multiple-solutions.rs", "is_primary": true, "label": null, "line_end": 1, "line_start": 1, "suggested_replacement": "", "suggestion_applicability": "MachineApplicable", "text": [ { "highlight_end": 24, "highlight_start": 23, "text": "use std::collections::{HashMap, HashSet, VecDeque};" } ] }, { "byte_end": 50, "byte_start": 49, "column_end": 51, "column_start": 50, "expansion": null, "file_name": "./tests/everything/multiple-solutions.rs", "is_primary": true, "label": null, "line_end": 1, "line_start": 1, "suggested_replacement": "", "suggestion_applicability": "MachineApplicable", "text": [ { "highlight_end": 51, "highlight_start": 50, "text": "use std::collections::{HashMap, HashSet, VecDeque};" } ] } ] } ], "code": { "code": "unused_imports", "explanation": null }, "level": "warning", "message": "unused imports: `HashMap` and `VecDeque`", "rendered": "warning: unused imports: `HashMap` and `VecDeque`/n --> ./tests/everything/multiple-solutions.rs:1:24/n |/n1 | use std::collections::{HashMap, HashSet, VecDeque};/n | ^^^^^^^ ^^^^^^^^/n |/n = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default/n/n", "spans": [ { "byte_end": 30, "byte_start": 23, "column_end": 31, "column_start": 24, "expansion": null, "file_name": "./tests/everything/multiple-solutions.rs", "is_primary": true, "label": null, "line_end": 1, "line_start": 1, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 31, "highlight_start": 24, "text": "use std::collections::{HashMap, HashSet, VecDeque};" } ] }, { "byte_end": 49, "byte_start": 41, "column_end": 50, "column_start": 42, "expansion": null, "file_name": "./tests/everything/multiple-solutions.rs", "is_primary": true, "label": null, "line_end": 1, "line_start": 1, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 50, "highlight_start": 42, "text": "use std::collections::{HashMap, HashSet, VecDeque};" } ] } ] } { "$message_type": "diagnostic", "children": [], "code": null, "level": "warning", "message": "1 warning emitted", "rendered": "warning: 1 warning emitted/n/n", "spans": [] } ================================================ FILE: crates/rustfix/tests/everything/multiple-solutions.rs ================================================ use std::collections::{HashMap, HashSet, VecDeque}; fn main() { let _: HashSet<()>; } ================================================ FILE: crates/rustfix/tests/everything/replace-only-one-char.fixed.rs ================================================ fn main() { let _x = 42; } ================================================ FILE: crates/rustfix/tests/everything/replace-only-one-char.json ================================================ { "$message_type": "diagnostic", "children": [ { "children": [], "code": null, "level": "note", "message": "`#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default", "rendered": null, "spans": [] }, { "children": [], "code": null, "level": "help", "message": "if this is intentional, prefix it with an underscore", "rendered": null, "spans": [ { "byte_end": 21, "byte_start": 20, "column_end": 10, "column_start": 9, "expansion": null, "file_name": "./tests/everything/replace-only-one-char.rs", "is_primary": true, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": "_x", "suggestion_applicability": "MaybeIncorrect", "text": [ { "highlight_end": 10, "highlight_start": 9, "text": " let x = 42;" } ] } ] } ], "code": { "code": "unused_variables", "explanation": null }, "level": "warning", "message": "unused variable: `x`", "rendered": "warning: unused variable: `x`/n --> ./tests/everything/replace-only-one-char.rs:2:9/n |/n2 | let x = 42;/n | ^ help: if this is intentional, prefix it with an underscore: `_x`/n |/n = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default/n/n", "spans": [ { "byte_end": 21, "byte_start": 20, "column_end": 10, "column_start": 9, "expansion": null, "file_name": "./tests/everything/replace-only-one-char.rs", "is_primary": true, "label": null, "line_end": 2, "line_start": 2, "suggested_replacement": null, "suggestion_applicability": null, "text": [ { "highlight_end": 10, "highlight_start": 9, "text": " let x = 42;" } ] } ] } { "$message_type": "diagnostic", "children": [], "code": null, "level": "warning", "message": "1 warning emitted", "rendered": "warning: 1 warning emitted/n/n", "spans": [] } ================================================ FILE: crates/rustfix/tests/everything/replace-only-one-char.rs ================================================ fn main() { let x = 42; } ================================================ FILE: crates/rustfix/tests/everything/str-lit-type-mismatch.fixed.rs ================================================ fn main() { let x: &[u8] = b"foo"; //~ ERROR mismatched types let y: &[u8; 4] = b"baaa"; //~ ERROR mismatched types let z: &str = "foo"; //~ ERROR mismatched types } ================================================ FILE: crates/rustfix/tests/everything/str-lit-type-mismatch.json ================================================ { "message": "mismatched types", "code": { "code": "E0308", "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" }, "level": "error", "spans": [ { "file_name": "./tests/everything/str-lit-type-mismatch.rs", "byte_start": 499, "byte_end": 504, "line_start": 13, "line_end": 13, "column_start": 20, "column_end": 25, "is_primary": true, "text": [ { "text": " let x: &[u8] = \"foo\"; //~ ERROR mismatched types", "highlight_start": 20, "highlight_end": 25 } ], "label": "expected slice, found str", "suggested_replacement": null, "expansion": null } ], "children": [ { "message": "expected type `&[u8]`\n found type `&'static str`", "code": null, "level": "note", "spans": [], "children": [], "rendered": null }, { "message": "consider adding a leading `b`", "code": null, "level": "help", "spans": [ { "file_name": "./tests/everything/str-lit-type-mismatch.rs", "byte_start": 499, "byte_end": 504, "line_start": 13, "line_end": 13, "column_start": 20, "column_end": 25, "is_primary": true, "text": [ { "text": " let x: &[u8] = \"foo\"; //~ ERROR mismatched types", "highlight_start": 20, "highlight_end": 25 } ], "label": null, "suggested_replacement": "b\"foo\"", "expansion": null } ], "children": [], "rendered": null } ], "rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:13:20\n |\n13 | let x: &[u8] = \"foo\"; //~ ERROR mismatched types\n | ^^^^^\n | |\n | expected slice, found str\n | help: consider adding a leading `b`: `b\"foo\"`\n |\n = note: expected type `&[u8]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n" } { "message": "mismatched types", "code": { "code": "E0308", "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" }, "level": "error", "spans": [ { "file_name": "./tests/everything/str-lit-type-mismatch.rs", "byte_start": 555, "byte_end": 561, "line_start": 14, "line_end": 14, "column_start": 23, "column_end": 29, "is_primary": true, "text": [ { "text": " let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types", "highlight_start": 23, "highlight_end": 29 } ], "label": "expected array of 4 elements, found str", "suggested_replacement": null, "expansion": null } ], "children": [ { "message": "expected type `&[u8; 4]`\n found type `&'static str`", "code": null, "level": "note", "spans": [], "children": [], "rendered": null }, { "message": "consider adding a leading `b`", "code": null, "level": "help", "spans": [ { "file_name": "./tests/everything/str-lit-type-mismatch.rs", "byte_start": 555, "byte_end": 561, "line_start": 14, "line_end": 14, "column_start": 23, "column_end": 29, "is_primary": true, "text": [ { "text": " let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types", "highlight_start": 23, "highlight_end": 29 } ], "label": null, "suggested_replacement": "b\"baaa\"", "expansion": null } ], "children": [], "rendered": null } ], "rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:14:23\n |\n14 | let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected array of 4 elements, found str\n | help: consider adding a leading `b`: `b\"baaa\"`\n |\n = note: expected type `&[u8; 4]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n" } { "message": "mismatched types", "code": { "code": "E0308", "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" }, "level": "error", "spans": [ { "file_name": "./tests/everything/str-lit-type-mismatch.rs", "byte_start": 608, "byte_end": 614, "line_start": 15, "line_end": 15, "column_start": 19, "column_end": 25, "is_primary": true, "text": [ { "text": " let z: &str = b\"foo\"; //~ ERROR mismatched types", "highlight_start": 19, "highlight_end": 25 } ], "label": "expected str, found array of 3 elements", "suggested_replacement": null, "expansion": null } ], "children": [ { "message": "expected type `&str`\n found type `&'static [u8; 3]`", "code": null, "level": "note", "spans": [], "children": [], "rendered": null }, { "message": "consider removing the leading `b`", "code": null, "level": "help", "spans": [ { "file_name": "./tests/everything/str-lit-type-mismatch.rs", "byte_start": 608, "byte_end": 614, "line_start": 15, "line_end": 15, "column_start": 19, "column_end": 25, "is_primary": true, "text": [ { "text": " let z: &str = b\"foo\"; //~ ERROR mismatched types", "highlight_start": 19, "highlight_end": 25 } ], "label": null, "suggested_replacement": "\"foo\"", "expansion": null } ], "children": [], "rendered": null } ], "rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:15:19\n |\n15 | let z: &str = b\"foo\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected str, found array of 3 elements\n | help: consider removing the leading `b`: `\"foo\"`\n |\n = note: expected type `&str`\n found type `&'static [u8; 3]`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n" } { "message": "aborting due to 3 previous errors", "code": null, "level": "error", "spans": [], "children": [], "rendered": "error: aborting due to 3 previous errors\n\n" } ================================================ FILE: crates/rustfix/tests/everything/str-lit-type-mismatch.rs ================================================ fn main() { let x: &[u8] = "foo"; //~ ERROR mismatched types let y: &[u8; 4] = "baaa"; //~ ERROR mismatched types let z: &str = b"foo"; //~ ERROR mismatched types } ================================================ FILE: crates/rustfix/tests/everything/use-insert.fixed.rs ================================================ use a::f; mod a { pub fn f() {} } fn main() { f(); } ================================================ FILE: crates/rustfix/tests/everything/use-insert.json ================================================ {"$message_type":"diagnostic","message":"cannot find function `f` in this scope","code":{"code":"E0425","explanation":"An unresolved name was used.\n\nErroneous code examples:\n\n```compile_fail,E0425\nsomething_that_doesnt_exist::foo;\n// error: unresolved name `something_that_doesnt_exist::foo`\n\n// or:\n\ntrait Foo {\n fn bar() {\n Self; // error: unresolved name `Self`\n }\n}\n\n// or:\n\nlet x = unknown_variable; // error: unresolved name `unknown_variable`\n```\n\nPlease verify that the name wasn't misspelled and ensure that the\nidentifier being referred to is valid for the given situation. Example:\n\n```\nenum something_that_does_exist {\n Foo,\n}\n```\n\nOr:\n\n```\nmod something_that_does_exist {\n pub static foo : i32 = 0i32;\n}\n\nsomething_that_does_exist::foo; // ok!\n```\n\nOr:\n\n```\nlet unknown_variable = 12u32;\nlet x = unknown_variable; // ok!\n```\n\nIf the item is not defined in the current module, it must be imported using a\n`use` statement, like so:\n\n```\n# mod foo { pub fn bar() {} }\n# fn main() {\nuse foo::bar;\nbar();\n# }\n```\n\nIf the item you are importing is not defined in some super-module of the\ncurrent module, then it must also be declared as public (e.g., `pub fn`).\n"},"level":"error","spans":[{"file_name":"./tests/everything/use-insert.rs","byte_start":45,"byte_end":46,"line_start":6,"line_end":6,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":" f();","highlight_start":5,"highlight_end":6}],"label":"not found in this scope","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"consider importing this function","code":null,"level":"help","spans":[{"file_name":"./tests/everything/use-insert.rs","byte_start":0,"byte_end":0,"line_start":1,"line_end":1,"column_start":1,"column_end":1,"is_primary":true,"text":[],"label":null,"suggested_replacement":"use a::f;\n\n","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"error[E0425]: cannot find function `f` in this scope\n --> ./tests/everything/use-insert.rs:6:5\n |\n6 | f();\n | ^ not found in this scope\n |\nhelp: consider importing this function\n |\n1 + use a::f;\n |\n\n"} {"$message_type":"diagnostic","message":"aborting due to 1 previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to 1 previous error\n\n"} {"$message_type":"diagnostic","message":"For more information about this error, try `rustc --explain E0425`.","code":null,"level":"failure-note","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0425`.\n"} ================================================ FILE: crates/rustfix/tests/everything/use-insert.rs ================================================ mod a { pub fn f() {} } fn main() { f(); } ================================================ FILE: crates/rustfix/tests/parse_and_replace.rs ================================================ //! Tests that verify rustfix applies the appropriate changes to a file. //! //! This test works by reading a series of `*.rs` files in the //! `tests/everything` directory. For each `.rs` file, it runs `rustc` to //! collect JSON diagnostics from the file. It feeds that JSON data into //! rustfix and applies the recommended suggestions to the `.rs` file. It then //! compares the result with the corresponding `.fixed.rs` file. If they don't //! match, then the test fails. //! //! The files ending in `.nightly.rs` will run only on the nightly toolchain //! //! To override snapshots, run `SNAPSHOTS=overwrite cargo test`. //! See [`snapbox::assert::Action`] for different actions. #![allow(clippy::disallowed_methods, clippy::print_stdout, clippy::print_stderr)] use anyhow::{Context, Error, anyhow}; use rustfix::apply_suggestions; use serde_json::Value; use snapbox::data::DataFormat; use snapbox::{Assert, Data}; use std::collections::HashSet; use std::env; use std::ffi::OsString; use std::fs; use std::path::Path; use std::process::{Command, Output}; use tempfile::tempdir; mod fixmode { pub const EVERYTHING: &str = "yolo"; } static mut VERSION: (u32, bool) = (0, false); // Temporarily copy from `cargo_test_macro::version`. fn version() -> (u32, bool) { static INIT: std::sync::Once = std::sync::Once::new(); INIT.call_once(|| { let output = Command::new("rustc") .arg("-V") .output() .expect("cargo should run"); let stdout = std::str::from_utf8(&output.stdout).expect("utf8"); let vers = stdout.split_whitespace().skip(1).next().unwrap(); let is_nightly = option_env!("CARGO_TEST_DISABLE_NIGHTLY").is_none() && (vers.contains("-nightly") || vers.contains("-dev")); let minor = vers.split('.').skip(1).next().unwrap().parse().unwrap(); unsafe { VERSION = (minor, is_nightly) } }); unsafe { VERSION } } fn compile(file: &Path) -> Result { let tmp = tempdir()?; let args: Vec = vec![ file.into(), "--error-format=json".into(), "--emit=metadata".into(), "--crate-name=rustfix_test".into(), "--out-dir".into(), tmp.path().into(), ]; let res = Command::new(env::var_os("RUSTC").unwrap_or("rustc".into())) .args(&args) .env("CLIPPY_DISABLE_DOCS_LINKS", "true") .env_remove("RUST_LOG") .output()?; Ok(res) } fn compile_and_get_json_errors(file: &Path) -> Result { let res = compile(file)?; let stderr = String::from_utf8(res.stderr)?; if stderr.contains("is only accepted on the nightly compiler") { panic!("rustfix tests require a nightly compiler"); } match res.status.code() { Some(0) | Some(1) | Some(101) => Ok(stderr), _ => Err(anyhow!( "failed with status {:?}: {}", res.status.code(), stderr )), } } fn compiles_without_errors(file: &Path) -> Result<(), Error> { let res = compile(file)?; match res.status.code() { Some(0) => Ok(()), _ => Err(anyhow!( "file {:?} failed compile with status {:?}:\n {}", file, res.status.code(), String::from_utf8(res.stderr)? )), } } fn test_rustfix_with_file>(file: P, mode: &str) { let file: &Path = file.as_ref(); let json_file = file.with_extension("json"); let expected_fixed_file = file.with_extension("fixed.rs"); let filter_suggestions = if mode == fixmode::EVERYTHING { rustfix::Filter::Everything } else { rustfix::Filter::MachineApplicableOnly }; let code = fs::read_to_string(file).unwrap(); let json = compile_and_get_json_errors(file) .with_context(|| format!("could not compile {}", file.display())) .unwrap(); let suggestions = rustfix::get_suggestions_from_json(&json, &HashSet::new(), filter_suggestions) .context("could not load suggestions") .unwrap(); let fixed = apply_suggestions(&code, &suggestions) .with_context(|| format!("could not apply suggestions to {}", file.display())) .unwrap() .replace('\r', ""); let assert = Assert::new().action_env(snapbox::assert::DEFAULT_ACTION_ENV); let (actual_fix, expected_fix) = assert.normalize( Data::text(&fixed), Data::read_from(expected_fixed_file.as_path(), Some(DataFormat::Text)), ); if actual_fix != expected_fix { let fixed_assert = assert.try_eq(Some(&"Current Fix"), actual_fix, expected_fix); assert!(fixed_assert.is_ok(), "{}", fixed_assert.err().unwrap()); let expected_json = Data::read_from(json_file.as_path(), Some(DataFormat::Text)); let pretty_json = json .split("\n") .filter(|j| !j.is_empty()) .map(|j| { serde_json::to_string_pretty(&serde_json::from_str::(j).unwrap()).unwrap() }) .collect::>() .join("\n"); let json_assert = assert.try_eq( Some(&"Compiler Error"), Data::text(pretty_json), expected_json, ); assert!(json_assert.is_ok(), "{}", json_assert.err().unwrap()); } compiles_without_errors(&expected_fixed_file).unwrap(); } macro_rules! run_test { ($name:ident, $file:expr) => { #[test] #[allow(non_snake_case)] fn $name() { let (_, nightly) = version(); if !$file.ends_with(".nightly.rs") || nightly { let file = Path::new(concat!("./tests/everything/", $file)); assert!(file.is_file(), "could not load {}", $file); test_rustfix_with_file(file, fixmode::EVERYTHING); } } }; } run_test! { closure_immutable_outer_variable, "closure-immutable-outer-variable.rs" } run_test! {dedup_suggestions, "dedup-suggestions.rs"} run_test! {E0178, "E0178.rs"} run_test! {handle_insert_only, "handle-insert-only.rs"} run_test! {lt_generic_comp, "lt-generic-comp.rs"} run_test! {multiple_solutions, "multiple-solutions.rs"} run_test! {replace_only_one_char, "replace-only-one-char.rs"} run_test! {str_lit_type_mismatch, "str-lit-type-mismatch.rs"} run_test! {use_insert, "use-insert.rs"} ================================================ FILE: crates/semver-check/Cargo.toml ================================================ [package] name = "semver-check" version = "0.0.0" authors = ["Eric Huss"] edition.workspace = true publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tempfile.workspace = true [lints] workspace = true ================================================ FILE: crates/semver-check/src/main.rs ================================================ //! Test runner for the semver compatibility doc chapter. //! //! This extracts all the "rust" annotated code blocks and tests that they //! either fail or succeed as expected. This also checks that the examples are //! formatted correctly. //! //! An example with the word "MINOR" at the top is expected to successfully //! build against the before and after. Otherwise it should fail. A comment of //! "// Error:" will check that the given message appears in the error output. //! //! The code block can also include the annotations: //! - `run-fail`: The test should fail at runtime, not compiletime. //! - `dont-deny`: By default tests have a `#![deny(warnings)]`. This option //! avoids this attribute. Note that `#![allow(unused)]` is always added. #![allow(clippy::print_stderr)] use std::error::Error; use std::fs; use std::path::Path; use std::process::{Command, Output}; fn main() { if let Err(e) = doit() { eprintln!("error: {}", e); std::process::exit(1); } } const SEPARATOR: &str = "///////////////////////////////////////////////////////////"; fn doit() -> Result<(), Box> { let filename = std::env::args().nth(1).unwrap_or_else(|| { Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../src/doc/src/reference/semver.md") .to_str() .unwrap() .to_string() }); let contents = fs::read_to_string(filename)?; let mut lines = contents.lines().enumerate(); loop { // Find a rust block. let (block_start, run_program, deny_warnings) = loop { match lines.next() { Some((lineno, line)) => { if line.trim().starts_with("```rust") && !line.contains("skip") { break ( lineno + 1, line.contains("run-fail"), !line.contains("dont-deny"), ); } } None => return Ok(()), } }; // Read in the code block. let mut block = Vec::new(); loop { match lines.next() { Some((_, line)) => { if line.trim() == "```" { break; } // Support rustdoc/mdbook hidden lines. let line = line.strip_prefix("# ").unwrap_or(line); if line == "#" { block.push(""); } else { block.push(line); } } None => { return Err(format!( "rust block did not end for example starting on line {}", block_start ) .into()); } } } // Split it into the separate source files. let parts: Vec<_> = block.split(|line| line.trim() == SEPARATOR).collect(); if parts.len() != 4 { return Err(format!( "expected 4 sections in example starting on line {}, got {}:\n{:?}", block_start, parts.len(), parts ) .into()); } let join = |part: &[&str]| { let mut result = String::new(); result.push_str("#![allow(unused)]\n"); if deny_warnings { result.push_str("#![deny(warnings)]\n"); } result.push_str(&part.join("\n")); if !result.ends_with('\n') { result.push('\n'); } result }; let expect_success = parts[0][0].contains("MINOR"); eprintln!("Running test from line {}", block_start); let result = run_test( join(parts[1]), join(parts[2]), join(parts[3]), expect_success, run_program, ); if let Err(e) = result { return Err(format!( "test failed for example starting on line {}: {}", block_start, e ) .into()); } } } const CRATE_NAME: &str = "updated_crate"; fn run_test( before: String, after: String, example: String, expect_success: bool, run_program: bool, ) -> Result<(), Box> { let tempdir = tempfile::TempDir::new()?; let before_p = tempdir.path().join("before.rs"); let after_p = tempdir.path().join("after.rs"); let example_p = tempdir.path().join("example.rs"); let check_fn = if run_program { run_check } else { compile_check }; compile_check(before, &before_p, CRATE_NAME, false, true)?; check_fn(example.clone(), &example_p, "example", true, true)?; compile_check(after, &after_p, CRATE_NAME, false, true)?; check_fn(example, &example_p, "example", true, expect_success)?; Ok(()) } fn check_formatting(path: &Path) -> Result<(), Box> { match Command::new("rustfmt") .args(&["--edition=2018", "--check"]) .arg(path) .status() { Ok(status) => { if !status.success() { return Err(format!("failed to run rustfmt: {}", status).into()); } Ok(()) } Err(e) => Err(format!("failed to run rustfmt: {}", e).into()), } } fn compile( contents: &str, path: &Path, crate_name: &str, extern_path: bool, ) -> Result> { let crate_type = if contents.contains("fn main()") { "bin" } else { "rlib" }; fs::write(path, &contents)?; check_formatting(path)?; let out_dir = path.parent().unwrap(); let mut cmd = Command::new("rustc"); cmd.args(&[ "--edition=2021", "--crate-type", crate_type, "--crate-name", crate_name, "--out-dir", ]); cmd.arg(&out_dir); if extern_path { let epath = out_dir.join(format!("lib{}.rlib", CRATE_NAME)); cmd.arg("--extern") .arg(format!("{}={}", CRATE_NAME, epath.display())); } cmd.arg(path); cmd.output().map_err(Into::into) } fn compile_check( mut contents: String, path: &Path, crate_name: &str, extern_path: bool, expect_success: bool, ) -> Result<(), Box> { // If the example has an error message, remove it so that it can be // compared with the actual output, and also to avoid issues with rustfmt // moving it around. let expected_error = match contents.find("// Error:") { Some(index) => { let start = contents[..index].rfind(|ch| ch != ' ').unwrap(); let end = contents[index..].find('\n').unwrap(); let error = contents[index + 9..index + end].trim().to_string(); contents.replace_range(start + 1..index + end, ""); Some(error) } None => None, }; let output = compile(&contents, path, crate_name, extern_path)?; let stderr = std::str::from_utf8(&output.stderr).unwrap(); match (output.status.success(), expect_success) { (true, true) => Ok(()), (true, false) => Err(format!( "expected failure, got success {}\n===== Contents:\n{}\n===== Output:\n{}\n", path.display(), contents, stderr ) .into()), (false, true) => Err(format!( "expected success, got error {}\n===== Contents:\n{}\n===== Output:\n{}\n", path.display(), contents, stderr ) .into()), (false, false) => { if expected_error.is_none() { return Err("failing test should have an \"// Error:\" annotation ".into()); } let expected_error = expected_error.unwrap(); if !stderr.contains(&expected_error) { Err(format!( "expected error message not found in compiler output\nExpected: {}\nGot:\n{}\n", expected_error, stderr ) .into()) } else { Ok(()) } } } } fn run_check( contents: String, path: &Path, crate_name: &str, extern_path: bool, expect_success: bool, ) -> Result<(), Box> { let compile_output = compile(&contents, path, crate_name, extern_path)?; if !compile_output.status.success() { let stderr = std::str::from_utf8(&compile_output.stderr).unwrap(); return Err(format!( "expected success, got error {}\n===== Contents:\n{}\n===== Output:\n{}\n", path.display(), contents, stderr ) .into()); } let binary_path = path.parent().unwrap().join(crate_name); let output = Command::new(binary_path).output()?; let stderr = std::str::from_utf8(&output.stderr).unwrap(); match (output.status.success(), expect_success) { (true, false) => Err(format!( "expected panic, got success {}\n===== Contents:\n{}\n===== Output:\n{}\n", path.display(), contents, stderr ) .into()), (false, true) => Err(format!( "expected success, got panic {}\n===== Contents:\n{}\n===== Output:\n{}\n", path.display(), contents, stderr, ) .into()), (_, _) => Ok(()), } } ================================================ FILE: crates/xtask-build-man/Cargo.toml ================================================ [package] name = "xtask-build-man" version = "0.0.0" edition.workspace = true publish = false [dependencies] [lints] workspace = true ================================================ FILE: crates/xtask-build-man/src/main.rs ================================================ //! ```text //! NAME //! build-man //! //! SYNOPSIS //! build-man //! //! DESCRIPTION //! Build the man pages for packages `mdman` and `cargo`. //! For more, read their doc comments. //! ``` #![allow(clippy::print_stderr)] use std::fs; use std::io; use std::path::PathBuf; use std::process; use std::process::Command; fn main() -> io::Result<()> { build_mdman()?; build_cargo()?; Ok(()) } /// Builds the man pages for `mdman`. fn build_mdman() -> io::Result<()> { cwd_to_workspace_root()?; let src_paths = &["crates/mdman/doc/mdman.md".into()]; let dst_path = "crates/mdman/doc/out"; let outs = [("md", dst_path), ("txt", dst_path), ("man", dst_path)]; build_man("mdman", src_paths, &outs, &[]) } /// Builds the man pages for Cargo. /// /// The source for the man pages are located in src/doc/man/ in markdown format. /// These also are handlebars templates, see crates/mdman/README.md for details. /// /// The generated man pages are placed in the src/etc/man/ directory. The pages /// are also expanded into markdown (after being expanded by handlebars) and /// saved in the src/doc/src/commands/ directory. These are included in the /// Cargo book, which is converted to HTML by mdbook. fn build_cargo() -> io::Result<()> { // Find all `src/doc/man/cargo*.md` let src_paths = { let mut src_paths = Vec::new(); for entry in fs::read_dir("src/doc/man")? { let entry = entry?; let file_name = entry.file_name(); let file_name = file_name.to_str().unwrap(); if file_name.starts_with("cargo") && file_name.ends_with(".md") { src_paths.push(entry.path()); } } src_paths }; let outs = [ ("md", "src/doc/src/commands"), ("txt", "src/doc/man/generated_txt"), ("man", "src/etc/man"), ]; let args = [ "--url", "https://doc.rust-lang.org/cargo/commands/", "--man", "rustc:1=https://doc.rust-lang.org/rustc/index.html", "--man", "rustdoc:1=https://doc.rust-lang.org/rustdoc/index.html", ]; build_man("cargo", &src_paths[..], &outs, &args) } /// Change to workspace root. /// /// Assumed this xtask is located in `[WORKSPACE]/crates/xtask-build-man`. fn cwd_to_workspace_root() -> io::Result<()> { let pkg_root = std::env!("CARGO_MANIFEST_DIR"); let ws_root = format!("{pkg_root}/../.."); std::env::set_current_dir(ws_root) } /// Builds the man pages. fn build_man( pkg_name: &str, src_paths: &[PathBuf], outs: &[(&str, &str)], extra_args: &[&str], ) -> io::Result<()> { for (format, dst_path) in outs { eprintln!("Start converting `{format}` for package `{pkg_name}`..."); let mut cmd = Command::new(std::env!("CARGO")); cmd.args(["run", "--package", "mdman", "--"]) .args(["-t", format, "-o", dst_path]) .args(src_paths) .args(extra_args); let status = cmd.status()?; if !status.success() { eprintln!("failed to build the man pages for package `{pkg_name}`"); eprintln!("failed command: `{cmd:?}`"); process::exit(status.code().unwrap_or(1)); } } Ok(()) } ================================================ FILE: crates/xtask-bump-check/Cargo.toml ================================================ [package] name = "xtask-bump-check" version = "0.0.0" edition.workspace = true publish = false [dependencies] anyhow.workspace = true cargo.workspace = true cargo-util.workspace = true clap.workspace = true git2.workspace = true semver.workspace = true tracing-subscriber.workspace = true tracing.workspace = true [lints] workspace = true ================================================ FILE: crates/xtask-bump-check/src/main.rs ================================================ mod xtask; fn main() { setup_logger(); let cli = xtask::cli(); let matches = cli.get_matches(); let mut gctx = cargo::util::context::GlobalContext::default().unwrap_or_else(|e| { let mut eval = cargo::core::shell::Shell::new(); cargo::exit_with_error(e.into(), &mut eval) }); if let Err(e) = xtask::exec(&matches, &mut gctx) { cargo::exit_with_error(e, &mut gctx.shell()) } } // In sync with `src/bin/cargo/main.rs@setup_logger`. fn setup_logger() { let env = tracing_subscriber::EnvFilter::from_env("CARGO_LOG"); tracing_subscriber::fmt() .with_timer(tracing_subscriber::fmt::time::Uptime::default()) .with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr())) .with_writer(std::io::stderr) .with_env_filter(env) .init(); } ================================================ FILE: crates/xtask-bump-check/src/xtask.rs ================================================ //! ```text //! NAME //! xtask-bump-check //! //! SYNOPSIS //! xtask-bump-check --base-rev --head-rev //! //! DESCRIPTION //! Checks if there is any member got changed since a base commit //! but forgot to bump its version. //! ``` #![allow(clippy::print_stdout)] // Fine for build utilities use std::collections::HashMap; use std::fmt::Write; use std::fs; use std::task; use cargo::CargoResult; use cargo::core::Package; use cargo::core::Registry; use cargo::core::SourceId; use cargo::core::Workspace; use cargo::core::dependency::Dependency; use cargo::sources::source::QueryKind; use cargo::util::cache_lock::CacheLockMode; use cargo::util::command_prelude::*; use cargo_util::ProcessBuilder; const UPSTREAM_BRANCH: &str = "master"; const STATUS: &str = "BumpCheck"; pub fn cli() -> clap::Command { clap::Command::new("xtask-bump-check") .arg( opt( "verbose", "Use verbose output (-vv very verbose/build.rs output)", ) .short('v') .action(ArgAction::Count) .global(true), ) .arg( flag("quiet", "Do not print cargo log messages") .short('q') .global(true), ) .arg( opt("color", "Coloring: auto, always, never") .value_name("WHEN") .global(true), ) .arg(opt("base-rev", "Git revision to lookup for a baseline")) .arg(opt("head-rev", "Git revision with changes")) .arg(flag("frozen", "Require Cargo.lock and cache to be up-to-date").global(true)) .arg(flag("locked", "Require Cargo.lock to be up-to-date").global(true)) .arg(flag("offline", "Run without accessing the network").global(true)) .arg(multi_opt("config", "KEY=VALUE", "Override a configuration value").global(true)) .arg(flag("github", "Group output using GitHub's syntax")) .arg( Arg::new("unstable-features") .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details") .short('Z') .value_name("FLAG") .action(ArgAction::Append) .global(true), ) } pub fn exec(args: &clap::ArgMatches, gctx: &mut cargo::util::GlobalContext) -> cargo::CliResult { global_context_configure(gctx, args)?; bump_check(args, gctx)?; Ok(()) } fn global_context_configure(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let verbose = args.verbose(); // quiet is unusual because it is redefined in some subcommands in order // to provide custom help text. let quiet = args.flag("quiet"); let color = args.get_one::("color").map(String::as_str); let frozen = args.flag("frozen"); let locked = args.flag("locked"); let offline = args.flag("offline"); let mut unstable_flags = vec![]; if let Some(values) = args.get_many::("unstable-features") { unstable_flags.extend(values.cloned()); } let mut config_args = vec![]; if let Some(values) = args.get_many::("config") { config_args.extend(values.cloned()); } gctx.configure( verbose, quiet, color, frozen, locked, offline, &None, &unstable_flags, &config_args, )?; Ok(()) } /// Main entry of `xtask-bump-check`. /// /// Assumption: version number are incremental. We never have point release for old versions. fn bump_check(args: &clap::ArgMatches, gctx: &cargo::util::GlobalContext) -> CargoResult<()> { let ws = args.workspace(gctx)?; let repo = git2::Repository::open(ws.root())?; let base_commit = get_base_commit(gctx, args, &repo)?; let head_commit = get_head_commit(args, &repo)?; let referenced_commit = get_referenced_commit(&repo, &base_commit)?; let github = args.get_flag("github"); let status = |msg: &str| gctx.shell().status(STATUS, msg); let crates_not_check_against_channels = [ // High false positive rate between beta branch and requisite version bump soon after // // Low risk because we always bump the "major" version after beta branch; we are // only losing out on checks for patch releases. // // Note: this is already skipped in `changed` "cargo", // Don't check against beta and stable branches, // as the publish of these crates are not tied with Rust release process. // See `TO_PUBLISH` in publish.py. "home", ]; status(&format!("base commit `{}`", base_commit.id()))?; status(&format!("head commit `{}`", head_commit.id()))?; let mut needs_bump = Vec::new(); if github { println!("::group::Checking for bumps of changed packages"); } let changed_members = changed(&ws, &repo, &base_commit, &head_commit)?; check_crates_io(&ws, &changed_members, &mut needs_bump)?; if let Some(referenced_commit) = referenced_commit.as_ref() { status(&format!("compare against `{}`", referenced_commit.id()))?; for referenced_member in checkout_ws(&ws, &repo, referenced_commit)?.members() { let pkg_name = referenced_member.name().as_str(); if crates_not_check_against_channels.contains(&pkg_name) { continue; } let Some(changed_member) = changed_members.get(pkg_name) else { tracing::trace!("skipping {pkg_name}, may be removed or not published"); continue; }; if changed_member.version() <= referenced_member.version() { needs_bump.push(*changed_member); } } } if !needs_bump.is_empty() { needs_bump.sort(); needs_bump.dedup(); let mut msg = String::new(); msg.push_str("Detected changes in these crates but no version bump found:\n"); for pkg in needs_bump { writeln!(&mut msg, " {}@{}", pkg.name(), pkg.version())?; } msg.push_str("\nPlease bump at least one patch version in each corresponding Cargo.toml."); anyhow::bail!(msg) } if github { println!("::endgroup::"); } if let Some(referenced_commit) = referenced_commit.as_ref() { if github { println!("::group::SemVer Checks against {}", referenced_commit.id()); } let mut cmd = ProcessBuilder::new("cargo"); cmd.arg("semver-checks") .arg("--workspace") .arg("--baseline-rev") .arg(referenced_commit.id().to_string()); for krate in crates_not_check_against_channels { cmd.args(&["--exclude", krate]); } gctx.shell().status("Running", &cmd)?; cmd.exec()?; if github { println!("::endgroup::"); } } // Even when we test against baseline-rev, we still need to make sure a // change doesn't violate SemVer rules against crates.io releases. The // possibility of this happening is nearly zero but no harm to check twice. if github { println!("::group::SemVer Checks against crates.io"); } let mut cmd = ProcessBuilder::new("cargo"); cmd.arg("semver-checks") .arg("check-release") .arg("--workspace") .args(&["--exclude", "cargo"]); gctx.shell().status("Running", &cmd)?; cmd.exec()?; // Cargo has mutually exclusive features for different HTTP backends, so // pass a specific `--features` instead of including this in the // `--all-features` performed by the previous command. let mut cmd = ProcessBuilder::new("cargo"); cmd.arg("semver-checks") .arg("check-release") .args(&["--package", "cargo"]) .arg("--default-features") .args(&["--features", "all-static"]); gctx.shell().status("Running", &cmd)?; cmd.exec()?; if github { println!("::endgroup::"); } status("no version bump needed for member crates.")?; Ok(()) } /// Returns the commit of upstream `master` branch if `base-rev` is missing. fn get_base_commit<'a>( gctx: &GlobalContext, args: &clap::ArgMatches, repo: &'a git2::Repository, ) -> CargoResult> { let base_commit = match args.get_one::("base-rev") { Some(sha) => { let obj = repo.revparse_single(sha)?; obj.peel_to_commit()? } None => { let upstream_branches = repo .branches(Some(git2::BranchType::Remote))? .filter_map(|r| r.ok()) .filter(|(b, _)| { b.name() .ok() .flatten() .unwrap_or_default() .ends_with(&format!("/{UPSTREAM_BRANCH}")) }) .map(|(b, _)| b) .collect::>(); if upstream_branches.is_empty() { anyhow::bail!( "could not find `base-sha` for `{UPSTREAM_BRANCH}`, pass it in directly" ); } let upstream_ref = upstream_branches[0].get(); if upstream_branches.len() > 1 { let name = upstream_ref.name().expect("name is valid UTF-8"); let _ = gctx.shell().warn(format!( "multiple `{UPSTREAM_BRANCH}` found, picking {name}" )); } upstream_ref.peel_to_commit()? } }; Ok(base_commit) } /// Returns `HEAD` of the Git repository if `head-rev` is missing. fn get_head_commit<'a>( args: &clap::ArgMatches, repo: &'a git2::Repository, ) -> CargoResult> { let head_commit = match args.get_one::("head-rev") { Some(sha) => { let head_obj = repo.revparse_single(sha)?; head_obj.peel_to_commit()? } None => { let head_ref = repo.head()?; head_ref.peel_to_commit()? } }; Ok(head_commit) } /// Gets the referenced commit to compare if version bump needed. /// /// * When merging into nightly, check the version with beta branch /// * When merging into beta, check the version with stable branch /// * When merging into stable, check against crates.io registry directly fn get_referenced_commit<'a>( repo: &'a git2::Repository, base: &git2::Commit<'a>, ) -> CargoResult>> { let [beta, stable] = beta_and_stable_branch(repo)?; let rev_id = base.id(); let stable_commit = stable.get().peel_to_commit()?; let beta_commit = beta.get().peel_to_commit()?; let referenced_commit = if rev_id == stable_commit.id() { None } else if rev_id == beta_commit.id() { tracing::trace!("stable branch from `{}`", stable.name().unwrap().unwrap()); Some(stable_commit) } else { tracing::trace!("beta branch from `{}`", beta.name().unwrap().unwrap()); Some(beta_commit) }; Ok(referenced_commit) } /// Get the current beta and stable branch in cargo repository. /// /// Assumptions: /// /// * The repository contains the full history of `/rust-1.*.0` branches. /// * The version part of `/rust-1.*.0` always ends with a zero. /// * The maximum version is for beta channel, and the second one is for stable. fn beta_and_stable_branch(repo: &git2::Repository) -> CargoResult<[git2::Branch<'_>; 2]> { let mut release_branches = Vec::new(); for branch in repo.branches(Some(git2::BranchType::Remote))? { let (branch, _) = branch?; let name = branch.name()?.unwrap(); let Some((_, version)) = name.split_once("/rust-") else { tracing::trace!("branch `{name}` is not in the format of `/rust-`"); continue; }; let Ok(version) = version.parse::() else { tracing::trace!("branch `{name}` is not a valid semver: `{version}`"); continue; }; release_branches.push((version, branch)); } release_branches.sort_unstable_by(|a, b| a.0.cmp(&b.0)); release_branches.dedup_by(|a, b| a.0 == b.0); let beta = release_branches.pop().unwrap(); let stable = release_branches.pop().unwrap(); assert_eq!(beta.0.major, 1); assert_eq!(beta.0.patch, 0); assert_eq!(stable.0.major, 1); assert_eq!(stable.0.patch, 0); assert_ne!(beta.0.minor, stable.0.minor); Ok([beta.1, stable.1]) } /// Lists all changed workspace members between two commits. fn changed<'r, 'ws>( ws: &'ws Workspace<'_>, repo: &'r git2::Repository, base_commit: &git2::Commit<'r>, head: &git2::Commit<'r>, ) -> CargoResult> { let root_pkg_name = ws.current()?.name(); // `cargo` crate. let ws_members = ws .members() .filter(|pkg| pkg.name() != root_pkg_name) // Only take care of sub crates here. .filter(|pkg| pkg.publish() != &Some(vec![])) // filter out `publish = false` .map(|pkg| { // Having relative package root path so that we can compare with // paths of changed files to determine which package has changed. let relative_pkg_root = pkg.root().strip_prefix(ws.root()).unwrap(); (relative_pkg_root, pkg) }) .collect::>(); let diff = symmetric_diff(repo, base_commit, head)?; let mut changed_members = HashMap::new(); for delta in diff.deltas() { let old = delta.old_file().path().unwrap(); let new = delta.new_file().path().unwrap(); for (pkg_root, pkg) in ws_members.iter() { if old.starts_with(pkg_root) || new.starts_with(pkg_root) { changed_members.insert(pkg.name().as_str(), *pkg); break; } } } tracing::trace!("changed_members: {:?}", changed_members.keys()); Ok(changed_members) } /// Using a "symmetric difference" between base and head. fn symmetric_diff<'a>( repo: &'a git2::Repository, base: &'a git2::Commit<'a>, head: &'a git2::Commit<'a>, ) -> CargoResult> { let ancestor_oid = repo.merge_base(base.id(), head.id())?; let ancestor_commit = repo.find_commit(ancestor_oid)?; let ancestor_tree = ancestor_commit.as_object().peel_to_tree()?; let head_tree = head.as_object().peel_to_tree()?; let diff = repo.diff_tree_to_tree(Some(&ancestor_tree), Some(&head_tree), Default::default())?; tracing::info!(merge_base = %ancestor_commit.id(), base = %base.id(), head = %head.id(), "git diff base...head"); Ok(diff) } /// Compares version against published crates on crates.io. /// /// Assumption: We always release a version larger than all existing versions. fn check_crates_io<'a>( ws: &Workspace<'a>, changed_members: &HashMap<&'a str, &'a Package>, needs_bump: &mut Vec<&'a Package>, ) -> CargoResult<()> { let gctx = ws.gctx(); let source_id = SourceId::crates_io(gctx)?; let mut registry = ws.package_registry()?; let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; registry.lock_patches(); gctx.shell().status( STATUS, format_args!("compare against `{}`", source_id.display_registry_name()), )?; for (name, member) in changed_members { let current = member.version(); let version_req = format!(">={current}"); let query = Dependency::parse(*name, Some(&version_req), source_id)?; let possibilities = loop { // Exact to avoid returning all for path/git match registry.query_vec(&query, QueryKind::Exact) { task::Poll::Ready(res) => { break res?; } task::Poll::Pending => registry.block_until_ready()?, } }; if possibilities.is_empty() { tracing::trace!("dep `{name}` has no version greater than or equal to `{current}`"); } else { tracing::trace!( "`{name}@{current}` needs a bump because its should have a version newer than crates.io: {:?}`", possibilities .iter() .map(|s| s.as_summary()) .map(|s| format!("{}@{}", s.name(), s.version())) .collect::>(), ); needs_bump.push(member); } } Ok(()) } /// Checkouts a temporary workspace to do further version comparisons. fn checkout_ws<'gctx, 'a>( ws: &Workspace<'gctx>, repo: &'a git2::Repository, referenced_commit: &git2::Commit<'a>, ) -> CargoResult> { let repo_path = repo.path().as_os_str().to_str().unwrap(); // Put it under `target/cargo-` let short_id = &referenced_commit.id().to_string()[..7]; let checkout_path = ws.target_dir().join(format!("cargo-{short_id}")); let checkout_path = checkout_path.as_path_unlocked(); let _ = fs::remove_dir_all(checkout_path); let new_repo = git2::build::RepoBuilder::new() .clone_local(git2::build::CloneLocal::Local) .clone(repo_path, checkout_path)?; let obj = new_repo.find_object(referenced_commit.id(), None)?; new_repo.reset(&obj, git2::ResetType::Hard, None)?; Workspace::new(&checkout_path.join("Cargo.toml"), ws.gctx()) } #[test] fn verify_cli() { cli().debug_assert(); } ================================================ FILE: crates/xtask-lint-docs/Cargo.toml ================================================ [package] name = "xtask-lint-docs" version = "0.1.0" edition.workspace = true publish = false [dependencies] anyhow.workspace = true cargo.workspace = true clap.workspace = true itertools.workspace = true [lints] workspace = true ================================================ FILE: crates/xtask-lint-docs/src/main.rs ================================================ use std::fmt::Write; use std::path::PathBuf; use cargo::lints::Lint; use cargo::lints::LintLevel; use cargo::util::command_prelude::{ArgMatchesExt, flag}; use itertools::Itertools; fn cli() -> clap::Command { clap::Command::new("xtask-lint-docs").arg(flag("check", "Check that the docs are up-to-date")) } fn main() -> anyhow::Result<()> { let args = cli().get_matches(); let check = args.flag("check"); let mut allow = Vec::new(); let mut warn = Vec::new(); let mut deny = Vec::new(); let mut forbid = Vec::new(); let mut lint_docs = String::new(); for lint in cargo::lints::LINTS.iter().sorted_by_key(|lint| lint.name) { if lint.docs.is_some() { let sectipn = match lint.primary_group.default_level { LintLevel::Allow => &mut allow, LintLevel::Warn => &mut warn, LintLevel::Deny => &mut deny, LintLevel::Forbid => &mut forbid, }; sectipn.push(lint.name); add_lint(lint, &mut lint_docs)?; } } let mut buf = String::new(); writeln!(buf, "# Lints\n")?; writeln!( buf, "Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only be used on nightly toolchains" )?; writeln!(buf)?; lint_groups(&mut buf)?; if !allow.is_empty() { add_level_section(LintLevel::Allow, &allow, &mut buf)?; } if !warn.is_empty() { add_level_section(LintLevel::Warn, &warn, &mut buf)?; } if !deny.is_empty() { add_level_section(LintLevel::Deny, &deny, &mut buf)?; } if !forbid.is_empty() { add_level_section(LintLevel::Forbid, &forbid, &mut buf)?; } buf.push_str(&lint_docs); if check { let old = std::fs::read_to_string(lint_docs_path())?; if old != buf { anyhow::bail!( "The lints documentation is out-of-date. Run `cargo lint-docs` to update it." ); } } else { std::fs::write(lint_docs_path(), buf)?; } Ok(()) } fn lint_groups(buf: &mut String) -> anyhow::Result<()> { let (max_name_len, max_desc_len) = cargo::lints::LINT_GROUPS.iter().filter(|g| !g.hidden).fold( (0, 0), |(max_name_len, max_desc_len), group| { // We add 9 to account for the "cargo::" prefix and backticks let name_len = group.name.chars().count() + 9; let desc_len = group.desc.chars().count(); (max_name_len.max(name_len), max_desc_len.max(desc_len)) }, ); let default_level_len = "Default level".chars().count(); writeln!(buf, "\n")?; writeln!( buf, "| {: std::fmt::Result { writeln!(buf, "## `{}`", lint.name)?; writeln!(buf, "Group: `{}`", lint.primary_group.name)?; writeln!(buf)?; writeln!(buf, "Level: `{}`", lint.primary_group.default_level)?; if let Some(msrv) = &lint.msrv { writeln!(buf)?; writeln!(buf, "MSRV: `{msrv}`")?; } writeln!(buf, "{}\n", lint.docs.as_ref().unwrap()) } fn add_level_section(level: LintLevel, lint_names: &[&str], buf: &mut String) -> std::fmt::Result { let title = match level { LintLevel::Allow => "Allowed-by-default", LintLevel::Warn => "Warn-by-default", LintLevel::Deny => "Deny-by-default", LintLevel::Forbid => "Forbid-by-default", }; writeln!(buf, "## {title}\n")?; writeln!( buf, "These lints are all set to the '{}' level by default.", level )?; for name in lint_names { writeln!(buf, "- [`{}`](#{})", name, name)?; } writeln!(buf)?; Ok(()) } fn lint_docs_path() -> PathBuf { let pkg_root = env!("CARGO_MANIFEST_DIR"); let ws_root = PathBuf::from(format!("{pkg_root}/../..")); let path = { let path = ws_root.join("src/doc/src/reference/lints.md"); path.canonicalize().unwrap_or(path) }; path } ================================================ FILE: crates/xtask-spellcheck/Cargo.toml ================================================ [package] name = "xtask-spellcheck" version = "0.0.0" edition.workspace = true publish = false [dependencies] anyhow.workspace = true cargo_metadata.workspace = true cargo-util.workspace = true clap.workspace = true semver.workspace = true [lints] workspace = true ================================================ FILE: crates/xtask-spellcheck/src/main.rs ================================================ #![allow(clippy::disallowed_methods)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] use anyhow::Result; use cargo_metadata::{Metadata, MetadataCommand}; use clap::{Arg, ArgAction}; use semver::Version; use std::{ env, io, path::{Path, PathBuf}, process::Command, }; const BIN_NAME: &str = "typos"; const PKG_NAME: &str = "typos-cli"; fn main() -> anyhow::Result<()> { let cli = cli(); exec(&cli.get_matches())?; Ok(()) } pub fn cli() -> clap::Command { clap::Command::new("xtask-spellcheck") .arg( Arg::new("color") .long("color") .help("Coloring: auto, always, never") .action(ArgAction::Set) .value_name("WHEN") .global(true), ) .arg( Arg::new("quiet") .long("quiet") .short('q') .help("Do not print cargo log messages") .action(ArgAction::SetTrue) .global(true), ) .arg( Arg::new("verbose") .long("verbose") .short('v') .help("Use verbose output (-vv very verbose/build.rs output)") .action(ArgAction::Count) .global(true), ) .arg( Arg::new("write-changes") .long("write-changes") .short('w') .help("Write fixes out") .action(ArgAction::SetTrue) .global(true), ) } pub fn exec(matches: &clap::ArgMatches) -> Result<()> { let mut args = vec![]; match matches.get_one::("color") { Some(c) if matches!(c.as_str(), "auto" | "always" | "never") => { args.push("--color"); args.push(c); } Some(c) => { anyhow::bail!( "argument for --color must be auto, always, or \ never, but found `{}`", c ); } _ => {} } if matches.get_flag("quiet") { args.push("--quiet"); } let verbose_count = matches.get_count("verbose"); for _ in 0..verbose_count { args.push("--verbose"); } if matches.get_flag("write-changes") { args.push("--write-changes"); } let metadata = MetadataCommand::new() .exec() .expect("cargo_metadata failed"); let required_version = extract_workflow_typos_version(&metadata)?; let outdir = metadata .build_directory .unwrap_or_else(|| metadata.target_directory) .as_std_path() .join("tmp"); let workspace_root = metadata.workspace_root.as_path().as_std_path(); let bin_path = crate::ensure_version_or_cargo_install(&outdir, required_version)?; eprintln!("running {BIN_NAME}"); Command::new(bin_path) .current_dir(workspace_root) .args(args) .status()?; Ok(()) } fn extract_workflow_typos_version(metadata: &Metadata) -> anyhow::Result { let ws_root = metadata.workspace_root.as_path().as_std_path(); let workflow_path = ws_root.join(".github").join("workflows").join("main.yml"); let file_content = std::fs::read_to_string(workflow_path)?; extract_typos_version_from_content(&file_content) } fn extract_typos_version_from_content(file_content: &str) -> anyhow::Result { file_content .lines() .find_map(|line| { line.trim() .strip_prefix("uses: crate-ci/typos@v") .and_then(|v| Version::parse(v).ok()) }) .ok_or_else(|| anyhow::anyhow!("Could not find typos version in workflow")) } /// If the given executable is installed with the given version, use that, /// otherwise install via cargo. pub fn ensure_version_or_cargo_install( build_dir: &Path, required_version: Version, ) -> io::Result { // Check if the user has a sufficient version already installed let bin_path = PathBuf::from(BIN_NAME).with_extension(env::consts::EXE_EXTENSION); if let Some(user_version) = get_typos_version(&bin_path) { if user_version >= required_version { return Ok(bin_path); } } let tool_root_dir = build_dir.join("misc-tools"); let tool_bin_dir = tool_root_dir.join("bin"); let bin_path = tool_bin_dir .join(BIN_NAME) .with_extension(env::consts::EXE_EXTENSION); // Check if we have already installed sufficient version if let Some(misc_tools_version) = get_typos_version(&bin_path) { if misc_tools_version >= required_version { return Ok(bin_path); } } eprintln!("required `typos` version ({required_version}) not found, building from source"); let mut cmd = Command::new("cargo"); // use --force to ensure that if the required version is bumped, we update it. cmd.args(["install", "--locked", "--force", "--quiet"]) .arg("--root") .arg(&tool_root_dir) // use --target-dir to ensure we have a build cache so repeated invocations aren't slow. .arg("--target-dir") .arg(tool_root_dir.join("target")) .arg(format!("{PKG_NAME}@{required_version}")) // modify PATH so that cargo doesn't print a warning telling the user to modify the path. .env( "PATH", env::join_paths( env::split_paths(&env::var("PATH").unwrap()) .chain(std::iter::once(tool_bin_dir.clone())), ) .expect("build dir contains invalid char"), ); let cargo_exit_code = cmd.spawn()?.wait()?; if !cargo_exit_code.success() { return Err(io::Error::other("cargo install failed")); } assert!( matches!(bin_path.try_exists(), Ok(true)), "cargo install did not produce the expected binary" ); eprintln!("finished {BIN_NAME}"); Ok(bin_path) } fn get_typos_version(bin: &PathBuf) -> Option { // ignore the process exit code here and instead just let the version number check fail if let Ok(output) = Command::new(&bin).arg("--version").output() && let Ok(s) = String::from_utf8(output.stdout) && let Some(version_str) = s.trim().split_whitespace().last() { Version::parse(version_str).ok() } else { None } } ================================================ FILE: crates/xtask-stale-label/Cargo.toml ================================================ [package] name = "xtask-stale-label" version = "0.0.0" edition.workspace = true publish = false [dependencies] toml_edit.workspace = true [lints] workspace = true ================================================ FILE: crates/xtask-stale-label/src/main.rs ================================================ //! ```text //! NAME //! stale-label //! //! SYNOPSIS //! stale-label //! //! DESCRIPTION //! Detect stale paths in autolabel definitions in triagebot.toml. //! Probably autofix them in the future. //! ``` #![allow(clippy::print_stderr)] use std::fmt::Write as _; use std::path::PathBuf; use std::process; use toml_edit::DocumentMut; fn main() { let pkg_root = std::env!("CARGO_MANIFEST_DIR"); let ws_root = PathBuf::from(format!("{pkg_root}/../..")); let path = { let path = ws_root.join("triagebot.toml"); path.canonicalize().unwrap_or(path) }; eprintln!("Checking file {path:?}\n"); let mut failed = 0; let mut passed = 0; let toml = std::fs::read_to_string(path).expect("read from file"); let doc = toml.parse::().expect("a toml"); let autolabel = doc["autolabel"].as_table().expect("a toml table"); for (label, value) in autolabel.iter() { let Some(trigger_files) = value.get("trigger_files") else { continue; }; let trigger_files = trigger_files.as_array().expect("an array"); let missing_files: Vec<_> = trigger_files .iter() // Hey TOML content is strict UTF-8. .map(|v| v.as_str().unwrap()) .filter(|f| { // triagebot checks with `starts_with` only. // See https://github.com/rust-lang/triagebot/blob/0e4b48ca86ffede9cc70fb1611e658e4d013bce2/src/handlers/autolabel.rs#L45 let path = ws_root.join(f); if path.exists() { return false; } let Some(mut read_dir) = path.parent().and_then(|p| p.read_dir().ok()) else { return true; }; !read_dir.any(|e| { e.unwrap() .path() .strip_prefix(&ws_root) .unwrap() .to_str() .unwrap() .starts_with(f) }) }) .collect(); failed += missing_files.len(); passed += trigger_files.len() - missing_files.len(); if missing_files.is_empty() { continue; } let mut msg = String::new(); writeln!( &mut msg, "missing files defined in `autolabel.{label}.trigger_files`:" ) .unwrap(); for f in missing_files.iter() { writeln!(&mut msg, "\t {f}").unwrap(); } eprintln!("{msg}"); } let result = if failed == 0 { "ok" } else { "FAILED" }; eprintln!("test result: {result}. {passed} passed; {failed} failed;"); if failed > 0 { process::exit(1); } } ================================================ FILE: credential/README.md ================================================ # Cargo Credential Packages This directory contains Cargo packages for handling storage of tokens in a secure manner. `cargo-credential` is a generic library to assist writing a credential process. The other directories contain implementations that integrate with specific credential systems. ================================================ FILE: credential/cargo-credential/Cargo.toml ================================================ [package] name = "cargo-credential" version = "0.4.10" rust-version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true description = "A library to assist writing Cargo credential helpers." [dependencies] anyhow.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true time.workspace = true [target.'cfg(unix)'.dependencies] libc.workspace = true [target.'cfg(windows)'.dependencies] windows-sys = { workspace = true, features = ["Win32_System_Console", "Win32_Foundation"] } [dev-dependencies] snapbox = { workspace = true, features = ["examples"] } [lints] workspace = true ================================================ FILE: credential/cargo-credential/README.md ================================================ # cargo-credential This package is a library to assist writing a Cargo credential helper, which provides an interface to store tokens for authorizing access to a registry such as https://crates.io/. Documentation about credential processes may be found at https://doc.rust-lang.org/nightly/cargo/reference/credential-provider-protocol.html Example implementations may be found at https://github.com/rust-lang/cargo/tree/master/credential > This crate is maintained by the Cargo team for use by the wider > ecosystem. This crate follows semver compatibility for its APIs. ## Usage Create a Cargo project with this as a dependency: ```toml # Add this to your Cargo.toml: [dependencies] cargo-credential = "0.4" ``` And then include a `main.rs` binary which implements the `Credential` trait, and calls the `main` function which will call the appropriate method of the trait: ```rust // src/main.rs use cargo_credential::{Credential, Error}; struct MyCredential; impl Credential for MyCredential { /// implement trait methods here... } fn main() { cargo_credential::main(MyCredential); } ``` ================================================ FILE: credential/cargo-credential/examples/file-provider.rs ================================================ //! Example credential provider that stores credentials in a JSON file. //! This is not secure use cargo_credential::{ Action, CacheControl, Credential, CredentialResponse, RegistryInfo, Secret, }; use std::{collections::HashMap, fs::File, io::ErrorKind}; type Error = Box; struct FileCredential; impl Credential for FileCredential { fn perform( &self, registry: &RegistryInfo<'_>, action: &Action<'_>, _args: &[&str], ) -> Result { if registry.index_url != "https://github.com/rust-lang/crates.io-index" { // Restrict this provider to only work for crates.io. Cargo will skip it and attempt // another provider for any other registry. // // If a provider supports any registry, then this check should be omitted. return Err(cargo_credential::Error::UrlNotSupported); } // `Error::Other` takes a boxed `std::error::Error` type that causes Cargo to show the error. let mut creds = FileCredential::read().map_err(cargo_credential::Error::Other)?; match action { Action::Get(_) => { // Cargo requested a token, look it up. if let Some(token) = creds.get(registry.index_url) { Ok(CredentialResponse::Get { token: token.clone(), cache: CacheControl::Session, operation_independent: true, }) } else { // Credential providers should respond with `NotFound` when a credential can not be // found, allowing Cargo to attempt another provider. Err(cargo_credential::Error::NotFound) } } Action::Login(login_options) => { // The token for `cargo login` can come from the `login_options` parameter or i // interactively reading from stdin. // // `cargo_credential::read_token` automatically handles this. let token = cargo_credential::read_token(login_options, registry)?; creds.insert(registry.index_url.to_string(), token); FileCredential::write(&creds).map_err(cargo_credential::Error::Other)?; // Credentials were successfully stored. Ok(CredentialResponse::Login) } Action::Logout => { if creds.remove(registry.index_url).is_none() { // If the user attempts to log out from a registry that has no credentials // stored, then NotFound is the appropriate error. Err(cargo_credential::Error::NotFound) } else { // Credentials were successfully erased. Ok(CredentialResponse::Logout) } } // If a credential provider doesn't support a given operation, it should respond with `OperationNotSupported`. _ => Err(cargo_credential::Error::OperationNotSupported), } } } impl FileCredential { fn read() -> Result>, Error> { match File::open("cargo-credentials.json") { Ok(f) => Ok(serde_json::from_reader(f)?), Err(e) if e.kind() == ErrorKind::NotFound => Ok(HashMap::new()), Err(e) => Err(e)?, } } fn write(value: &HashMap>) -> Result<(), Error> { let file = File::create("cargo-credentials.json")?; Ok(serde_json::to_writer_pretty(file, value)?) } } fn main() { cargo_credential::main(FileCredential); } ================================================ FILE: credential/cargo-credential/examples/stdout-redirected.rs ================================================ //! Provider used for testing redirection of stdout. #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] use cargo_credential::{Action, Credential, CredentialResponse, Error, RegistryInfo}; struct MyCredential; impl Credential for MyCredential { fn perform( &self, _registry: &RegistryInfo<'_>, _action: &Action<'_>, _args: &[&str], ) -> Result { // Informational messages should be sent on stderr. eprintln!("message on stderr should be sent to the parent process"); // Reading from stdin and writing to stdout will go to the attached console (tty). println!("message from test credential provider"); Err(Error::OperationNotSupported) } } fn main() { cargo_credential::main(MyCredential); } ================================================ FILE: credential/cargo-credential/src/error.rs ================================================ use serde::{Deserialize, Serialize}; use std::error::Error as StdError; use thiserror::Error as ThisError; /// Credential provider error type. /// /// `UrlNotSupported` and `NotFound` errors both cause Cargo /// to attempt another provider, if one is available. The other /// variants are fatal. /// /// Note: Do not add a tuple variant, as it cannot be serialized. #[derive(Serialize, Deserialize, ThisError, Debug)] #[serde(rename_all = "kebab-case", tag = "kind")] #[non_exhaustive] pub enum Error { /// Registry URL is not supported. This should be used if /// the provider only works for some registries. Cargo will /// try another provider, if available #[error("registry not supported")] UrlNotSupported, /// Credentials could not be found. Cargo will try another /// provider, if available #[error("credential not found")] NotFound, /// The provider doesn't support this operation, such as /// a provider that can't support 'login' / 'logout' #[error("requested operation not supported")] OperationNotSupported, /// The provider failed to perform the operation. Other /// providers will not be attempted #[error(transparent)] #[serde(with = "error_serialize")] Other(Box), /// A new variant was added to this enum since Cargo was built #[error("unknown error kind; try updating Cargo?")] #[serde(other)] Unknown, } impl From for Error { fn from(message: String) -> Self { Box::new(StringTypedError { message, source: None, }) .into() } } impl From<&str> for Error { fn from(err: &str) -> Self { err.to_string().into() } } impl From for Error { fn from(value: anyhow::Error) -> Self { let mut prev = None; for e in value.chain().rev() { prev = Some(Box::new(StringTypedError { message: e.to_string(), source: prev, })); } Error::Other(prev.unwrap()) } } impl From> for Error { fn from(value: Box) -> Self { Error::Other(value) } } /// String-based error type with an optional source #[derive(Debug)] struct StringTypedError { message: String, source: Option>, } impl StdError for StringTypedError { fn source(&self) -> Option<&(dyn StdError + 'static)> { self.source.as_ref().map(|err| err as &dyn StdError) } } impl std::fmt::Display for StringTypedError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.message.fmt(f) } } /// Serializer / deserializer for any boxed error. /// The string representation of the error, and its `source` chain can roundtrip across /// the serialization. The actual types are lost (downcast will not work). mod error_serialize { use std::error::Error as StdError; use std::ops::Deref; use serde::{Deserialize, Deserializer, Serializer, ser::SerializeStruct}; use crate::error::StringTypedError; pub fn serialize( e: &Box, serializer: S, ) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("StringTypedError", 2)?; state.serialize_field("message", &format!("{}", e))?; // Serialize the source error chain recursively let mut current_source: &dyn StdError = e.deref(); let mut sources = Vec::new(); while let Some(err) = current_source.source() { sources.push(err.to_string()); current_source = err; } state.serialize_field("caused-by", &sources)?; state.end() } pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] struct ErrorData { message: String, caused_by: Option>, } let data = ErrorData::deserialize(deserializer)?; let mut prev = None; if let Some(source) = data.caused_by { for e in source.into_iter().rev() { prev = Some(Box::new(StringTypedError { message: e, source: prev, })); } } let e = Box::new(StringTypedError { message: data.message, source: prev, }); Ok(e) } } #[cfg(test)] mod tests { use super::Error; #[test] pub fn unknown_kind() { let json = r#"{ "kind": "unexpected-kind", "unexpected-content": "test" }"#; let e: Error = serde_json::from_str(&json).unwrap(); assert!(matches!(e, Error::Unknown)); } #[test] pub fn roundtrip() { // Construct an error with context let e = anyhow::anyhow!("E1").context("E2").context("E3"); // Convert to a string with contexts. let s1 = format!("{:?}", e); // Convert the error into an `Error` let e: Error = e.into(); // Convert that error into JSON let json = serde_json::to_string_pretty(&e).unwrap(); // Convert that error back to anyhow let e: anyhow::Error = e.into(); let s2 = format!("{:?}", e); assert_eq!(s1, s2); // Convert the error back from JSON let e: Error = serde_json::from_str(&json).unwrap(); // Convert to back to anyhow let e: anyhow::Error = e.into(); let s3 = format!("{:?}", e); assert_eq!(s2, s3); assert_eq!( r#"{ "kind": "other", "message": "E3", "caused-by": [ "E2", "E1" ] }"#, json ); } } ================================================ FILE: credential/cargo-credential/src/lib.rs ================================================ //! Helper library for writing Cargo credential providers. //! //! A credential process should have a `struct` that implements the `Credential` trait. //! The `main` function should be called with an instance of that struct, such as: //! //! ```rust,ignore //! fn main() { //! cargo_credential::main(MyCredential); //! } //! ``` //! //! While in the `perform` function, stdin and stdout will be re-attached to the //! active console. This allows credential providers to be interactive if necessary. //! //! > This crate is maintained by the Cargo team for use by the wider //! > ecosystem. This crate follows semver compatibility for its APIs. //! //! ## Error handling //! ### [`Error::UrlNotSupported`] //! A credential provider may only support some registry URLs. If this is the case //! and an unsupported index URL is passed to the provider, it should respond with //! [`Error::UrlNotSupported`]. Other credential providers may be attempted by Cargo. //! //! ### [`Error::NotFound`] //! When attempting an [`Action::Get`] or [`Action::Logout`], if a credential can not //! be found, the provider should respond with [`Error::NotFound`]. Other credential //! providers may be attempted by Cargo. //! //! ### [`Error::OperationNotSupported`] //! A credential provider might not support all operations. For example if the provider //! only supports [`Action::Get`], [`Error::OperationNotSupported`] should be returned //! for all other requests. //! //! ### [`Error::Other`] //! All other errors go here. The error will be shown to the user in Cargo, including //! the full error chain using [`std::error::Error::source`]. //! //! ## Example //! ```rust,ignore #![doc = include_str!("../examples/file-provider.rs")] //! ``` #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] use serde::{Deserialize, Serialize}; use std::{fmt::Display, io}; use time::OffsetDateTime; mod error; mod secret; mod stdio; pub use error::Error; pub use secret::Secret; use stdio::stdin_stdout_to_console; /// Message sent by the credential helper on startup #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct CredentialHello { // Protocol versions supported by the credential process. pub v: Vec, } /// Credential provider that doesn't support any registries. pub struct UnsupportedCredential; impl Credential for UnsupportedCredential { fn perform( &self, _registry: &RegistryInfo<'_>, _action: &Action<'_>, _args: &[&str], ) -> Result { Err(Error::UrlNotSupported) } } /// Message sent by Cargo to the credential helper after the hello #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct CredentialRequest<'a> { // Cargo will respond with the highest common protocol supported by both. pub v: u32, #[serde(borrow)] pub registry: RegistryInfo<'a>, #[serde(borrow, flatten)] pub action: Action<'a>, /// Additional command-line arguments passed to the credential provider. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub args: Vec<&'a str>, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct RegistryInfo<'a> { /// Registry index url pub index_url: &'a str, /// Name of the registry in configuration. May not be available. /// The crates.io registry will be `crates-io` (`CRATES_IO_REGISTRY`). #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<&'a str>, /// Headers from attempting to access a registry that resulted in a HTTP 401. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub headers: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[non_exhaustive] #[serde(tag = "kind", rename_all = "kebab-case")] pub enum Action<'a> { #[serde(borrow)] Get(Operation<'a>), Login(LoginOptions<'a>), Logout, #[serde(other)] Unknown, } impl<'a> Display for Action<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Action::Get(_) => f.write_str("get"), Action::Login(_) => f.write_str("login"), Action::Logout => f.write_str("logout"), Action::Unknown => f.write_str(""), } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct LoginOptions<'a> { /// Token passed on the command line via --token or from stdin #[serde(skip_serializing_if = "Option::is_none")] pub token: Option>, /// Optional URL that the user can visit to log in to the registry #[serde(skip_serializing_if = "Option::is_none")] pub login_url: Option<&'a str>, } /// A record of what kind of operation is happening that we should generate a token for. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[non_exhaustive] #[serde(tag = "operation", rename_all = "kebab-case")] pub enum Operation<'a> { /// The user is attempting to fetch a crate. Read, /// The user is attempting to publish a crate. Publish { /// The name of the crate name: &'a str, /// The version of the crate vers: &'a str, /// The checksum of the crate file being uploaded cksum: &'a str, }, /// The user is attempting to yank a crate. Yank { /// The name of the crate name: &'a str, /// The version of the crate vers: &'a str, }, /// The user is attempting to unyank a crate. Unyank { /// The name of the crate name: &'a str, /// The version of the crate vers: &'a str, }, /// The user is attempting to modify the owners of a crate. Owners { /// The name of the crate name: &'a str, }, #[serde(other)] Unknown, } /// Message sent by the credential helper #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(tag = "kind", rename_all = "kebab-case")] #[non_exhaustive] pub enum CredentialResponse { Get { token: Secret, #[serde(flatten)] cache: CacheControl, operation_independent: bool, }, Login, Logout, #[serde(other)] Unknown, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(tag = "cache", rename_all = "kebab-case")] #[non_exhaustive] pub enum CacheControl { /// Do not cache this result. Never, /// Cache this result and use it for subsequent requests in the current Cargo invocation until the specified time. Expires { #[serde(with = "time::serde::timestamp")] expiration: OffsetDateTime, }, /// Cache this result and use it for all subsequent requests in the current Cargo invocation. Session, #[serde(other)] Unknown, } /// Credential process JSON protocol version. /// /// If the protocol needs to make /// a breaking change, a new protocol version should be defined (`PROTOCOL_VERSION_2`). /// This library should offer support for both protocols if possible, by signaling /// in the `CredentialHello` message. Cargo will then choose which protocol to use, /// or it will error if there are no common protocol versions available. pub const PROTOCOL_VERSION_1: u32 = 1; pub trait Credential { /// Retrieves a token for the given registry. fn perform( &self, registry: &RegistryInfo<'_>, action: &Action<'_>, args: &[&str], ) -> Result; } /// Runs the credential interaction pub fn main(credential: impl Credential) { let result = doit(credential).map_err(|e| Error::Other(e)); if result.is_err() { serde_json::to_writer(std::io::stdout(), &result) .expect("failed to serialize credential provider error"); println!(); } } fn doit( credential: impl Credential, ) -> Result<(), Box> { let hello = CredentialHello { v: vec![PROTOCOL_VERSION_1], }; serde_json::to_writer(std::io::stdout(), &hello)?; println!(); loop { let mut buffer = String::new(); let len = std::io::stdin().read_line(&mut buffer)?; if len == 0 { return Ok(()); } let request = deserialize_request(&buffer)?; let response = stdin_stdout_to_console(|| { credential.perform(&request.registry, &request.action, &request.args) })?; serde_json::to_writer(std::io::stdout(), &response)?; println!(); } } /// Deserialize a request from Cargo. fn deserialize_request( value: &str, ) -> Result, Box> { let request: CredentialRequest<'_> = serde_json::from_str(&value)?; if request.v != PROTOCOL_VERSION_1 { return Err(format!("unsupported protocol version {}", request.v).into()); } Ok(request) } /// Read a line of text from stdin. pub fn read_line() -> Result { let mut buf = String::new(); io::stdin().read_line(&mut buf)?; Ok(buf.trim().to_string()) } /// Prompt the user for a token. pub fn read_token( login_options: &LoginOptions<'_>, registry: &RegistryInfo<'_>, ) -> Result, Error> { if let Some(token) = &login_options.token { return Ok(token.to_owned()); } if let Some(url) = login_options.login_url { eprintln!("please paste the token found on {url} below"); } else if let Some(name) = registry.name { eprintln!("please paste the token for {name} below"); } else { eprintln!("please paste the token for {} below", registry.index_url); } Ok(Secret::from(read_line().map_err(Box::new)?)) } #[cfg(test)] mod tests { use super::*; #[test] fn unsupported_version() { // This shouldn't ever happen in practice, since the credential provider signals to Cargo which // protocol versions it supports, and Cargo should only attempt to use one of those. let msg = r#"{"v":999, "registry": {"index-url":""}, "args":[], "kind": "unexpected"}"#; assert_eq!( "unsupported protocol version 999", deserialize_request(msg).unwrap_err().to_string() ); } #[test] fn cache_control() { let cc = CacheControl::Expires { expiration: OffsetDateTime::from_unix_timestamp(1693928537).unwrap(), }; let json = serde_json::to_string(&cc).unwrap(); assert_eq!(json, r#"{"cache":"expires","expiration":1693928537}"#); let cc = CacheControl::Session; let json = serde_json::to_string(&cc).unwrap(); assert_eq!(json, r#"{"cache":"session"}"#); let cc: CacheControl = serde_json::from_str(r#"{"cache":"unknown-kind"}"#).unwrap(); assert_eq!(cc, CacheControl::Unknown); assert_eq!( "missing field `expiration`", serde_json::from_str::(r#"{"cache":"expires"}"#) .unwrap_err() .to_string() ); } #[test] fn credential_response() { let cr = CredentialResponse::Get { cache: CacheControl::Never, operation_independent: true, token: Secret::from("value".to_string()), }; let json = serde_json::to_string(&cr).unwrap(); assert_eq!( json, r#"{"kind":"get","token":"value","cache":"never","operation_independent":true}"# ); let cr = CredentialResponse::Login; let json = serde_json::to_string(&cr).unwrap(); assert_eq!(json, r#"{"kind":"login"}"#); let cr: CredentialResponse = serde_json::from_str(r#"{"kind":"unknown-kind","extra-data":true}"#).unwrap(); assert_eq!(cr, CredentialResponse::Unknown); let cr: CredentialResponse = serde_json::from_str(r#"{"kind":"login","extra-data":true}"#).unwrap(); assert_eq!(cr, CredentialResponse::Login); let cr: CredentialResponse = serde_json::from_str(r#"{"kind":"get","token":"value","cache":"never","operation_independent":true,"extra-field-ignored":123}"#).unwrap(); assert_eq!( cr, CredentialResponse::Get { cache: CacheControl::Never, operation_independent: true, token: Secret::from("value".to_string()) } ); } #[test] fn credential_request() { let get_oweners = CredentialRequest { v: PROTOCOL_VERSION_1, args: vec![], registry: RegistryInfo { index_url: "url", name: None, headers: vec![], }, action: Action::Get(Operation::Owners { name: "pkg" }), }; let json = serde_json::to_string(&get_oweners).unwrap(); assert_eq!( json, r#"{"v":1,"registry":{"index-url":"url"},"kind":"get","operation":"owners","name":"pkg"}"# ); let cr: CredentialRequest<'_> = serde_json::from_str(r#"{"extra-1":true,"v":1,"registry":{"index-url":"url","extra-2":true},"kind":"get","operation":"owners","name":"pkg","args":[]}"#).unwrap(); assert_eq!(cr, get_oweners); } #[test] fn credential_request_logout() { let unknown = CredentialRequest { v: PROTOCOL_VERSION_1, args: vec![], registry: RegistryInfo { index_url: "url", name: None, headers: vec![], }, action: Action::Logout, }; let cr: CredentialRequest<'_> = serde_json::from_str( r#"{"v":1,"registry":{"index-url":"url"},"kind":"logout","extra-1":true,"args":[]}"#, ) .unwrap(); assert_eq!(cr, unknown); } #[test] fn credential_request_unknown() { let unknown = CredentialRequest { v: PROTOCOL_VERSION_1, args: vec![], registry: RegistryInfo { index_url: "", name: None, headers: vec![], }, action: Action::Unknown, }; let cr: CredentialRequest<'_> = serde_json::from_str( r#"{"v":1,"registry":{"index-url":""},"kind":"unexpected-1","extra-1":true,"args":[]}"#, ) .unwrap(); assert_eq!(cr, unknown); } } ================================================ FILE: credential/cargo-credential/src/secret.rs ================================================ use std::fmt; use std::ops::Deref; use serde::{Deserialize, Serialize}; /// A wrapper for values that should not be printed. /// /// This type does not implement `Display`, and has a `Debug` impl that hides /// the contained value. /// /// ``` /// # use cargo_credential::Secret; /// let token = Secret::from("super secret string"); /// assert_eq!(format!("{:?}", token), "Secret { inner: \"REDACTED\" }"); /// ``` /// /// Currently, we write a borrowed `Secret` as `Secret<&T>`. /// The [`as_deref`](Secret::as_deref) and [`to_owned`](Secret::to_owned) methods can /// be used to convert back and forth between `Secret` and `Secret<&str>`. #[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct Secret { inner: T, } impl Secret { /// Unwraps the contained value. /// /// Use of this method marks the boundary of where the contained value is /// hidden. pub fn expose(self) -> T { self.inner } /// Converts a `Secret` to a `Secret<&T::Target>`. /// ``` /// # use cargo_credential::Secret; /// let owned: Secret = Secret::from(String::from("token")); /// let borrowed: Secret<&str> = owned.as_deref(); /// ``` pub fn as_deref(&self) -> Secret<&::Target> where T: Deref, { Secret::from(self.inner.deref()) } /// Converts a `Secret` to a `Secret<&T>`. pub fn as_ref(&self) -> Secret<&T> { Secret::from(&self.inner) } /// Converts a `Secret` to a `Secret` by applying `f` to the contained value. pub fn map(self, f: F) -> Secret where F: FnOnce(T) -> U, { Secret::from(f(self.inner)) } } impl Secret<&T> { /// Converts a `Secret` containing a borrowed type to a `Secret` containing the /// corresponding owned type. /// ``` /// # use cargo_credential::Secret; /// let borrowed: Secret<&str> = Secret::from("token"); /// let owned: Secret = borrowed.to_owned(); /// ``` pub fn to_owned(&self) -> Secret<::Owned> { Secret::from(self.inner.to_owned()) } } impl Secret> { /// Converts a `Secret>` to a `Result, E>`. pub fn transpose(self) -> Result, E> { self.inner.map(|v| Secret::from(v)) } } impl> Secret { /// Checks if the contained value is empty. pub fn is_empty(&self) -> bool { self.inner.as_ref().is_empty() } } impl From for Secret { fn from(inner: T) -> Self { Self { inner } } } impl fmt::Debug for Secret { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Secret") .field("inner", &"REDACTED") .finish() } } ================================================ FILE: credential/cargo-credential/src/stdio.rs ================================================ use std::{fs::File, io::Error}; /// Reset stdin and stdout to the attached console / tty for the duration of the closure. /// If no console is available, stdin and stdout will be redirected to null. pub fn stdin_stdout_to_console(f: F) -> Result where F: FnOnce() -> T, { let open_write = |f| std::fs::OpenOptions::new().write(true).open(f); let mut stdin = File::open(imp::IN_DEVICE).or_else(|_| File::open(imp::NULL_DEVICE))?; let mut stdout = open_write(imp::OUT_DEVICE).or_else(|_| open_write(imp::NULL_DEVICE))?; let _stdin_guard = imp::ReplacementGuard::new(Stdio::Stdin, &mut stdin)?; let _stdout_guard = imp::ReplacementGuard::new(Stdio::Stdout, &mut stdout)?; Ok(f()) } enum Stdio { Stdin, Stdout, } #[cfg(windows)] mod imp { use super::Stdio; use std::{fs::File, io::Error, os::windows::prelude::AsRawHandle}; use windows_sys::Win32::{ Foundation::{HANDLE, INVALID_HANDLE_VALUE}, System::Console::{ GetStdHandle, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, SetStdHandle, }, }; pub const OUT_DEVICE: &str = "CONOUT$"; pub const IN_DEVICE: &str = "CONIN$"; pub const NULL_DEVICE: &str = "NUL"; /// Restores previous stdio when dropped. pub struct ReplacementGuard { std_handle: STD_HANDLE, previous: HANDLE, } impl ReplacementGuard { pub(super) fn new(stdio: Stdio, replacement: &mut File) -> Result { let std_handle = match stdio { Stdio::Stdin => STD_INPUT_HANDLE, Stdio::Stdout => STD_OUTPUT_HANDLE, }; let previous; unsafe { // Make a copy of the current handle previous = GetStdHandle(std_handle); if previous == INVALID_HANDLE_VALUE { return Err(std::io::Error::last_os_error()); } // Replace stdin with the replacement handle if SetStdHandle(std_handle, replacement.as_raw_handle() as HANDLE) == 0 { return Err(std::io::Error::last_os_error()); } } Ok(ReplacementGuard { previous, std_handle, }) } } impl Drop for ReplacementGuard { fn drop(&mut self) { unsafe { // Put previous handle back in to stdin SetStdHandle(self.std_handle, self.previous); } } } } #[cfg(unix)] mod imp { use super::Stdio; use libc::{STDIN_FILENO, STDOUT_FILENO, close, dup, dup2}; use std::{fs::File, io::Error, os::fd::AsRawFd}; pub const IN_DEVICE: &str = "/dev/tty"; pub const OUT_DEVICE: &str = "/dev/tty"; pub const NULL_DEVICE: &str = "/dev/null"; /// Restores previous stdio when dropped. pub struct ReplacementGuard { std_fileno: i32, previous: i32, } impl ReplacementGuard { pub(super) fn new(stdio: Stdio, replacement: &mut File) -> Result { let std_fileno = match stdio { Stdio::Stdin => STDIN_FILENO, Stdio::Stdout => STDOUT_FILENO, }; let previous; unsafe { // Duplicate the existing stdin file to a new descriptor previous = dup(std_fileno); if previous == -1 { return Err(std::io::Error::last_os_error()); } // Replace stdin with the replacement file if dup2(replacement.as_raw_fd(), std_fileno) == -1 { return Err(std::io::Error::last_os_error()); } } Ok(ReplacementGuard { previous, std_fileno, }) } } impl Drop for ReplacementGuard { fn drop(&mut self) { unsafe { // Put previous file back in to stdin dup2(self.previous, self.std_fileno); // Close the file descriptor we used as a backup close(self.previous); } } } } #[cfg(test)] mod test { use std::fs::OpenOptions; use std::io::{Seek, Write}; use super::Stdio; use super::imp::ReplacementGuard; #[test] fn stdin() { let tempdir = snapbox::dir::DirRoot::mutable_temp().unwrap(); let file = tempdir.path().unwrap().join("stdin"); let mut file = OpenOptions::new() .read(true) .write(true) .create(true) .open(file) .unwrap(); writeln!(&mut file, "hello").unwrap(); file.seek(std::io::SeekFrom::Start(0)).unwrap(); { let _guard = ReplacementGuard::new(Stdio::Stdin, &mut file).unwrap(); let line = std::io::stdin().lines().next().unwrap().unwrap(); assert_eq!(line, "hello"); } } } ================================================ FILE: credential/cargo-credential/tests/examples.rs ================================================ use std::path::Path; use snapbox::cmd::Command; use snapbox::prelude::*; #[test] fn stdout_redirected() { let bin = snapbox::cmd::compile_example("stdout-redirected", []).unwrap(); let hello = r#"{"v":[1]}"#; let get_request = r#"{"v": 1, "registry": {"index-url":"sparse+https://test/","name":"alternative"},"kind": "get","operation": "read","args": []}"#; let err_not_supported = r#"{"Err":{"kind":"operation-not-supported"}}"#; Command::new(bin) .stdin(format!("{get_request}\n")) .arg("--cargo-plugin") .assert() .stdout_eq(format!("{hello}\n{err_not_supported}\n").raw()) .stderr_eq("message on stderr should be sent to the parent process\n".raw()) .success(); } #[test] fn file_provider() { let bin = snapbox::cmd::compile_example("file-provider", []).unwrap(); let hello = r#"{"v":[1]}"#; let login_request = r#"{"v": 1,"registry": {"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind": "login","token": "s3krit","args": []}"#; let login_response = r#"{"Ok":{"kind":"login"}}"#; let get_request = r#"{"v": 1,"registry": {"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind": "get","operation": "read","args": []}"#; let get_response = r#"{"Ok":{"kind":"get","token":"s3krit","cache":"session","operation_independent":true}}"#; let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join("cargo-credential-tests"); std::fs::create_dir(&dir).unwrap(); Command::new(bin) .current_dir(&dir) .stdin(format!("{login_request}\n{get_request}\n")) .arg("--cargo-plugin") .assert() .stdout_eq(format!("{hello}\n{login_response}\n{get_response}\n").raw()) .stderr_eq("".raw()) .success(); std::fs::remove_dir_all(&dir).unwrap(); } ================================================ FILE: credential/cargo-credential-1password/Cargo.toml ================================================ [package] name = "cargo-credential-1password" version = "0.4.6" rust-version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true description = "A Cargo credential process that stores tokens in a 1password vault." [dependencies] cargo-credential.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true [lints] workspace = true ================================================ FILE: credential/cargo-credential-1password/README.md ================================================ # cargo-credential-1password A Cargo [credential provider] for [1password]. > This crate is maintained by the Cargo team as a part of an experiment around > 1password integration. We encourage people to try to use this crate in their projects and > provide feedback through [issues](https://github.com/rust-lang/cargo/issues/), but do not > guarantee long term maintenance. ## Usage `cargo-credential-1password` uses the 1password `op` CLI to store the token. You must install the `op` CLI from the [1password website](https://1password.com/downloads/command-line/). Afterward you need to configure `cargo` to use `cargo-credential-1password` as the credential provider. You can do this by adding something like the following to your [cargo config file][credential provider]: ```toml [registry] global-credential-providers = ["cargo-credential-1password --account my.1password.com"] ``` Finally, run `cargo login` to save your registry token in 1password. ## CLI Arguments `cargo-credential-1password` supports the following command-line arguments: * `--account`: The account name to use. For a list of available accounts, run `op account list`. * `--vault`: The vault name to use. For a list of available vaults, run `op vault list`. [1password]: https://1password.com/ [credential provider]: https://doc.rust-lang.org/stable/cargo/reference/registry-authentication.html ================================================ FILE: credential/cargo-credential-1password/src/main.rs ================================================ //! Cargo registry 1password credential process. //! //! > This crate is maintained by the Cargo team as a part of an experiment around //! > 1password integration. We encourage people to try to use this crate in their projects and //! > provide feedback through [issues](https://github.com/rust-lang/cargo/issues/), but do not //! > guarantee long term maintenance. #![allow(clippy::disallowed_methods)] #![allow(clippy::print_stderr)] use cargo_credential::{ Action, CacheControl, Credential, CredentialResponse, Error, RegistryInfo, Secret, }; use serde::Deserialize; use std::io::Read; use std::process::{Command, Stdio}; const CARGO_TAG: &str = "cargo-registry"; /// Implementation of 1password keychain access for Cargo registries. struct OnePasswordKeychain { account: Option, vault: Option, } /// 1password Login item type, used for the JSON output of `op item get`. #[derive(Deserialize)] struct Login { fields: Vec, } #[derive(Deserialize)] struct Field { id: String, value: Option, } /// 1password item from `op items list`. #[derive(Deserialize)] struct ListItem { id: String, urls: Vec, } #[derive(Deserialize)] struct Url { href: String, } impl OnePasswordKeychain { fn new(args: &[&str]) -> Result { let mut args = args.iter(); let mut action = false; let mut account = None; let mut vault = None; while let Some(arg) = args.next() { match *arg { "--account" => { account = Some(args.next().ok_or("--account needs an arg")?); } "--vault" => { vault = Some(args.next().ok_or("--vault needs an arg")?); } s if s.starts_with('-') => { return Err(format!("unknown option {}", s).into()); } _ => { if action { return Err("too many arguments".into()); } else { action = true; } } } } Ok(OnePasswordKeychain { account: account.map(|s| s.to_string()), vault: vault.map(|s| s.to_string()), }) } fn signin(&self) -> Result, Error> { // If there are any session env vars, we'll assume that this is the // correct account, and that the user knows what they are doing. if std::env::vars().any(|(name, _)| name.starts_with("OP_SESSION_")) { return Ok(None); } let mut cmd = Command::new("op"); cmd.args(["signin", "--raw"]); if let Some(account) = &self.account { cmd.arg("--account"); cmd.arg(account); } cmd.stdout(Stdio::piped()); let mut child = cmd .spawn() .map_err(|e| format!("failed to spawn `op`: {}", e))?; let mut buffer = String::new(); child .stdout .as_mut() .unwrap() .read_to_string(&mut buffer) .map_err(|e| format!("failed to get session from `op`: {}", e))?; if let Some(end) = buffer.find('\n') { buffer.truncate(end); } let status = child .wait() .map_err(|e| format!("failed to wait for `op`: {}", e))?; if !status.success() { return Err(format!("failed to run `op signin`: {}", status).into()); } if buffer.is_empty() { // When using CLI integration, `op signin` returns no output, // so there is no need to set the session. return Ok(None); } Ok(Some(buffer)) } fn make_cmd(&self, session: &Option, args: &[&str]) -> Command { let mut cmd = Command::new("op"); cmd.args(args); if let Some(account) = &self.account { cmd.arg("--account"); cmd.arg(account); } if let Some(vault) = &self.vault { cmd.arg("--vault"); cmd.arg(vault); } if let Some(session) = session { cmd.arg("--session"); cmd.arg(session); } cmd } fn run_cmd(&self, mut cmd: Command) -> Result { cmd.stdout(Stdio::piped()); let mut child = cmd .spawn() .map_err(|e| format!("failed to spawn `op`: {}", e))?; let mut buffer = String::new(); child .stdout .as_mut() .unwrap() .read_to_string(&mut buffer) .map_err(|e| format!("failed to read `op` output: {}", e))?; let status = child .wait() .map_err(|e| format!("failed to wait for `op`: {}", e))?; if !status.success() { return Err(format!("`op` command exit error: {}", status).into()); } Ok(buffer) } fn search(&self, session: &Option, index_url: &str) -> Result, Error> { let cmd = self.make_cmd( session, &[ "items", "list", "--categories", "Login", "--tags", CARGO_TAG, "--format", "json", ], ); let buffer = self.run_cmd(cmd)?; let items: Vec = serde_json::from_str(&buffer) .map_err(|e| format!("failed to deserialize JSON from 1password list: {}", e))?; let mut matches = items .into_iter() .filter(|item| item.urls.iter().any(|url| url.href == index_url)); match matches.next() { Some(login) => { // Should this maybe just sort on `updatedAt` and return the newest one? if matches.next().is_some() { return Err(format!( "too many 1password logins match registry `{}`, \ consider deleting the excess entries", index_url ) .into()); } Ok(Some(login.id)) } None => Ok(None), } } fn modify( &self, session: &Option, id: &str, token: Secret<&str>, _name: Option<&str>, ) -> Result<(), Error> { let cmd = self.make_cmd( session, &["item", "edit", id, &format!("password={}", token.expose())], ); self.run_cmd(cmd)?; Ok(()) } fn create( &self, session: &Option, index_url: &str, token: Secret<&str>, name: Option<&str>, ) -> Result<(), Error> { let title = match name { Some(name) => format!("Cargo registry token for {}", name), None => "Cargo registry token".to_string(), }; let cmd = self.make_cmd( session, &[ "item", "create", "--category", "Login", &format!("password={}", token.expose()), &format!("url={}", index_url), "--title", &title, "--tags", CARGO_TAG, ], ); self.run_cmd(cmd)?; Ok(()) } fn get_token(&self, session: &Option, id: &str) -> Result, Error> { let cmd = self.make_cmd(session, &["item", "get", "--format=json", id]); let buffer = self.run_cmd(cmd)?; let item: Login = serde_json::from_str(&buffer) .map_err(|e| format!("failed to deserialize JSON from 1password get: {}", e))?; let password = item.fields.into_iter().find(|item| item.id == "password"); match password { Some(password) => password .value .map(Secret::from) .ok_or("missing password value for entry".into()), None => Err("could not find password field".into()), } } fn delete(&self, session: &Option, id: &str) -> Result<(), Error> { let cmd = self.make_cmd(session, &["item", "delete", id]); self.run_cmd(cmd)?; Ok(()) } } pub struct OnePasswordCredential {} impl Credential for OnePasswordCredential { fn perform( &self, registry: &RegistryInfo<'_>, action: &Action<'_>, args: &[&str], ) -> Result { let op = OnePasswordKeychain::new(args)?; match action { Action::Get(_) => { let session = op.signin()?; if let Some(id) = op.search(&session, registry.index_url)? { op.get_token(&session, &id) .map(|token| CredentialResponse::Get { token, cache: CacheControl::Session, operation_independent: true, }) } else { Err(Error::NotFound) } } Action::Login(options) => { let session = op.signin()?; // Check if an item already exists. if let Some(id) = op.search(&session, registry.index_url)? { eprintln!("note: token already exists for `{}`", registry.index_url); let token = cargo_credential::read_token(options, registry)?; op.modify(&session, &id, token.as_deref(), None)?; } else { let token = cargo_credential::read_token(options, registry)?; op.create(&session, registry.index_url, token.as_deref(), None)?; } Ok(CredentialResponse::Login) } Action::Logout => { let session = op.signin()?; // Check if an item already exists. if let Some(id) = op.search(&session, registry.index_url)? { op.delete(&session, &id)?; Ok(CredentialResponse::Logout) } else { Err(Error::NotFound) } } _ => Err(Error::OperationNotSupported), } } } fn main() { cargo_credential::main(OnePasswordCredential {}); } ================================================ FILE: credential/cargo-credential-libsecret/Cargo.toml ================================================ [package] name = "cargo-credential-libsecret" version = "0.5.7" rust-version = "1.94" # MSRV:1 edition.workspace = true license.workspace = true repository.workspace = true description = "A Cargo credential process that stores tokens with GNOME libsecret." [dependencies] anyhow.workspace = true cargo-credential.workspace = true libloading.workspace = true [lints] workspace = true ================================================ FILE: credential/cargo-credential-libsecret/README.md ================================================ # cargo-credential-libsecret This is the implementation for the Cargo credential helper for [GNOME libsecret]. See the [credential-provider] documentation for how to use this. This credential provider is built-in to cargo as `cargo:libsecret`. > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use (except as a transitive dependency). This > crate may make major changes to its APIs or be deprecated without warning. [GNOME libsecret]: https://wiki.gnome.org/Projects/Libsecret [credential-provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html ================================================ FILE: credential/cargo-credential-libsecret/src/lib.rs ================================================ //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use (except as a transitive dependency). This //! > crate may make major changes to its APIs or be deprecated without warning. #[cfg(target_os = "linux")] mod linux { //! Implementation of the libsecret credential helper. use anyhow::Context; use cargo_credential::{ Action, CacheControl, Credential, CredentialResponse, Error, RegistryInfo, Secret, read_token, }; use libloading::{Library, Symbol}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int}; use std::ptr::{null, null_mut}; #[allow(non_camel_case_types)] type gchar = c_char; #[allow(non_camel_case_types)] type gboolean = c_int; #[allow(non_camel_case_types)] type gint = c_int; #[allow(non_camel_case_types)] type gpointer = *mut (); type GQuark = u32; #[repr(C)] struct GError { domain: GQuark, code: c_int, message: *mut gchar, } #[repr(C)] struct GCancellable { _private: [u8; 0], } #[repr(C)] struct SecretSchema { name: *const gchar, flags: SecretSchemaFlags, attributes: [SecretSchemaAttribute; 32], reserved: gint, reserved1: gpointer, reserved2: gpointer, reserved3: gpointer, reserved4: gpointer, reserved5: gpointer, reserved6: gpointer, reserved7: gpointer, } #[repr(C)] #[derive(Copy, Clone)] struct SecretSchemaAttribute { name: *const gchar, attr_type: SecretSchemaAttributeType, } #[repr(C)] enum SecretSchemaFlags { None = 0, } #[repr(C)] #[derive(Copy, Clone)] enum SecretSchemaAttributeType { String = 0, } type SecretPasswordStoreSync = extern "C" fn( schema: *const SecretSchema, collection: *const gchar, label: *const gchar, password: *const gchar, cancellable: *mut GCancellable, error: *mut *mut GError, ... ) -> gboolean; type SecretPasswordClearSync = extern "C" fn( schema: *const SecretSchema, cancellable: *mut GCancellable, error: *mut *mut GError, ... ) -> gboolean; type SecretPasswordLookupSync = extern "C" fn( schema: *const SecretSchema, cancellable: *mut GCancellable, error: *mut *mut GError, ... ) -> *mut gchar; pub struct LibSecretCredential { libsecret: Library, } fn label(index_url: &str) -> CString { CString::new(format!("cargo-registry:{}", index_url)).unwrap() } fn schema() -> SecretSchema { let mut attributes = [SecretSchemaAttribute { name: null(), attr_type: SecretSchemaAttributeType::String, }; 32]; attributes[0] = SecretSchemaAttribute { name: c"url".as_ptr() as *const gchar, attr_type: SecretSchemaAttributeType::String, }; SecretSchema { name: c"org.rust-lang.cargo.registry".as_ptr() as *const gchar, flags: SecretSchemaFlags::None, attributes, reserved: 0, reserved1: null_mut(), reserved2: null_mut(), reserved3: null_mut(), reserved4: null_mut(), reserved5: null_mut(), reserved6: null_mut(), reserved7: null_mut(), } } impl LibSecretCredential { pub fn new() -> Result { // Dynamically load libsecret to avoid users needing to install // additional -dev packages when building this provider. let libsecret = unsafe { Library::new("libsecret-1.so.0") }.context( "failed to load libsecret: try installing the `libsecret` \ or `libsecret-1-0` package with the system package manager", )?; Ok(Self { libsecret }) } } impl Credential for &LibSecretCredential { fn perform( &self, registry: &RegistryInfo<'_>, action: &Action<'_>, _args: &[&str], ) -> Result { let secret_password_lookup_sync: Symbol<'_, SecretPasswordLookupSync>; let secret_password_store_sync: Symbol<'_, SecretPasswordStoreSync>; let secret_password_clear_sync: Symbol<'_, SecretPasswordClearSync>; unsafe { secret_password_lookup_sync = self .libsecret .get(b"secret_password_lookup_sync\0") .map_err(Box::new)?; secret_password_store_sync = self .libsecret .get(b"secret_password_store_sync\0") .map_err(Box::new)?; secret_password_clear_sync = self .libsecret .get(b"secret_password_clear_sync\0") .map_err(Box::new)?; } let index_url_c = CString::new(registry.index_url).unwrap(); let mut error: *mut GError = null_mut(); let attr_url = c"url".as_ptr() as *const gchar; let schema = schema(); match action { cargo_credential::Action::Get(_) => unsafe { let token_c = secret_password_lookup_sync( &schema, null_mut(), &mut error, attr_url, index_url_c.as_ptr(), null() as *const gchar, ); if !error.is_null() { return Err(format!( "failed to get token: {}", CStr::from_ptr((*error).message) .to_str() .unwrap_or_default() ) .into()); } if token_c.is_null() { return Err(Error::NotFound); } let token = Secret::from( CStr::from_ptr(token_c) .to_str() .map_err(|e| format!("expected utf8 token: {}", e))? .to_string(), ); Ok(CredentialResponse::Get { token, cache: CacheControl::Session, operation_independent: true, }) }, cargo_credential::Action::Login(options) => { let label = label(registry.name.unwrap_or(registry.index_url)); let token = CString::new(read_token(options, registry)?.expose()).unwrap(); unsafe { secret_password_store_sync( &schema, c"default".as_ptr() as *const gchar, label.as_ptr(), token.as_ptr(), null_mut(), &mut error, attr_url, index_url_c.as_ptr(), null() as *const gchar, ); if !error.is_null() { return Err(format!( "failed to store token: {}", CStr::from_ptr((*error).message) .to_str() .unwrap_or_default() ) .into()); } } Ok(CredentialResponse::Login) } cargo_credential::Action::Logout => { unsafe { secret_password_clear_sync( &schema, null_mut(), &mut error, attr_url, index_url_c.as_ptr(), null() as *const gchar, ); if !error.is_null() { return Err(format!( "failed to erase token: {}", CStr::from_ptr((*error).message) .to_str() .unwrap_or_default() ) .into()); } } Ok(CredentialResponse::Logout) } _ => Err(Error::OperationNotSupported), } } } } #[cfg(not(target_os = "linux"))] pub use cargo_credential::UnsupportedCredential as LibSecretCredential; #[cfg(target_os = "linux")] pub use linux::LibSecretCredential; ================================================ FILE: credential/cargo-credential-macos-keychain/Cargo.toml ================================================ [package] name = "cargo-credential-macos-keychain" version = "0.4.22" rust-version = "1.94" # MSRV:1 edition.workspace = true license.workspace = true repository.workspace = true description = "A Cargo credential process that stores tokens in a macOS keychain." [dependencies] cargo-credential.workspace = true [target.'cfg(target_os = "macos")'.dependencies] security-framework.workspace = true [lints] workspace = true ================================================ FILE: credential/cargo-credential-macos-keychain/README.md ================================================ # cargo-credential-macos-keychain This is the implementation for the Cargo credential helper for [macOS Keychain]. See the [credential-provider] documentation for how to use this. This credential provider is built-in to cargo as `cargo:macos-keychain`. > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use (except as a transitive dependency). This > crate may make major changes to its APIs or be deprecated without warning. [macOS Keychain]: https://support.apple.com/guide/keychain-access/welcome/mac [credential-provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html ================================================ FILE: credential/cargo-credential-macos-keychain/src/lib.rs ================================================ //! Cargo registry macos keychain credential process. //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use (except as a transitive dependency). This //! > crate may make major changes to its APIs or be deprecated without warning. #![allow(clippy::print_stderr)] #[cfg(target_os = "macos")] mod macos { use cargo_credential::{ Action, CacheControl, Credential, CredentialResponse, Error, RegistryInfo, read_token, }; use security_framework::os::macos::keychain::SecKeychain; pub struct MacKeychain; /// The account name is not used. const ACCOUNT: &'static str = ""; const NOT_FOUND: i32 = -25300; // errSecItemNotFound fn registry(index_url: &str) -> String { format!("cargo-registry:{}", index_url) } impl Credential for MacKeychain { fn perform( &self, reg: &RegistryInfo<'_>, action: &Action<'_>, _args: &[&str], ) -> Result { let keychain = SecKeychain::default().unwrap(); let service_name = registry(reg.index_url); let not_found = security_framework::base::Error::from(NOT_FOUND).code(); match action { Action::Get(_) => match keychain.find_generic_password(&service_name, ACCOUNT) { Err(e) if e.code() == not_found => Err(Error::NotFound), Err(e) => Err(Box::new(e).into()), Ok((pass, _)) => { let token = String::from_utf8(pass.as_ref().to_vec()).map_err(Box::new)?; Ok(CredentialResponse::Get { token: token.into(), cache: CacheControl::Session, operation_independent: true, }) } }, Action::Login(options) => { let token = read_token(options, reg)?; match keychain.find_generic_password(&service_name, ACCOUNT) { Err(e) => { if e.code() == not_found { keychain .add_generic_password( &service_name, ACCOUNT, token.expose().as_bytes(), ) .map_err(Box::new)?; } } Ok((_, mut item)) => { item.set_password(token.expose().as_bytes()) .map_err(Box::new)?; } } Ok(CredentialResponse::Login) } Action::Logout => match keychain.find_generic_password(&service_name, ACCOUNT) { Err(e) if e.code() == not_found => Err(Error::NotFound), Err(e) => Err(Box::new(e).into()), Ok((_, item)) => { item.delete(); Ok(CredentialResponse::Logout) } }, _ => Err(Error::OperationNotSupported), } } } } #[cfg(not(target_os = "macos"))] pub use cargo_credential::UnsupportedCredential as MacKeychain; #[cfg(target_os = "macos")] pub use macos::MacKeychain; ================================================ FILE: credential/cargo-credential-wincred/Cargo.toml ================================================ [package] name = "cargo-credential-wincred" version = "0.4.22" rust-version = "1.94" # MSRV:1 edition.workspace = true license.workspace = true repository.workspace = true description = "A Cargo credential process that stores tokens with Windows Credential Manager." [dependencies] cargo-credential.workspace = true [target.'cfg(windows)'.dependencies.windows-sys] features = ["Win32_Foundation", "Win32_Security_Credentials"] workspace = true [lints] workspace = true ================================================ FILE: credential/cargo-credential-wincred/README.md ================================================ # cargo-credential-wincred This is the implementation for the Cargo credential helper for [Windows Credential Manager]. See the [credential-provider] documentation for how to use this. This credential provider is built-in to cargo as `cargo:wincred`. > This crate is maintained by the Cargo team, primarily for use by Cargo > and not intended for external use (except as a transitive dependency). This > crate may make major changes to its APIs or be deprecated without warning. [Windows Credential Manager]: https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0 [credential-provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html ================================================ FILE: credential/cargo-credential-wincred/src/lib.rs ================================================ //! Cargo registry windows credential process. //! //! > This crate is maintained by the Cargo team, primarily for use by Cargo //! > and not intended for external use (except as a transitive dependency). This //! > crate may make major changes to its APIs or be deprecated without warning. #[cfg(windows)] mod win { use cargo_credential::{Action, CacheControl, CredentialResponse, RegistryInfo, read_token}; use cargo_credential::{Credential, Error}; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use windows_sys::Win32::Foundation::ERROR_NOT_FOUND; use windows_sys::Win32::Foundation::FILETIME; use windows_sys::Win32::Foundation::TRUE; use windows_sys::Win32::Security::Credentials::CRED_PERSIST_LOCAL_MACHINE; use windows_sys::Win32::Security::Credentials::CRED_TYPE_GENERIC; use windows_sys::Win32::Security::Credentials::CREDENTIALW; use windows_sys::Win32::Security::Credentials::CredReadW; use windows_sys::Win32::Security::Credentials::CredWriteW; use windows_sys::Win32::Security::Credentials::{CredDeleteW, CredFree}; use windows_sys::core::PWSTR; pub struct WindowsCredential; /// Converts a string to a nul-terminated wide UTF-16 byte sequence. fn wstr(s: &str) -> Vec { let mut wide: Vec = OsStr::new(s).encode_wide().collect(); if wide.iter().any(|b| *b == 0) { panic!("nul byte in wide string"); } wide.push(0); wide } fn target_name(index_url: &str) -> Vec { wstr(&format!("cargo-registry:{}", index_url)) } impl Credential for WindowsCredential { fn perform( &self, registry: &RegistryInfo<'_>, action: &Action<'_>, _args: &[&str], ) -> Result { match action { Action::Get(_) => { let target_name = target_name(registry.index_url); let mut p_credential: *mut CREDENTIALW = std::ptr::null_mut() as *mut _; let bytes = unsafe { if CredReadW( target_name.as_ptr(), CRED_TYPE_GENERIC, 0, &mut p_credential as *mut _, ) != TRUE { let err = std::io::Error::last_os_error(); if err.raw_os_error() == Some(ERROR_NOT_FOUND as i32) { return Err(Error::NotFound); } return Err(Box::new(err).into()); } std::slice::from_raw_parts( (*p_credential).CredentialBlob, (*p_credential).CredentialBlobSize as usize, ) }; let token = String::from_utf8(bytes.to_vec()).map_err(Box::new); unsafe { CredFree(p_credential as *mut _) }; Ok(CredentialResponse::Get { token: token?.into(), cache: CacheControl::Session, operation_independent: true, }) } Action::Login(options) => { let token = read_token(options, registry)?.expose(); let target_name = target_name(registry.index_url); let comment = wstr("Cargo registry token"); let credential = CREDENTIALW { Flags: 0, Type: CRED_TYPE_GENERIC, TargetName: target_name.as_ptr() as PWSTR, Comment: comment.as_ptr() as PWSTR, LastWritten: FILETIME { dwLowDateTime: 0, dwHighDateTime: 0, }, CredentialBlobSize: token.len() as u32, CredentialBlob: token.as_bytes().as_ptr() as *mut u8, Persist: CRED_PERSIST_LOCAL_MACHINE, AttributeCount: 0, Attributes: std::ptr::null_mut(), TargetAlias: std::ptr::null_mut(), UserName: std::ptr::null_mut(), }; let result = unsafe { CredWriteW(&credential, 0) }; if result != TRUE { let err = std::io::Error::last_os_error(); return Err(Box::new(err).into()); } Ok(CredentialResponse::Login) } Action::Logout => { let target_name = target_name(registry.index_url); let result = unsafe { CredDeleteW(target_name.as_ptr(), CRED_TYPE_GENERIC, 0) }; if result != TRUE { let err = std::io::Error::last_os_error(); if err.raw_os_error() == Some(ERROR_NOT_FOUND as i32) { return Err(Error::NotFound); } return Err(Box::new(err).into()); } Ok(CredentialResponse::Logout) } _ => Err(Error::OperationNotSupported), } } } } #[cfg(not(windows))] pub use cargo_credential::UnsupportedCredential as WindowsCredential; #[cfg(windows)] pub use win::WindowsCredential; ================================================ FILE: deny.toml ================================================ # This template contains all of the possible sections and their default values # Note that all fields that take a lint level have these possible values: # * deny - An error will be produced and the check will fail # * warn - A warning will be produced, but the check will not fail # * allow - No warning or error will be produced, though in some cases a note # will be # The values provided in this template are the default values that will be used # when any section or field is not specified in your own configuration # Root options # The graph table configures how the dependency graph is constructed and thus # which crates the checks are performed against [graph] # If 1 or more target triples (and optionally, target_features) are specified, # only the specified targets will be checked when running `cargo deny check`. # This means, if a particular package is only ever used as a target specific # dependency, such as, for example, the `nix` crate only being used via the # `target_family = "unix"` configuration, that only having windows targets in # this list would mean the nix crate, as well as any of its exclusive # dependencies not shared by any other crates, would be ignored, as the target # list here is effectively saying which targets you are building for. targets = [ # The triple can be any string, but only the target triples built in to # rustc (as of 1.40) can be checked against actual config expressions #"x86_64-unknown-linux-musl", # You can also specify which target_features you promise are enabled for a # particular target. target_features are currently not validated against # the actual valid features supported by the target architecture. #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, ] # When creating the dependency graph used as the source of truth when checks are # executed, this field can be used to prune crates from the graph, removing them # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate # is pruned from the graph, all of its dependencies will also be pruned unless # they are connected to another crate in the graph that hasn't been pruned, # so it should be used with care. The identifiers are [Package ID Specifications] # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) #exclude = [] # If true, metadata will be collected with `--all-features`. Note that this can't # be toggled off if true, if you want to conditionally enable `--all-features` it # is recommended to pass `--all-features` on the cmd line instead all-features = false # If true, metadata will be collected with `--no-default-features`. The same # caveat with `all-features` applies no-default-features = false # If set, these feature will be enabled when collecting metadata. If `--features` # is specified on the cmd line they will take precedence over this option. #features = [] # The output table provides options for how/if diagnostics are outputted [output] # When outputting inclusion graphs in diagnostics that include features, this # option can be used to specify the depth at which feature edges will be added. # This option is included since the graphs can be quite large and the addition # of features from the crate(s) to all of the graph roots can be far too verbose. # This option can be overridden via `--feature-depth` on the cmd line feature-depth = 1 # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] # The path where the advisory databases are cloned/fetched into #db-path = "$CARGO_HOME/advisory-dbs" # The url(s) of the advisory databases to use #db-urls = ["https://github.com/rustsec/advisory-db"] yanked = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ #"RUSTSEC-0000-0000", #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. # See Git Authentication for more information about setting up git authentication. #git-fetch-with-cli = true # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ "MIT", "MIT-0", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "MPL-2.0", "Unicode-3.0", "CC0-1.0", "ISC", "Zlib", ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. # [possible values: any between 0.0 and 1.0]. confidence-threshold = 0.8 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list #{ allow = ["Zlib"], crate = "adler32" }, ] # Some crates don't have (easily) machine readable licensing information, # adding a clarification entry for it allows you to manually specify the # licensing information #[[licenses.clarify]] # The package spec the clarification applies to #crate = "ring" # The SPDX expression for the license requirements of the crate #expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents #{ path = "LICENSE", hash = 0xbd0eed23 } #] [licenses.private] # If true, ignores workspace crates that aren't published, or are only # published to private registries. # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked registries = [ #"https://sekretz.com/registry ] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` wildcards = "allow" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted # * simplest-path - The path to the version with the fewest edges is highlighted # * all - Both lowest-version and simplest-path are used highlight = "all" # The default lint level for `default` features for crates that are members of # the workspace that is being checked. This can be overridden by allowing/denying # `default` on a crate-by-crate basis if desired. workspace-default-features = "allow" # The default lint level for `default` features for external crates that are not # members of the workspace. This can be overridden by allowing/denying `default` # on a crate-by-crate basis if desired. external-default-features = "allow" # List of crates that are allowed. Use with care! allow = [ #"ansi_term@0.11.0", #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, ] # List of crates to deny deny = [ #"ansi_term@0.11.0", #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, # Wrapper crates can optionally be specified to allow the crate when it # is a direct dependency of the otherwise banned crate #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, ] # List of features to allow/deny # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. #[[bans.features]] #crate = "reqwest" # Features to not allow #deny = ["json"] # Features to allow #allow = [ # "rustls", # "__rustls", # "__tls", # "hyper-rustls", # "rustls", # "rustls-pemfile", # "rustls-tls-webpki-roots", # "tokio-rustls", # "webpki-roots", #] # If true, the allowed features must exactly match the enabled feature set. If # this is set there is no point setting `deny` #exact = true # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ #"ansi_term@0.11.0", #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive # dependencies starting at the specified crate, up to a certain depth, which is # by default infinite. skip-tree = [ #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies #{ crate = "ansi_term@0.11.0", depth = 20 }, ] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html [sources] # Lint level for what to happen when a crate from a crate registry that is not # in the allow list is encountered unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "warn" # List of URLs for allowed crate registries. Defaults to the crates.io index # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [] [sources.allow-org] # github.com organizations to allow git sources for github = [] # gitlab.com organizations to allow git sources for gitlab = [] # bitbucket.org organizations to allow git sources for bitbucket = [] ================================================ FILE: publish.py ================================================ #!/usr/bin/env python3 # This script is used to publish Cargo to crates.io. # # This is run automatically every 6 weeks by the Release team's automation # whose source is at https://github.com/rust-lang/simpleinfra/. # # See https://doc.crates.io/contrib/process/release.html for more about # Cargo's release process. import os import re import subprocess import urllib.request from urllib.error import HTTPError # Whenever you add a new crate to this list that does NOT start with "cargo-" # you must reach out to the infra team to add the crate to the list of crates # allowed to be published from the "cargo CI" crates.io token. TO_PUBLISH = [ 'credential/cargo-credential', 'credential/cargo-credential-libsecret', 'credential/cargo-credential-wincred', 'credential/cargo-credential-1password', 'credential/cargo-credential-macos-keychain', 'crates/rustfix', 'crates/cargo-platform', 'crates/cargo-util', 'crates/crates-io', 'crates/cargo-util-schemas', 'crates/cargo-test-macro', 'crates/cargo-test-support', 'crates/build-rs', '.', ] def already_published(name, version): url = f'https://static.crates.io/crates/{name}/{version}/download' try: urllib.request.urlopen(url) except HTTPError as e: # 403 and 404 are common responses to assume it is not published if 400 <= e.code < 500: return False print(f'error: failed to check if {name} {version} is already published') print(f' HTTP response error code {e.code} checking {url}') raise return True def maybe_publish(path): content = open(os.path.join(path, 'Cargo.toml')).read() name = re.search('^name = "([^"]+)"', content, re.M).group(1) version = re.search('^version = "([^"]+)"', content, re.M).group(1) if already_published(name, version): print('%s %s is already published, skipping' % (name, version)) return False subprocess.check_call(['cargo', 'publish', '--no-verify'], cwd=path) return True def main(): print('Starting publish...') for path in TO_PUBLISH: maybe_publish(path) print('Publish complete!') if __name__ == '__main__': main() ================================================ FILE: rustfmt.toml ================================================ style_edition = "2024" ================================================ FILE: src/bin/cargo/cli.rs ================================================ use annotate_snippets::Level; use anyhow::{Context as _, anyhow}; use cargo::core::{CliUnstable, features}; use cargo::util::context::TermConfig; use cargo::{CargoResult, drop_print, drop_println}; use clap::builder::UnknownArgumentValueParser; use itertools::Itertools; use std::collections::HashMap; use std::ffi::OsStr; use std::ffi::OsString; use std::fmt::Write; use super::commands; use super::list_commands; use super::third_party_subcommands; use super::user_defined_aliases; use crate::command_prelude::*; use crate::util::is_rustup; use cargo::core::shell::ColorChoice; use cargo::util::style; #[tracing::instrument(skip_all)] pub fn main(gctx: &mut GlobalContext) -> CliResult { // CAUTION: Be careful with using `config` until it is configured below. // In general, try to avoid loading config values unless necessary (like // the [alias] table). let args = cli(gctx).try_get_matches()?; // Update the process-level notion of cwd if let Some(new_cwd) = args.get_one::("directory") { // This is a temporary hack. // This cannot access `GlobalContext`, so this is a bit messy. // This does not properly parse `-Z` flags that appear after the subcommand. // The error message is not as helpful as the standard one. let nightly_features_allowed = matches!(&*features::channel(), "nightly" | "dev"); if !nightly_features_allowed || (nightly_features_allowed && !args .get_many("unstable-features") .map(|mut z| z.any(|value: &String| value == "unstable-options")) .unwrap_or(false)) { return Err(anyhow::format_err!( "the `-C` flag is unstable, \ pass `-Z unstable-options` on the nightly channel to enable it" ) .into()); } std::env::set_current_dir(&new_cwd).context("could not change to requested directory")?; gctx.reload_cwd()?; } let (expanded_args, global_args) = expand_aliases(gctx, args, vec![])?; let is_verbose = expanded_args.verbose() > 0; if expanded_args .get_one::("unstable-features") .map(String::as_str) == Some("help") { // Don't let config errors get in the way of parsing arguments let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); print_zhelp(gctx); } else if expanded_args.flag("version") { // Don't let config errors get in the way of parsing arguments let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); let version = get_version_string(is_verbose); drop_print!(gctx, "{}", version); } else if let Some(code) = expanded_args.get_one::("explain") { // Don't let config errors get in the way of parsing arguments let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); let mut process = gctx.load_global_rustc(None)?.process(); process.arg("--explain").arg(code).exec()?; } else if expanded_args.flag("list") { // Don't let config errors get in the way of parsing arguments let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); print_list(gctx, is_verbose); } else { let (cmd, subcommand_args) = match expanded_args.subcommand() { Some((cmd, args)) => (cmd, args), _ => { // No subcommand provided. cli(gctx).print_help()?; return Ok(()); } }; let exec = Exec::infer(cmd)?; configure_gctx( gctx, &expanded_args, Some(subcommand_args), global_args, Some(&exec), )?; super::init_git(gctx); exec.exec(gctx, subcommand_args)?; } Ok(()) } fn print_zhelp(gctx: &GlobalContext) { let header = style::HEADER; let literal = style::LITERAL; let placeholder = style::PLACEHOLDER; let options = CliUnstable::help(); let max_length = options .iter() .filter(|(_, help)| help.is_some()) .map(|(option_name, _)| option_name.len()) .max() .unwrap_or(0); let z_flags = options .iter() .filter(|(_, help)| help.is_some()) .map(|(opt, help)| { let opt = opt.replace("_", "-"); let help = help.unwrap(); format!(" {literal}-Z {opt:Installed Commands:") ); for (name, command) in list_commands(gctx) { let known_external_desc = known_external_command_descriptions.get(name.as_str()); let literal = style::LITERAL; match command { CommandInfo::BuiltIn { about } => { assert!( known_external_desc.is_none(), "known_external_commands shouldn't contain builtin `{name}`", ); let summary = about.unwrap_or_default(); let summary = summary.lines().next().unwrap_or(&summary); // display only the first line drop_println!(gctx, " {literal}{name:<20}{literal:#} {summary}"); } CommandInfo::External { path } => { if let Some(desc) = known_external_desc { drop_println!(gctx, " {literal}{name:<20}{literal:#} {desc}"); } else if is_verbose { drop_println!( gctx, " {literal}{name:<20}{literal:#} {}", path.display() ); } else { drop_println!(gctx, " {literal}{name}{literal:#}"); } } CommandInfo::Alias { target } => { drop_println!( gctx, " {literal}{name:<20}{literal:#} alias: {}", target.iter().join(" ") ); } } } } pub fn get_version_string(is_verbose: bool) -> String { let version = cargo::version(); let mut version_string = format!("cargo {}\n", version); if is_verbose { version_string.push_str(&format!("release: {}\n", version.version)); if let Some(ref ci) = version.commit_info { version_string.push_str(&format!("commit-hash: {}\n", ci.commit_hash)); version_string.push_str(&format!("commit-date: {}\n", ci.commit_date)); } writeln!(version_string, "host: {}", env!("RUST_HOST_TARGET")).unwrap(); add_libgit2(&mut version_string); add_curl(&mut version_string); add_ssl(&mut version_string); writeln!(version_string, "os: {}", os_info::get()).unwrap(); } version_string } fn add_libgit2(version_string: &mut String) { let git2_v = git2::Version::get(); let lib_v = git2_v.libgit2_version(); let vendored = if git2_v.vendored() { format!("vendored") } else { format!("system") }; writeln!( version_string, "libgit2: {}.{}.{} (sys:{} {})", lib_v.0, lib_v.1, lib_v.2, git2_v.crate_version(), vendored ) .unwrap(); } fn add_curl(version_string: &mut String) { let curl_v = curl::Version::get(); let vendored = if curl_v.vendored() { format!("vendored") } else { format!("system") }; writeln!( version_string, "libcurl: {} (sys:{} {} ssl:{})", curl_v.version(), curl_sys::rust_crate_version(), vendored, curl_v.ssl_version().unwrap_or("none") ) .unwrap(); } fn add_ssl(version_string: &mut String) { #[cfg(feature = "openssl")] { writeln!(version_string, "ssl: {}", openssl::version::version()).unwrap(); } #[cfg(not(feature = "openssl"))] { let _ = version_string; // Silence unused warning. } } /// Expands aliases recursively to collect all the command line arguments. /// /// [`GlobalArgs`] need to be extracted before expanding aliases because the /// clap code for extracting a subcommand discards global options /// (appearing before the subcommand). #[tracing::instrument(skip_all)] fn expand_aliases( gctx: &mut GlobalContext, args: ArgMatches, mut already_expanded: Vec, ) -> Result<(ArgMatches, GlobalArgs), CliError> { if let Some((cmd, sub_args)) = args.subcommand() { let exec = commands::builtin_exec(cmd); let aliased_cmd = super::aliased_command(gctx, cmd); match (exec, aliased_cmd) { (Some(_), Ok(Some(_))) => { // User alias conflicts with a built-in subcommand gctx.shell().warn(format!( "user-defined alias `{}` is ignored, because it is shadowed by a built-in command", cmd, ))?; } (Some(_), Ok(None) | Err(_)) => { // Here we ignore errors from aliasing as we already favor built-in command, // and alias doesn't involve in this context. if let Some(values) = sub_args.get_many::("") { // Command is built-in and is not conflicting with alias, but contains ignored values. return Err(anyhow::format_err!( "\ trailing arguments after built-in command `{}` are unsupported: `{}` To pass the arguments to the subcommand, remove `--`", cmd, values.map(|s| s.to_string_lossy()).join(" "), ) .into()); } } (None, Ok(None)) => {} (None, Ok(Some(alias))) => { // Check if a user-defined alias is shadowing an external subcommand // (binary of the form `cargo-`) // Currently this is only a warning, but after a transition period this will become // a hard error. if super::builtin_aliases_execs(cmd).is_none() { if let Some(path) = super::find_external_subcommand(gctx, cmd) { gctx.shell().print_report( &[ Level::WARNING.secondary_title(format!( "user-defined alias `{}` is shadowing an external subcommand found at `{}`", cmd, path.display() )).element( Level::NOTE.message( "this was previously accepted but will become a hard error in the future; \ see " ) ) ], false, )?; } } if commands::run::is_manifest_command(cmd) { if gctx.cli_unstable().script { return Ok((args, GlobalArgs::default())); } else { gctx.shell().print_report( &[ Level::WARNING.secondary_title( format!("user-defined alias `{cmd}` has the appearance of a manifest-command") ).element( Level::NOTE.message( "this was previously accepted but will be phased out when `-Zscript` is stabilized; \ see " ) ) ], false )?; } } let mut alias = alias .into_iter() .map(|s| OsString::from(s)) .collect::>(); alias.extend( sub_args .get_many::("") .unwrap_or_default() .cloned(), ); // new_args strips out everything before the subcommand, so // capture those global options now. // Note that an alias to an external command will not receive // these arguments. That may be confusing, but such is life. let global_args = GlobalArgs::new(sub_args); let new_args = cli(gctx).no_binary_name(true).try_get_matches_from(alias)?; let Some(new_cmd) = new_args.subcommand_name() else { return Err(anyhow!( "subcommand is required, add a subcommand to the command alias `alias.{cmd}`" ) .into()); }; already_expanded.push(cmd.to_string()); if already_expanded.contains(&new_cmd.to_string()) { // Crash if the aliases are corecursive / unresolvable return Err(anyhow!( "alias {} has unresolvable recursive definition: {} -> {}", already_expanded[0], already_expanded.join(" -> "), new_cmd, ) .into()); } let (expanded_args, _) = expand_aliases(gctx, new_args, already_expanded)?; return Ok((expanded_args, global_args)); } (None, Err(e)) => return Err(e.into()), } }; Ok((args, GlobalArgs::default())) } #[tracing::instrument(skip_all)] fn configure_gctx( gctx: &mut GlobalContext, args: &ArgMatches, subcommand_args: Option<&ArgMatches>, global_args: GlobalArgs, exec: Option<&Exec>, ) -> CliResult { let arg_target_dir = &subcommand_args.and_then(|a| a.value_of_path("target-dir", gctx)); let mut verbose = global_args.verbose + args.verbose(); // quiet is unusual because it is redefined in some subcommands in order // to provide custom help text. let mut quiet = args.flag("quiet") || subcommand_args.map(|a| a.flag("quiet")).unwrap_or_default() || global_args.quiet; if matches!(exec, Some(Exec::Manifest(_))) && !quiet { // Verbosity is shifted quieter for `Exec::Manifest` as it is can be used as if you ran // `cargo install` and we especially shouldn't pollute programmatic output. // // For now, interactive output has the same default output as `cargo run` but that is // subject to change. if let Some(lower) = verbose.checked_sub(1) { verbose = lower; } else if !gctx.shell().is_err_tty() { // Don't pollute potentially-scripted output quiet = true; } } let global_color = global_args.color; // Extract so it can take reference. let color = args .get_one::("color") .map(String::as_str) .or_else(|| global_color.as_deref()); let frozen = args.flag("frozen") || global_args.frozen; let locked = args.flag("locked") || global_args.locked; let offline = args.flag("offline") || global_args.offline; let mut unstable_flags = global_args.unstable_flags; if let Some(values) = args.get_many::("unstable-features") { unstable_flags.extend(values.cloned()); } let mut config_args = global_args.config_args; if let Some(values) = args.get_many::("config") { config_args.extend(values.cloned()); } gctx.configure( verbose, quiet, color, frozen, locked, offline, arg_target_dir, &unstable_flags, &config_args, )?; Ok(()) } enum Exec { Builtin(commands::Exec), Manifest(String), External(String), } impl Exec { /// Precedence isn't the most obvious from this function because /// - Some is determined by `expand_aliases` /// - Some is enforced by `avoid_ambiguity_between_builtins_and_manifest_commands` /// /// In actuality, it is: /// 1. built-ins xor manifest-command /// 2. aliases /// 3. external subcommands fn infer(cmd: &str) -> CargoResult { if let Some(exec) = commands::builtin_exec(cmd) { Ok(Self::Builtin(exec)) } else if commands::run::is_manifest_command(cmd) { Ok(Self::Manifest(cmd.to_owned())) } else { Ok(Self::External(cmd.to_owned())) } } #[tracing::instrument(skip_all)] fn exec(self, gctx: &mut GlobalContext, subcommand_args: &ArgMatches) -> CliResult { match self { Self::Builtin(exec) => exec(gctx, subcommand_args), Self::Manifest(cmd) => { let ext_path = super::find_external_subcommand(gctx, &cmd); if !gctx.cli_unstable().script && ext_path.is_some() { gctx.shell().print_report( &[ Level::WARNING.secondary_title( format!("external subcommand `{cmd}` has the appearance of a manifest-command") ).element( Level::NOTE.message( "this was previously accepted but will be phased out when `-Zscript` is stabilized; \ see " ) ) ], false )?; Self::External(cmd).exec(gctx, subcommand_args) } else { let ext_args: Vec = subcommand_args .get_many::("") .unwrap_or_default() .cloned() .collect(); commands::run::exec_manifest_command(gctx, &cmd, &ext_args) } } Self::External(cmd) => { let mut ext_args = vec![OsStr::new(&cmd)]; ext_args.extend( subcommand_args .get_many::("") .unwrap_or_default() .map(OsString::as_os_str), ); super::execute_external_subcommand(gctx, &cmd, &ext_args) } } } } #[derive(Default)] struct GlobalArgs { verbose: u32, quiet: bool, color: Option, frozen: bool, locked: bool, offline: bool, unstable_flags: Vec, config_args: Vec, } impl GlobalArgs { fn new(args: &ArgMatches) -> GlobalArgs { GlobalArgs { verbose: args.verbose(), quiet: args.flag("quiet"), color: args.get_one::("color").cloned(), frozen: args.flag("frozen"), locked: args.flag("locked"), offline: args.flag("offline"), unstable_flags: args .get_many::("unstable-features") .unwrap_or_default() .cloned() .collect(), config_args: args .get_many::("config") .unwrap_or_default() .cloned() .collect(), } } } #[tracing::instrument(skip_all)] pub fn cli(gctx: &GlobalContext) -> Command { // Don't let config errors get in the way of parsing arguments let term = gctx.get::("term").unwrap_or_default(); let color = term .color .and_then(|c| c.parse().ok()) .unwrap_or(ColorChoice::CargoAuto); let color = match color { ColorChoice::Always => clap::ColorChoice::Always, ColorChoice::Never => clap::ColorChoice::Never, ColorChoice::CargoAuto => clap::ColorChoice::Auto, }; let usage = if is_rustup() { color_print::cstr!( "cargo [+toolchain] [OPTIONS] [COMMAND]\n cargo [+toolchain] [OPTIONS] -Zscript <> [ARGS]..." ) } else { color_print::cstr!( "cargo [OPTIONS] [COMMAND]\n cargo [OPTIONS] -Zscript <> [ARGS]..." ) }; let styles = { clap::builder::styling::Styles::styled() .header(style::HEADER) .usage(style::USAGE) .literal(style::LITERAL) .placeholder(style::PLACEHOLDER) .error(style::ERROR) .valid(style::VALID) .invalid(style::INVALID) }; Command::new("cargo") // Subcommands all count their args' display order independently (from 0), // which makes their args interspersed with global args. This puts global args last. // // We also want these to come before auto-generated `--help` .next_display_order(800) .allow_external_subcommands(true) .color(color) .styles(styles) // Provide a custom help subcommand for calling into man pages .disable_help_subcommand(true) .override_usage(usage) .help_template(color_print::cstr!( "\ Rust's package manager Usage: {usage} Options: {options} Commands: build, b Compile the current package check, c Analyze the current package and report errors, but don't build object files clean Remove the target directory doc, d Build this package's and its dependencies' documentation new Create a new cargo package init Create a new cargo package in an existing directory add Add dependencies to a manifest file remove Remove dependencies from a manifest file run, r Run a binary or example of the local package test, t Run the tests bench Run the benchmarks update Update dependencies listed in Cargo.lock search Search registry for crates publish Package and upload this package to the registry install Install a Rust binary uninstall Uninstall a Rust binary ... See all commands with --list See 'cargo help <>' for more information on a specific command.\n", )) .arg(flag("version", "Print version info and exit").short('V')) .arg(flag("list", "List installed commands")) .arg( opt( "explain", "Provide a detailed explanation of a rustc error message", ) .value_name("CODE"), ) .arg( opt( "verbose", "Use verbose output (-vv very verbose/build.rs output)", ) .short('v') .action(ArgAction::Count) .global(true), ) .arg(flag("quiet", "Do not print cargo log messages").short('q').global(true)) .arg( opt("color", "Coloring") .value_name("WHEN") .global(true) .value_parser(["auto", "always", "never"]) .ignore_case(true), ) .arg( Arg::new("directory") .help("Change to DIRECTORY before doing anything (nightly-only)") .short('C') .value_name("DIRECTORY") .value_hint(clap::ValueHint::DirPath) .value_parser(clap::builder::ValueParser::path_buf()), ) .arg( flag("locked", "Assert that `Cargo.lock` will remain unchanged") .help_heading(heading::MANIFEST_OPTIONS) .global(true), ) .arg( flag("offline", "Run without accessing the network") .help_heading(heading::MANIFEST_OPTIONS) .global(true), ) .arg( flag("frozen", "Equivalent to specifying both --locked and --offline") .help_heading(heading::MANIFEST_OPTIONS) .global(true), ) // Better suggestion for the unsupported short config flag. .arg( Arg::new("unsupported-short-config-flag") .help("") .short('c') .value_parser(UnknownArgumentValueParser::suggest_arg("--config")) .action(ArgAction::SetTrue) .global(true) .hide(true)) .arg(multi_opt("config", "KEY=VALUE|PATH", "Override a configuration value").global(true)) // Better suggestion for the unsupported lowercase unstable feature flag. .arg( Arg::new("unsupported-lowercase-unstable-feature-flag") .help("") .short('z') .value_parser(UnknownArgumentValueParser::suggest_arg("-Z")) .action(ArgAction::SetTrue) .global(true) .hide(true)) .arg(Arg::new("unstable-features") .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details") .short('Z') .value_name("FLAG") .action(ArgAction::Append) .global(true) .add(clap_complete::ArgValueCandidates::new(|| { let flags = CliUnstable::help(); flags.into_iter().map(|flag| { clap_complete::CompletionCandidate::new(flag.0.replace("_", "-")).help(flag.1.map(|help| { help.into() })) }).collect() }))) .add(clap_complete::engine::SubcommandCandidates::new(move || { let mut candidates = get_toolchains_from_rustup() .into_iter() .map(|t| clap_complete::CompletionCandidate::new(t)) .collect::>(); if let Ok(gctx) = new_gctx_for_completions() { candidates.extend(get_command_candidates(&gctx)); } candidates })) .subcommands(commands::builtin()) } fn get_toolchains_from_rustup() -> Vec { let output = std::process::Command::new("rustup") .arg("toolchain") .arg("list") .arg("-q") .output() .unwrap(); if !output.status.success() { return vec![]; } let stdout = String::from_utf8(output.stdout).unwrap(); stdout.lines().map(|line| format!("+{}", line)).collect() } fn get_command_candidates(gctx: &GlobalContext) -> Vec { let mut commands = user_defined_aliases(gctx); commands.extend(third_party_subcommands(gctx)); commands .iter() .map(|(name, cmd_info)| { let help_text = match cmd_info { CommandInfo::Alias { target } => { let cmd_str = target .iter() .map(String::as_str) .collect::>() .join(" "); format!("alias for {}", cmd_str) } CommandInfo::BuiltIn { .. } => { unreachable!("BuiltIn command shouldn't appear in alias map") } CommandInfo::External { path } => { format!("from {}", path.display()) } }; clap_complete::CompletionCandidate::new(name.clone()).help(Some(help_text.into())) }) .collect() } #[test] fn verify_cli() { let gctx = GlobalContext::default().unwrap(); cli(&gctx).debug_assert(); } #[test] fn avoid_ambiguity_between_builtins_and_manifest_commands() { for cmd in commands::builtin() { let name = cmd.get_name(); assert!( !commands::run::is_manifest_command(&name), "built-in command {name} is ambiguous with manifest-commands" ) } } ================================================ FILE: src/bin/cargo/commands/add.rs ================================================ use cargo::sources::CRATES_IO_REGISTRY; use cargo::util::print_available_packages; use indexmap::IndexMap; use indexmap::IndexSet; use cargo::CargoResult; use cargo::core::FeatureValue; use cargo::core::dependency::DepKind; use cargo::ops::cargo_add::AddOptions; use cargo::ops::cargo_add::DepOp; use cargo::ops::cargo_add::add; use cargo::ops::resolve_ws; use cargo::util::command_prelude::*; use cargo::util::toml_mut::manifest::DepTable; pub fn cli() -> Command { clap::Command::new("add") .about("Add dependencies to a Cargo.toml manifest file") .override_usage( color_print::cstr!("\ cargo add [OPTIONS] <>[@<>] ... cargo add [OPTIONS] --path <> ... cargo add [OPTIONS] --git <> ..." )) .after_help(color_print::cstr!("Run `cargo help add` for more detailed information.\n")) .group(clap::ArgGroup::new("selected").multiple(true).required(true)) .args([ clap::Arg::new("crates") .value_name("DEP_ID") .num_args(0..) .help("Reference to a package to add as a dependency") .long_help( "Reference to a package to add as a dependency You can reference a package by: - ``, like `cargo add serde` (latest version will be used) - `@`, like `cargo add serde@1` or `cargo add serde@=1.0.38`" ) .group("selected"), flag("no-default-features", "Disable the default features"), flag("default-features", "Re-enable the default features") .overrides_with("no-default-features"), clap::Arg::new("features") .short('F') .long("features") .value_name("FEATURES") .action(ArgAction::Append) .help("Space or comma separated list of features to activate"), flag("optional", "Mark the dependency as optional") .long_help("Mark the dependency as optional The package name will be exposed as feature of your crate.") .conflicts_with("dev"), flag("no-optional", "Mark the dependency as required") .long_help("Mark the dependency as required The package will be removed from your features.") .conflicts_with("dev") .overrides_with("optional"), flag("public", "Mark the dependency as public (unstable)") .conflicts_with("dev") .conflicts_with("build") .long_help("Mark the dependency as public (unstable) The dependency can be referenced in your library's public API."), flag("no-public", "Mark the dependency as private (unstable)") .conflicts_with("dev") .conflicts_with("build") .overrides_with("public") .long_help("Mark the dependency as private (unstable) While you can use the crate in your implementation, it cannot be referenced in your public API."), clap::Arg::new("rename") .long("rename") .action(ArgAction::Set) .value_name("NAME") .help("Rename the dependency") .long_help("Rename the dependency Example uses: - Depending on multiple versions of a crate - Depend on crates with the same name from different registries"), ]) .arg_manifest_path_without_unsupported_path_tip() .arg_package("Package to modify") .arg_ignore_rust_version() .arg_dry_run("Don't actually write the manifest") .arg_silent_suggestion() .next_help_heading("Source") .args([ clap::Arg::new("path") .long("path") .action(ArgAction::Set) .value_name("PATH") .help("Filesystem path to local crate to add") .group("selected") .conflicts_with("git") .add(clap_complete::engine::ArgValueCompleter::new( clap_complete::engine::PathCompleter::any() .filter(|path| path.join("Cargo.toml").exists()), )), clap::Arg::new("base") .long("base") .action(ArgAction::Set) .value_name("BASE") .help("The path base to use when adding from a local crate (unstable).") .requires("path"), clap::Arg::new("git") .long("git") .action(ArgAction::Set) .value_name("URI") .help("Git repository location") .long_help("Git repository location Without any other information, cargo will use latest commit on the main branch.") .group("selected"), clap::Arg::new("branch") .long("branch") .action(ArgAction::Set) .value_name("BRANCH") .help("Git branch to download the crate from") .requires("git") .group("git-ref"), clap::Arg::new("tag") .long("tag") .action(ArgAction::Set) .value_name("TAG") .help("Git tag to download the crate from") .requires("git") .group("git-ref"), clap::Arg::new("rev") .long("rev") .action(ArgAction::Set) .value_name("REV") .help("Git reference to download the crate from") .long_help("Git reference to download the crate from This is the catch all, handling hashes to named references in remote repositories.") .requires("git") .group("git-ref"), clap::Arg::new("registry") .long("registry") .action(ArgAction::Set) .value_name("NAME") .help("Package registry for this dependency") .add(clap_complete::ArgValueCandidates::new(|| { let candidates = get_registry_candidates(); candidates.unwrap_or_default() })), ]) .next_help_heading("Section") .args([ flag("dev", "Add as development dependency") .long_help("Add as development dependency Dev-dependencies are not used when compiling a package for building, but are used for compiling tests, examples, and benchmarks. These dependencies are not propagated to other packages which depend on this package.") .group("section"), flag("build", "Add as build dependency") .long_help("Add as build dependency Build-dependencies are the only dependencies available for use by build scripts (`build.rs` files).") .group("section"), clap::Arg::new("target") .long("target") .action(ArgAction::Set) .value_name("TARGET") .value_parser(clap::builder::NonEmptyStringValueParser::new()) .help("Add as dependency to the given target platform") ]) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let dry_run = args.dry_run(); let section = parse_section(args); let ws = args.workspace(gctx)?; if args.is_present_with_zero_values("package") { print_available_packages(&ws)?; } let packages = args.packages_from_flags()?; let packages = packages.get_packages(&ws)?; let spec = match packages.len() { 0 => { return Err(CliError::new( anyhow::format_err!( "no packages selected to modify. Please specify one with `-p `" ), 101, )); } 1 => packages[0], _ => { let names = packages.iter().map(|p| p.name()).collect::>(); return Err(CliError::new( anyhow::format_err!( "`cargo add` could not determine which package to modify. \ Use the `--package` option to specify a package. \n\ available packages: {}", names.join(", ") ), 101, )); } }; let dependencies = parse_dependencies(gctx, args)?; let honor_rust_version = args.honor_rust_version(); let options = AddOptions { gctx, spec, dependencies, section, dry_run, honor_rust_version, }; add(&ws, &options)?; // Reload the workspace since we've changed dependencies let ws = args.workspace(gctx)?; resolve_ws(&ws, dry_run)?; Ok(()) } fn parse_dependencies(gctx: &GlobalContext, matches: &ArgMatches) -> CargoResult> { let path = matches.get_one::("path"); let base = matches.get_one::("base"); let git = matches.get_one::("git"); let branch = matches.get_one::("branch"); let rev = matches.get_one::("rev"); let tag = matches.get_one::("tag"); let rename = matches.get_one::("rename"); let registry = match matches.registry(gctx)? { Some(reg) if reg == CRATES_IO_REGISTRY => None, reg => reg, }; let default_features = default_features(matches); let optional = optional(matches); let public = public(matches); let mut crates = matches .get_many::("crates") .into_iter() .flatten() .map(|c| (Some(c.clone()), None)) .collect::>(); let mut infer_crate_name = false; for (crate_name, _) in crates.iter() { let crate_name = crate_name.as_ref().unwrap(); if let Some(toolchain) = crate_name.strip_prefix("+") { anyhow::bail!( "invalid character `+` in dependency name: `+{toolchain}` Use `cargo +{toolchain} add` if you meant to use the `{toolchain}` toolchain." ); } } if crates.is_empty() { if path.is_some() || git.is_some() { crates.insert(None, None); infer_crate_name = true; } else { unreachable!("clap should ensure we have some source selected"); } } for feature in matches .get_many::("features") .into_iter() .flatten() .map(String::as_str) .flat_map(parse_feature) { let parsed_value = FeatureValue::new(feature.into()); match parsed_value { FeatureValue::Feature(_) => { if 1 < crates.len() { let candidates = crates .keys() .map(|c| { format!( "`{}/{}`", c.as_deref().expect("only none when there is 1"), feature ) }) .collect::>(); anyhow::bail!( "feature `{feature}` must be qualified by the dependency it's being activated for, like {}", candidates.join(", ") ); } crates .first_mut() .expect("always at least one crate") .1 .get_or_insert_with(IndexSet::new) .insert(feature.to_owned()); } FeatureValue::Dep { .. } => { anyhow::bail!("feature `{feature}` is not allowed to use explicit `dep:` syntax",) } FeatureValue::DepFeature { dep_name, dep_feature, .. } => { if infer_crate_name { anyhow::bail!( "`{feature}` is unsupported when inferring the crate name, use `{dep_feature}`" ); } if dep_feature.contains('/') { anyhow::bail!("multiple slashes in feature `{feature}` is not allowed"); } crates.get_mut(&Some(dep_name.as_str().to_owned())).ok_or_else(|| { anyhow::format_err!("feature `{dep_feature}` activated for crate `{dep_name}` but the crate wasn't specified") })? .get_or_insert_with(IndexSet::new) .insert(dep_feature.as_str().to_owned()); } } } let mut deps: Vec = Vec::new(); for (crate_spec, features) in crates { let dep = DepOp { crate_spec, rename: rename.map(String::from), features, default_features, optional, public, registry: registry.clone(), path: path.map(String::from), base: base.map(String::from), git: git.map(String::from), branch: branch.map(String::from), rev: rev.map(String::from), tag: tag.map(String::from), }; deps.push(dep); } if deps.len() > 1 && rename.is_some() { anyhow::bail!("cannot specify multiple crates with `--rename`"); } Ok(deps) } fn default_features(matches: &ArgMatches) -> Option { resolve_bool_arg( matches.flag("default-features"), matches.flag("no-default-features"), ) } fn optional(matches: &ArgMatches) -> Option { resolve_bool_arg(matches.flag("optional"), matches.flag("no-optional")) } fn public(matches: &ArgMatches) -> Option { resolve_bool_arg(matches.flag("public"), matches.flag("no-public")) } fn resolve_bool_arg(yes: bool, no: bool) -> Option { match (yes, no) { (true, false) => Some(true), (false, true) => Some(false), (false, false) => None, (_, _) => unreachable!("clap should make this impossible"), } } fn parse_section(matches: &ArgMatches) -> DepTable { let kind = if matches.flag("dev") { DepKind::Development } else if matches.flag("build") { DepKind::Build } else { DepKind::Normal }; let mut table = DepTable::new().set_kind(kind); if let Some(target) = matches.get_one::("target") { assert!(!target.is_empty(), "Target specification may not be empty"); table = table.set_target(target); } table } /// Split feature flag list fn parse_feature(feature: &str) -> impl Iterator { // Not re-using `CliFeatures` because it uses a BTreeSet and loses user's ordering feature .split_whitespace() .flat_map(|s| s.split(',')) .filter(|s| !s.is_empty()) } ================================================ FILE: src/bin/cargo/commands/bench.rs ================================================ use crate::command_prelude::*; use cargo::ops::{self, TestOptions}; pub fn cli() -> Command { subcommand("bench") .about("Execute all benchmarks of a local package") .next_display_order(0) .arg( Arg::new("BENCHNAME") .action(ArgAction::Set) .help("If specified, only run benches containing this string in their names"), ) .arg( Arg::new("args") .value_name("ARGS") .help("Arguments for the bench binary") .num_args(0..) .last(true), ) .arg(flag("no-run", "Compile, but don't run benchmarks")) .arg(flag( "no-fail-fast", "Run all benchmarks regardless of failure", )) .arg_message_format() .arg_silent_suggestion() .arg_package_spec( "Package to run benchmarks for", "Benchmark all packages in the workspace", "Exclude packages from the benchmark", ) .arg_targets_all( "Benchmark only this package's library", "Benchmark only the specified binary", "Benchmark all binaries", "Benchmark only the specified example", "Benchmark all examples", "Benchmark only the specified test target", "Benchmark all targets that have `test = true` set", "Benchmark only the specified bench target", "Benchmark all targets that have `bench = true` set", "Benchmark all targets", ) .arg_features() .arg_jobs() .arg_unsupported_keep_going() .arg_profile("Build artifacts with the specified profile") .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_unit_graph() .arg_timings() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help bench` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let mut compile_opts = args.compile_options(gctx, UserIntent::Bench, Some(&ws), ProfileChecking::Custom)?; compile_opts.build_config.requested_profile = args.get_profile_name("bench", ProfileChecking::Custom)?; let ops = TestOptions { no_run: args.flag("no-run"), no_fail_fast: args.flag("no-fail-fast"), compile_opts, }; let bench_args = args.get_one::("BENCHNAME").into_iter(); let bench_args = bench_args.chain(args.get_many::("args").unwrap_or_default()); let bench_args = bench_args.map(String::as_str).collect::>(); ops::run_benches(&ws, &ops, &bench_args) } ================================================ FILE: src/bin/cargo/commands/build.rs ================================================ use crate::command_prelude::*; use cargo::ops; pub fn cli() -> Command { subcommand("build") // subcommand aliases are handled in aliased_command() // .alias("b") .about("Compile a local package and all of its dependencies") .arg_future_incompat_report() .arg_message_format() .arg_silent_suggestion() .arg_package_spec( "Package to build (see `cargo help pkgid`)", "Build all packages in the workspace", "Exclude packages from the build", ) .arg_targets_all( "Build only this package's library", "Build only the specified binary", "Build all binaries", "Build only the specified example", "Build all examples", "Build only the specified test target", "Build all targets that have `test = true` set", "Build only the specified bench target", "Build all targets that have `bench = true` set", "Build all targets", ) .arg_features() .arg_release("Build artifacts in release mode, with optimizations") .arg_redundant_default_mode("debug", "build", "release") .arg_profile("Build artifacts with the specified profile") .arg_parallel() .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_artifact_dir() .arg_unit_graph() .arg_timings() .arg_compile_time_deps() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help build` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let mut compile_opts = args.compile_options(gctx, UserIntent::Build, Some(&ws), ProfileChecking::Custom)?; if let Some(artifact_dir) = args.value_of_path("artifact-dir", gctx) { // If the user specifies `--artifact-dir`, use that compile_opts.build_config.export_dir = Some(artifact_dir); } else if let Some(artifact_dir) = gctx.build_config()?.artifact_dir.as_ref() { // If a CLI option is not specified for choosing the artifact dir, use the `artifact-dir` from the build config, if // present let artifact_dir = artifact_dir.resolve_path(gctx); compile_opts.build_config.export_dir = Some(artifact_dir); } if compile_opts.build_config.export_dir.is_some() { gctx.cli_unstable() .fail_if_stable_opt("--artifact-dir", 6790)?; } ops::compile(&ws, &compile_opts)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/check.rs ================================================ use crate::command_prelude::*; use cargo::ops; pub fn cli() -> Command { subcommand("check") // subcommand aliases are handled in aliased_command() // .alias("c") .about("Check a local package and all of its dependencies for errors") .arg_future_incompat_report() .arg_message_format() .arg_silent_suggestion() .arg_package_spec( "Package(s) to check", "Check all packages in the workspace", "Exclude packages from the check", ) .arg_targets_all( "Check only this package's library", "Check only the specified binary", "Check all binaries", "Check only the specified example", "Check all examples", "Check only the specified test target", "Check all targets that have `test = true` set", "Check only the specified bench target", "Check all targets that have `bench = true` set", "Check all targets", ) .arg_features() .arg_parallel() .arg_release("Check artifacts in release mode, with optimizations") .arg_profile("Check artifacts with the specified profile") .arg_target_triple("Check for the target triple") .arg_target_dir() .arg_unit_graph() .arg_timings() .arg_compile_time_deps() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help check` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; // This is a legacy behavior that causes `cargo check` to pass `--test`. let test = matches!( args.get_one::("profile").map(String::as_str), Some("test") ); let intent = UserIntent::Check { test }; let compile_opts = args.compile_options(gctx, intent, Some(&ws), ProfileChecking::LegacyTestOnly)?; ops::compile(&ws, &compile_opts)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/clean.rs ================================================ use crate::command_prelude::*; use crate::util::cache_lock::CacheLockMode; use cargo::core::gc::Gc; use cargo::core::gc::{GcOpts, parse_human_size, parse_time_span}; use cargo::core::global_cache_tracker::GlobalCacheTracker; use cargo::ops::CleanContext; use cargo::ops::{self, CleanOptions}; use cargo::util::print_available_packages; use clap_complete::ArgValueCandidates; use indexmap::IndexSet; use std::time::Duration; pub fn cli() -> Command { subcommand("clean") .about("Remove artifacts that cargo has generated in the past") .arg_doc("Whether or not to clean just the documentation directory") .arg_silent_suggestion() .arg_package_spec_simple( "Package to clean artifacts for", ArgValueCandidates::new(get_pkg_name_candidates), ) .arg( flag("workspace", "Clean artifacts of the workspace members") .help_heading(heading::PACKAGE_SELECTION), ) .arg_release("Whether or not to clean release artifacts") .arg_profile("Clean artifacts of the specified profile") .arg_target_triple("Target triple to clean output for") .arg_target_dir() .arg_manifest_path() .arg_dry_run("Display what would be deleted without deleting anything") .args_conflicts_with_subcommands(true) .subcommand( subcommand("gc") .about("Clean global caches") .hide(true) .arg_silent_suggestion() .arg_dry_run("Display what would be deleted without deleting anything") // NOTE: Not all of these options may get stabilized. Some of them are // very low-level details, and may not be something typical users need. .arg( opt( "max-src-age", "Deletes source cache files that have not been used \ since the given age (unstable)", ) .value_name("DURATION") .value_parser(parse_time_span), ) .arg( opt( "max-crate-age", "Deletes crate cache files that have not been used \ since the given age (unstable)", ) .value_name("DURATION") .value_parser(parse_time_span), ) .arg( opt( "max-index-age", "Deletes registry indexes that have not been used \ since the given age (unstable)", ) .value_name("DURATION") .value_parser(parse_time_span), ) .arg( opt( "max-git-co-age", "Deletes git dependency checkouts that have not been used \ since the given age (unstable)", ) .value_name("DURATION") .value_parser(parse_time_span), ) .arg( opt( "max-git-db-age", "Deletes git dependency clones that have not been used \ since the given age (unstable)", ) .value_name("DURATION") .value_parser(parse_time_span), ) .arg( opt( "max-download-age", "Deletes any downloaded cache data that has not been used \ since the given age (unstable)", ) .value_name("DURATION") .value_parser(parse_time_span), ) .arg( opt( "max-src-size", "Deletes source cache files until the cache is under the \ given size (unstable)", ) .value_name("SIZE") .value_parser(parse_human_size), ) .arg( opt( "max-crate-size", "Deletes crate cache files until the cache is under the \ given size (unstable)", ) .value_name("SIZE") .value_parser(parse_human_size), ) .arg( opt( "max-git-size", "Deletes git dependency caches until the cache is under \ the given size (unstable)", ) .value_name("SIZE") .value_parser(parse_human_size), ) .arg( opt( "max-download-size", "Deletes downloaded cache data until the cache is under \ the given size (unstable)", ) .value_name("SIZE") .value_parser(parse_human_size), ), ) .after_help(color_print::cstr!( "Run `cargo help clean` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { match args.subcommand() { Some(("gc", args)) => { return gc(gctx, args); } Some((cmd, _)) => { unreachable!("unexpected command {}", cmd) } None => {} } let ws = args.workspace(gctx)?; if args.is_present_with_zero_values("package") { print_available_packages(&ws)?; } let mut spec = IndexSet::from_iter(values(args, "package")); if args.flag("workspace") { spec.extend(ws.members().map(|package| package.name().to_string())) }; let opts = CleanOptions { gctx, spec, targets: args.targets()?, requested_profile: args.get_profile_name("dev", ProfileChecking::Custom)?, profile_specified: args.contains_id("profile") || args.flag("release"), doc: args.flag("doc"), dry_run: args.dry_run(), }; ops::clean(&ws, &opts)?; Ok(()) } fn gc(gctx: &GlobalContext, args: &ArgMatches) -> CliResult { gctx.cli_unstable().fail_if_stable_command( gctx, "clean gc", 12633, "gc", gctx.cli_unstable().gc, )?; let size_opt = |opt| -> Option { args.get_one::(opt).copied() }; let duration_opt = |opt| -> Option { args.get_one::(opt).copied() }; let mut gc_opts = GcOpts { max_src_age: duration_opt("max-src-age"), max_crate_age: duration_opt("max-crate-age"), max_index_age: duration_opt("max-index-age"), max_git_co_age: duration_opt("max-git-co-age"), max_git_db_age: duration_opt("max-git-db-age"), max_src_size: size_opt("max-src-size"), max_crate_size: size_opt("max-crate-size"), max_git_size: size_opt("max-git-size"), max_download_size: size_opt("max-download-size"), }; if let Some(age) = duration_opt("max-download-age") { gc_opts.set_max_download_age(age); } // If the user sets any options, then only perform the options requested. // If no options are set, do the default behavior. if !gc_opts.is_download_cache_opt_set() { gc_opts.update_for_auto_gc(gctx)?; } let _lock = gctx.acquire_package_cache_lock(CacheLockMode::MutateExclusive)?; let mut cache_track = GlobalCacheTracker::new(&gctx)?; let mut gc = Gc::new(gctx, &mut cache_track)?; let mut clean_ctx = CleanContext::new(gctx); clean_ctx.dry_run = args.dry_run(); gc.gc(&mut clean_ctx, &gc_opts)?; clean_ctx.display_summary()?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/config.rs ================================================ use crate::command_prelude::*; use cargo::ops::cargo_config; pub fn cli() -> Command { subcommand("config") .about("Inspect configuration values") .subcommand_required(true) .arg_required_else_help(true) .subcommand( subcommand("get") .arg( Arg::new("key") .action(ArgAction::Set) .help("The config key to display"), ) .arg( opt("format", "Display format") .value_parser(cargo_config::ConfigFormat::POSSIBLE_VALUES) .default_value("toml"), ) .arg(flag( "show-origin", "Display where the config value is defined", )) .arg( opt("merged", "Whether or not to merge config values") .value_parser(["yes", "no"]) .default_value("yes"), ), ) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { gctx.cli_unstable().fail_if_stable_command( gctx, "config", 9301, "unstable-options", gctx.cli_unstable().unstable_options, )?; match args.subcommand() { Some(("get", args)) => { let opts = cargo_config::GetOptions { key: args.get_one::("key").map(String::as_str), format: args.get_one::("format").unwrap().parse()?, show_origin: args.flag("show-origin"), merged: args.get_one::("merged").map(String::as_str) == Some("yes"), }; cargo_config::get(gctx, &opts)?; } Some((cmd, _)) => { unreachable!("unexpected command {}", cmd) } None => { unreachable!("unexpected command") } } Ok(()) } ================================================ FILE: src/bin/cargo/commands/doc.rs ================================================ use crate::command_prelude::*; use cargo::ops::{self, DocOptions}; pub fn cli() -> Command { subcommand("doc") // subcommand aliases are handled in aliased_command() // .alias("d") .about("Build a package's documentation") .arg(flag( "open", "Opens the docs in a browser after the operation", )) .arg(flag( "no-deps", "Don't build documentation for dependencies", )) .arg(flag("document-private-items", "Document private items")) .arg_message_format() .arg_silent_suggestion() .arg_package_spec( "Package to document", "Document all packages in the workspace", "Exclude packages from the build", ) .arg_features() .arg_targets_lib_bin_example( "Document only this package's library", "Document only the specified binary", "Document all binaries", "Document only the specified example", "Document all examples", ) .arg_parallel() .arg_release("Build artifacts in release mode, with optimizations") .arg_profile("Build artifacts with the specified profile") .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_unit_graph() .arg_timings() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help doc` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let intent = UserIntent::Doc { deps: !args.flag("no-deps"), json: false, }; let mut compile_opts = args.compile_options(gctx, intent, Some(&ws), ProfileChecking::Custom)?; compile_opts.rustdoc_document_private_items = args.flag("document-private-items"); let doc_opts = DocOptions { open_result: args.flag("open"), output_format: ops::OutputFormat::Html, compile_opts, }; ops::doc(&ws, &doc_opts)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/fetch.rs ================================================ use crate::command_prelude::*; use cargo::ops; use cargo::ops::FetchOptions; pub fn cli() -> Command { subcommand("fetch") .about("Fetch dependencies of a package from the network") .arg_silent_suggestion() .arg_target_triple("Fetch dependencies for the target triple") .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help fetch` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let opts = FetchOptions { gctx, targets: args.targets()?, }; let _ = ops::fetch(&ws, &opts)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/fix.rs ================================================ use crate::command_prelude::*; use cargo::core::Workspace; use cargo::ops; pub fn cli() -> Command { subcommand("fix") .about("Automatically fix lint warnings reported by rustc") .arg(flag("edition", "Fix in preparation for the next edition")) .arg(flag( "edition-idioms", "Fix warnings to migrate to the idioms of an edition", )) .arg(flag( "broken-code", "Fix code even if it already has compiler errors", )) .arg(flag( "allow-no-vcs", "Fix code even if a VCS was not detected", )) .arg(flag( "allow-dirty", "Fix code even if the working directory is dirty or has staged changes", )) .arg(flag( "allow-staged", "Fix code even if the working directory has staged changes", )) .arg_message_format() .arg_silent_suggestion() .arg_package_spec( "Package(s) to fix", "Fix all packages in the workspace", "Exclude packages from the fixes", ) .arg_targets_all( "Fix only this package's library", "Fix only the specified binary", "Fix all binaries", "Fix only the specified example", "Fix all examples", "Fix only the specified test target", "Fix all targets that have `test = true` set", "Fix only the specified bench target", "Fix all targets that have `bench = true` set", "Fix all targets (default)", ) .arg_features() .arg_parallel() .arg_release("Fix artifacts in release mode, with optimizations") .arg_profile("Build artifacts with the specified profile") .arg_target_triple("Fix for the target triple") .arg_target_dir() .arg_timings() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help fix` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { // This is a legacy behavior that causes `cargo fix` to pass `--test`. let test = matches!( args.get_one::("profile").map(String::as_str), Some("test") ); let intent = UserIntent::Check { test }; // Unlike other commands default `cargo fix` to all targets to fix as much // code as we can. let root_manifest = args.root_manifest(gctx)?; // Can't use workspace() to avoid using -Zavoid-dev-deps (if passed) let mut ws = Workspace::new(&root_manifest, gctx)?; ws.set_resolve_honors_rust_version(args.honor_rust_version()); let mut opts = args.compile_options(gctx, intent, Some(&ws), ProfileChecking::LegacyTestOnly)?; let edition = args.flag("edition") || args.flag("edition-idioms"); if !opts.filter.is_specific() && edition { // When `cargo fix` is run without specifying targets but with `--edition` or `--edition-idioms`, // it should default to fixing all targets. // See: https://github.com/rust-lang/cargo/issues/13527 opts.filter = ops::CompileFilter::new_all_targets(); } let allow_dirty = args.flag("allow-dirty"); let mut opts = ops::FixOptions { edition: args .flag("edition") .then_some(ops::EditionFixMode::NextRelative), idioms: args.flag("edition-idioms"), compile_opts: opts, allow_dirty, allow_staged: allow_dirty || args.flag("allow-staged"), allow_no_vcs: args.flag("allow-no-vcs"), broken_code: args.flag("broken-code"), }; if let Some(fe) = &gctx.cli_unstable().fix_edition { ops::fix_edition(gctx, &ws, &mut opts, fe)?; } else { ops::fix(gctx, &ws, &mut opts)?; } Ok(()) } ================================================ FILE: src/bin/cargo/commands/generate_lockfile.rs ================================================ use clap_complete::engine::ArgValueCompleter; use clap_complete::engine::CompletionCandidate; use crate::command_prelude::*; use cargo::ops; pub fn cli() -> Command { subcommand("generate-lockfile") .about("Generate the lockfile for a package") .arg_silent_suggestion() .arg_manifest_path() .arg_ignore_rust_version_with_help("Ignore `rust-version` specification in packages") .arg( clap::Arg::new("publish-time") .long("publish-time") .value_name("yyyy-mm-ddThh:mm:ssZ") .add(ArgValueCompleter::new(datetime_completer)) .help("Latest publish time allowed for registry packages (unstable)") .help_heading(heading::MANIFEST_OPTIONS) ) .after_help(color_print::cstr!( "Run `cargo help generate-lockfile` for more detailed information.\n" )) } fn datetime_completer(current: &std::ffi::OsStr) -> Vec { let mut completions = vec![]; let Some(current) = current.to_str() else { return completions; }; if current.is_empty() { // While not likely what people want, it can at least give them a starting point to edit let timestamp = jiff::Timestamp::now(); completions.push(CompletionCandidate::new(timestamp.to_string())); } else if let Ok(date) = current.parse::() { if let Ok(zoned) = jiff::Zoned::default().with().date(date).build() { let timestamp = zoned.timestamp(); completions.push(CompletionCandidate::new(timestamp.to_string())); } } completions } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let publish_time = args.get_one::("publish-time"); let mut ws = args.workspace(gctx)?; if let Some(publish_time) = publish_time { gctx.cli_unstable() .fail_if_stable_opt("--publish-time", 5221)?; let publish_time = cargo_util_schemas::index::parse_pubtime(publish_time).map_err(anyhow::Error::from)?; ws.set_resolve_publish_time(publish_time); } ops::generate_lockfile(&ws)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/git_checkout.rs ================================================ //! Removed. use crate::command_prelude::*; const REMOVED: &str = "The `git-checkout` command has been removed."; pub fn cli() -> Command { subcommand("git-checkout") .about("REMOVED: This command has been removed") .hide(true) .override_help(REMOVED) } pub fn exec(_gctx: &mut GlobalContext, _args: &ArgMatches) -> CliResult { Err(anyhow::format_err!(REMOVED).into()) } ================================================ FILE: src/bin/cargo/commands/help.rs ================================================ use crate::aliased_command; use crate::command_prelude::*; use cargo::drop_println; use cargo::util::errors::CargoResult; use cargo_util::paths::resolve_executable; use flate2::read::GzDecoder; use std::ffi::OsStr; use std::ffi::OsString; use std::io::Read; use std::io::Write; use std::path::Path; const COMPRESSED_MAN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/man.tgz")); pub fn cli() -> Command { subcommand("help") .about("Displays help for a cargo command") .arg( Arg::new("COMMAND") .num_args(1..) .action(ArgAction::Append) .add(clap_complete::ArgValueCandidates::new( get_completion_candidates, )), ) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let args_command = args .get_many::("COMMAND") .map(|vals| vals.map(String::as_str).collect::>()) .unwrap_or_default(); if args_command.is_empty() { let _ = crate::cli::cli(gctx).print_help(); return Ok(()); } let cmd: String; let lookup_parts: Vec<&str> = if args_command.len() == 1 { // Expand alias first let subcommand = args_command.first().unwrap(); match aliased_command(gctx, subcommand).ok().flatten() { Some(argv) if argv.len() > 1 => { // If this alias is more than a simple subcommand pass-through, show the alias. let alias = argv.join(" "); drop_println!(gctx, "`{}` is aliased to `{}`", subcommand, alias); return Ok(()); } // Otherwise, resolve the alias into its subcommand. Some(argv) => { // An alias with an empty argv can be created via `"empty-alias" = ""`. cmd = argv .into_iter() .next() .unwrap_or_else(|| subcommand.to_string()); vec![cmd.as_str()] } None => args_command.clone(), } } else { args_command.clone() }; match find_builtin_cmd(&lookup_parts) { Ok(path) => { let man_page_name = path.join("-"); if try_help(&man_page_name)? { return Ok(()); } crate::execute_internal_subcommand( gctx, &[OsStr::new(&man_page_name), OsStr::new("--help")], )?; } Err(FindError::UnknownCommand(cmd)) => { if lookup_parts.len() == 1 { if let Some(man_page_name) = find_builtin_cmd_dash_joined(cmd) { if try_help(&man_page_name)? { return Ok(()); } crate::execute_internal_subcommand( gctx, &[OsStr::new(&man_page_name), OsStr::new("--help")], )?; } else { crate::execute_external_subcommand( gctx, cmd, &[OsStr::new(cmd), OsStr::new("--help")], )?; } } else { let err = anyhow::format_err!( "no such command: `{cmd}`\n\n\ help: view all installed commands with `cargo --list`", ); return Err(err.into()); } } Err(FindError::UnknownSubcommand { valid_prefix, invalid, }) => { let valid_prefix = valid_prefix.join(" "); let err = anyhow::format_err!( "no such command: `cargo {valid_prefix} {invalid}` \n\n\ help: view available subcommands with `cargo {valid_prefix} --help`", ); return Err(err.into()); } } Ok(()) } fn try_help(subcommand: &str) -> CargoResult { #[expect( clippy::disallowed_methods, reason = "testing only, no reason for config support" )] let force_help_text = std::env::var("__CARGO_TEST_FORCE_HELP_TXT").is_ok(); if resolve_executable(Path::new("man")).is_ok() && !force_help_text { let Some(man) = extract_man(subcommand, "1") else { return Ok(false); }; write_and_spawn(subcommand, &man, "man")?; } else { let Some(txt) = extract_man(subcommand, "txt") else { return Ok(false); }; if force_help_text { drop(std::io::stdout().write_all(&txt)); } else if resolve_executable(Path::new("less")).is_ok() { write_and_spawn(subcommand, &txt, "less")?; } else if resolve_executable(Path::new("more")).is_ok() { write_and_spawn(subcommand, &txt, "more")?; } else { drop(std::io::stdout().write_all(&txt)); } } Ok(true) } /// Extracts the given man page from the compressed archive. /// /// Returns None if the command wasn't found. fn extract_man(subcommand: &str, extension: &str) -> Option> { let extract_name = OsString::from(format!("cargo-{}.{}", subcommand, extension)); let gz = GzDecoder::new(COMPRESSED_MAN); let mut ar = tar::Archive::new(gz); // Unwraps should be safe here, since this is a static archive generated // by our build script. It should never be an invalid format! for entry in ar.entries().unwrap() { let mut entry = entry.unwrap(); let path = entry.path().unwrap(); if path.file_name().unwrap() != extract_name { continue; } let mut result = Vec::new(); entry.read_to_end(&mut result).unwrap(); return Some(result); } None } /// Write the contents of a man page to disk and spawn the given command to /// display it. fn write_and_spawn(name: &str, contents: &[u8], command: &str) -> CargoResult<()> { let prefix = format!("cargo-{}.", name); let mut tmp = tempfile::Builder::new().prefix(&prefix).tempfile()?; let f = tmp.as_file_mut(); f.write_all(contents)?; f.flush()?; let path = tmp.path(); // Use a path relative to the temp directory so that it can work on // cygwin/msys systems which don't handle windows-style paths. let mut relative_name = std::ffi::OsString::from("./"); relative_name.push(path.file_name().unwrap()); let mut cmd = std::process::Command::new(command) .arg(relative_name) .current_dir(path.parent().unwrap()) .spawn()?; drop(cmd.wait()); Ok(()) } enum FindError<'a> { /// The primary command was not found. UnknownCommand(&'a str), /// A subcommand in the path was not found. UnknownSubcommand { valid_prefix: Vec<&'a str>, invalid: &'a str, }, } /// Finds a auiltin command. fn find_builtin_cmd<'a>(parts: &[&'a str]) -> Result, FindError<'a>> { let Some((first, rest)) = parts.split_first() else { return Err(FindError::UnknownCommand("")); }; let builtins = super::builtin(); let Some(mut current) = builtins .iter() .find(|cmd| cmd.get_name() == *first || cmd.get_all_aliases().any(|a| a == *first)) else { return Err(FindError::UnknownCommand(first)); }; let mut path = vec![current.get_name().to_string()]; for (i, &part) in rest.iter().enumerate() { let next = current .get_subcommands() .find(|cmd| cmd.get_name() == part || cmd.get_all_aliases().any(|a| a == part)); if let Some(next) = next { path.push(next.get_name().to_string()); current = next; } else { let valid_prefix = [*first] .into_iter() .chain(rest[..i].iter().copied()) .collect::>(); return Err(FindError::UnknownSubcommand { valid_prefix, invalid: part, }); }; } Ok(path) } fn find_builtin_cmd_dash_joined(s: &str) -> Option { let builtins = super::builtin(); for cmd in builtins.iter() { if let Some(result) = try_match_cmd(cmd, s) { return Some(result); } } None } /// Tries to match a single dash-joined argument against commands fn try_match_cmd(cmd: &Command, arg: &str) -> Option { let name = cmd.get_name(); if arg == name || cmd.get_all_aliases().any(|alias| alias == arg) { return Some(name.to_string()); } if let Some(rest) = arg.strip_prefix(name).and_then(|r| r.strip_prefix('-')) { for cmd in cmd.get_subcommands() { if let Some(sub_cmds) = try_match_cmd(cmd, rest) { return Some(format!("{name}-{sub_cmds}")); } } } for alias in cmd.get_all_aliases() { if let Some(rest) = arg.strip_prefix(alias).and_then(|r| r.strip_prefix('-')) { for cmd in cmd.get_subcommands() { if let Some(sub_cmds) = try_match_cmd(cmd, rest) { return Some(format!("{name}-{sub_cmds}")); } } } } None } /// Returns dash-joined names for nested commands, /// so they can be completed as single tokens. fn get_completion_candidates() -> Vec { fn walk( cmd: Command, prefix: Option<&String>, candidates: &mut Vec, ) { let name = cmd.get_name(); let key = match prefix { Some(prefix) => format!("{prefix}-{name}"), None => name.to_string(), }; for cmd in cmd.get_subcommands() { walk(cmd.clone(), Some(&key), candidates); } let candidate = clap_complete::CompletionCandidate::new(&key) .help(cmd.get_about().cloned()) .hide(cmd.is_hide_set()); candidates.push(candidate); } let mut candidates = Vec::new(); for cmd in super::builtin() { walk(cmd, None, &mut candidates); } candidates } ================================================ FILE: src/bin/cargo/commands/info.rs ================================================ use anyhow::Context; use cargo::ops::info; use cargo::util::command_prelude::*; use cargo_util_schemas::core::PackageIdSpec; pub fn cli() -> Command { Command::new("info") .about("Display information about a package") .arg( Arg::new("package") .required(true) .value_name("SPEC") .help_heading(heading::PACKAGE_SELECTION) .help("Package to inspect"), ) .arg_index("Registry index URL to search packages in") .arg_registry("Registry to search packages in") .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help info` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let package = args .get_one::("package") .map(String::as_str) .unwrap(); let spec = PackageIdSpec::parse(package) .with_context(|| format!("invalid package ID specification: `{package}`"))?; // Check if --registry or --index was explicitly provided let explicit_registry = args._contains("registry") || args._contains("index"); let reg_or_index = args.registry_or_index(gctx)?; info(&spec, gctx, reg_or_index, explicit_registry)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/init.rs ================================================ use crate::command_prelude::*; use cargo::ops; pub fn cli() -> Command { subcommand("init") .about("Create a new cargo package in an existing directory") .arg( Arg::new("path") .value_name("PATH") .action(ArgAction::Set) .default_value("."), ) .arg_new_opts() .arg_registry("Registry to use") .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help init` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let opts = args.new_options(gctx)?; ops::init(&opts, gctx)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/install.rs ================================================ use crate::command_prelude::*; use anyhow::anyhow; use anyhow::bail; use anyhow::format_err; use cargo::CargoResult; use cargo::core::{GitReference, SourceId, Workspace}; use cargo::ops; use cargo::util::IntoUrl; use cargo::util::VersionExt; use cargo_util_schemas::manifest::PackageName; use itertools::Itertools; use semver::VersionReq; use cargo_util::paths; pub fn cli() -> Command { subcommand("install") .about("Install a Rust binary") .arg( Arg::new("crate") .value_name("CRATE[@]") .help("Select the package from the given source") .value_parser(parse_crate) .num_args(0..), ) .arg( opt("version", "Specify a version to install") .alias("vers") .value_name("VERSION") .value_parser(parse_semver_flag) .requires("crate"), ) .arg( opt("index", "Registry index to install from") .value_name("INDEX") .requires("crate") .conflicts_with_all(&["git", "path", "registry"]), ) .arg( opt("registry", "Registry to use") .value_name("REGISTRY") .requires("crate") .conflicts_with_all(&["git", "path", "index"]), ) .arg( opt("git", "Git URL to install the specified crate from") .value_name("URL") .conflicts_with_all(&["path", "index", "registry"]), ) .arg( opt("branch", "Branch to use when installing from git") .value_name("BRANCH") .requires("git"), ) .arg( opt("tag", "Tag to use when installing from git") .value_name("TAG") .requires("git"), ) .arg( opt("rev", "Specific commit to use when installing from git") .value_name("SHA") .requires("git"), ) .arg( opt("path", "Filesystem path to local crate to install from") .value_name("PATH") .conflicts_with_all(&["git", "index", "registry"]) .add(clap_complete::engine::ArgValueCompleter::new( clap_complete::engine::PathCompleter::any() .filter(|path| path.join("Cargo.toml").exists()), )), ) .arg(opt("root", "Directory to install packages into").value_name("DIR")) .arg(flag("force", "Force overwriting existing crates or binaries").short('f')) .arg_dry_run("Perform all checks without installing (unstable)") .arg(flag("no-track", "Do not save tracking information")) .arg(flag( "list", "List all installed packages and their versions", )) .arg_ignore_rust_version() .arg_message_format() .arg_silent_suggestion() .arg_targets_bins_examples( "Install only the specified binary", "Install all binaries", "Install only the specified example", "Install all examples", ) .arg_features() .arg_parallel() .arg( flag( "debug", "Build in debug mode (with the 'dev' profile) instead of release mode", ) .conflicts_with("profile"), ) .arg_redundant_default_mode("release", "install", "debug") .arg_profile("Install artifacts with the specified profile") .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_timings() .after_help(color_print::cstr!( "Run `cargo help install` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let path = args.value_of_path("path", gctx); if let Some(path) = &path { gctx.reload_rooted_at(path)?; } else { // TODO: Consider calling set_search_stop_path(home). gctx.reload_rooted_at(gctx.home().clone().into_path_unlocked())?; } // In general, we try to avoid normalizing paths in Cargo, // but in these particular cases we need it to fix rust-lang/cargo#10283. // (Handle `SourceId::for_path` and `Workspace::new`, // but not `GlobalContext::reload_rooted_at` which is always cwd) let path = path.map(|p| paths::normalize_path(&p)); let version = args.get_one::("version"); let krates = args .get_many::("crate") .unwrap_or_default() .cloned() .dedup_by(|x, y| x == y) .map(|(krate, local_version)| resolve_crate(krate, local_version, version)) .collect::>>()?; for (crate_name, _) in krates.iter() { let package_name = PackageName::new(crate_name); if !crate_name.contains("@") && package_name.is_err() { for (idx, ch) in crate_name.char_indices() { if !(unicode_ident::is_xid_continue(ch) || ch == '-') { let mut suggested_crate_name = crate_name.to_string(); suggested_crate_name.insert_str(idx, "@"); if let Ok((_, Some(_))) = parse_crate(&suggested_crate_name.as_str()) { let err = package_name.unwrap_err(); return Err( anyhow::format_err!("{err}\n\n\ help: if this is meant to be a package name followed by a version, insert an `@` like `{suggested_crate_name}`").into()); } } } } if let Some(toolchain) = crate_name.strip_prefix("+") { return Err(anyhow!( "invalid character `+` in package name: `+{toolchain}` Use `cargo +{toolchain} install` if you meant to use the `{toolchain}` toolchain." ) .into()); } if let Ok(url) = crate_name.into_url() { if matches!(url.scheme(), "http" | "https") { return Err(anyhow!( "invalid package name: `{url}` Use `cargo install --git {url}` if you meant to install from a git repository." ) .into()); } } if crate_name != "." && let Err(e) = package_name { return Err(anyhow::format_err!("{e}").into()); } } let mut from_cwd = false; let source = if let Some(url) = args.get_one::("git") { let url = url.into_url()?; let gitref = if let Some(branch) = args.get_one::("branch") { GitReference::Branch(branch.clone()) } else if let Some(tag) = args.get_one::("tag") { GitReference::Tag(tag.clone()) } else if let Some(rev) = args.get_one::("rev") { GitReference::Rev(rev.clone()) } else { GitReference::DefaultBranch }; SourceId::for_git(&url, gitref)? } else if let Some(path) = &path { SourceId::for_path(path)? } else if krates.is_empty() { from_cwd = true; SourceId::for_path(gctx.cwd())? } else if let Some(reg_or_index) = args.registry_or_index(gctx)? { match reg_or_index { ops::RegistryOrIndex::Registry(r) => SourceId::alt_registry(gctx, &r)?, ops::RegistryOrIndex::Index(url) => SourceId::for_registry(&url)?, } } else { SourceId::crates_io(gctx)? }; let root = args.get_one::("root").map(String::as_str); // We only provide workspace information for local crate installation from // one of the following sources: // - From current working directory (only work for edition 2015). // - From a specific local file path (from `--path` arg). // // This workspace information is for emitting helpful messages from // `ArgMatchesExt::compile_options` and won't affect the actual compilation. let workspace = if from_cwd { args.workspace(gctx).ok() } else if let Some(path) = &path { Workspace::new(&path.join("Cargo.toml"), gctx).ok() } else { None }; let mut compile_opts = args.compile_options( gctx, UserIntent::Build, workspace.as_ref(), ProfileChecking::Custom, )?; compile_opts.build_config.requested_profile = args.get_profile_name("release", ProfileChecking::Custom)?; if args.dry_run() { gctx.cli_unstable().fail_if_stable_opt("--dry-run", 11123)?; } if args.flag("list") { ops::install_list(root, gctx)?; } else { ops::install( gctx, root, krates, source, from_cwd, &compile_opts, args.flag("force"), args.flag("no-track"), args.dry_run(), )?; } Ok(()) } type CrateVersion = (String, Option); fn parse_crate(krate: &str) -> crate::CargoResult { let (krate, version) = if let Some((k, v)) = krate.split_once('@') { if k.is_empty() { // by convention, arguments starting with `@` are response files anyhow::bail!("missing crate name before '@'"); } let krate = k.to_owned(); let version = Some(parse_semver_flag(v)?); (krate, version) } else { let krate = krate.to_owned(); let version = None; (krate, version) }; if krate.is_empty() { anyhow::bail!("crate name is empty"); } Ok((krate, version)) } /// Parses x.y.z as if it were =x.y.z, and gives CLI-specific error messages in the case of invalid /// values. fn parse_semver_flag(v: &str) -> CargoResult { // If the version begins with character <, >, =, ^, ~ parse it as a // version range, otherwise parse it as a specific version let first = v .chars() .next() .ok_or_else(|| format_err!("no version provided for the `--version` flag"))?; if let Some(stripped) = v.strip_prefix("v") { bail!( "the version provided, `{v}` is not a valid SemVer requirement\n\n\ help: try changing the version to `{stripped}`", ) } let is_req = "<>=^~".contains(first) || v.contains('*'); if is_req { match v.parse::() { Ok(v) => Ok(v), Err(_) => bail!( "the `--version` provided, `{}`, is \ not a valid semver version requirement\n\n\ Please have a look at \ https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html \ for the correct format", v ), } } else { match v.trim().parse::() { Ok(v) => Ok(v.to_exact_req()), Err(e) => { let mut msg = e.to_string(); // If it is not a valid version but it is a valid version // requirement, add a note to the warning if v.parse::().is_ok() { msg.push_str(&format!( "\n\n tip: if you want to specify SemVer range, \ add an explicit qualifier, like '^{}'", v )); } bail!(msg); } } } } fn resolve_crate( krate: String, local_version: Option, version: Option<&VersionReq>, ) -> crate::CargoResult { let version = match (local_version, version) { (Some(_), Some(_)) => { anyhow::bail!("cannot specify both `@` and `--version `"); } (Some(l), None) => Some(l), (None, Some(g)) => Some(g.to_owned()), (None, None) => None, }; Ok((krate, version)) } ================================================ FILE: src/bin/cargo/commands/locate_project.rs ================================================ use crate::command_prelude::*; use anyhow::bail; use cargo::{CargoResult, drop_println}; use serde::Serialize; pub fn cli() -> Command { subcommand("locate-project") .about("Print a JSON representation of a Cargo.toml file's location") .arg(flag("workspace", "Locate Cargo.toml of the workspace root")) .arg( opt("message-format", "Output representation") .value_name("FMT") .value_parser(["json", "plain"]) .ignore_case(true), ) .arg_silent_suggestion() .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help locate-project` for more detailed information.\n" )) } #[derive(Serialize)] pub struct ProjectLocation<'a> { root: &'a str, } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let root_manifest; let workspace_root; let workspace; let root = match WhatToFind::parse(args) { WhatToFind::CurrentManifest => { root_manifest = args.root_manifest(gctx)?; &root_manifest } WhatToFind::Workspace => { root_manifest = args.root_manifest(gctx)?; // Try fast path first - only works when package is explicitly listed in members if let Some(ws_root) = cargo::core::find_workspace_root_with_membership_check(&root_manifest, gctx)? { workspace_root = ws_root; &workspace_root } else { // Fallback to full workspace loading for path dependency membership. // If loading fails, we must propagate the error to avoid false results. workspace = args.workspace(gctx)?; workspace.root_manifest() } } }; let root = root .to_str() .ok_or_else(|| { anyhow::format_err!( "your package path contains characters \ not representable in Unicode" ) }) .map_err(|e| CliError::new(e, 1))?; let location = ProjectLocation { root }; match MessageFormat::parse(args)? { MessageFormat::Json => gctx.shell().print_json(&location)?, MessageFormat::Plain => drop_println!(gctx, "{}", location.root), } Ok(()) } enum WhatToFind { CurrentManifest, Workspace, } impl WhatToFind { fn parse(args: &ArgMatches) -> Self { if args.flag("workspace") { WhatToFind::Workspace } else { WhatToFind::CurrentManifest } } } enum MessageFormat { Json, Plain, } impl MessageFormat { fn parse(args: &ArgMatches) -> CargoResult { let fmt = match args.get_one::("message-format") { Some(fmt) => fmt, None => return Ok(MessageFormat::Json), }; match fmt.to_ascii_lowercase().as_str() { "json" => Ok(MessageFormat::Json), "plain" => Ok(MessageFormat::Plain), s => bail!("invalid message format specifier: `{}`", s), } } } ================================================ FILE: src/bin/cargo/commands/login.rs ================================================ use cargo::ops; use cargo::ops::RegistryOrIndex; use crate::command_prelude::*; pub fn cli() -> Command { subcommand("login") .about("Log in to a registry.") .arg( Arg::new("token") .value_name("TOKEN") .action(ArgAction::Set) .hide(true), ) .arg_registry("Registry to use") .arg( Arg::new("args") .help("Additional arguments for the credential provider") .num_args(0..) .last(true), ) .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help login` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let reg = args.registry_or_index(gctx)?; assert!( !matches!(reg, Some(RegistryOrIndex::Index(..))), "must not be index URL" ); let token = args.get_one::("token").map(|s| s.as_str().into()); if token.is_some() { let _ = gctx .shell() .warn("`cargo login ` is deprecated in favor of reading `` from stdin"); } let extra_args = args .get_many::("args") .unwrap_or_default() .map(String::as_str) .collect::>(); ops::registry_login(gctx, token, reg.as_ref(), &extra_args)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/logout.rs ================================================ use cargo::ops; use cargo::ops::RegistryOrIndex; use crate::command_prelude::*; pub fn cli() -> Command { subcommand("logout") .about("Remove an API token from the registry locally") .arg_registry("Registry to use") .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help logout` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let reg = args.registry_or_index(gctx)?; assert!( !matches!(reg, Some(RegistryOrIndex::Index(..))), "must not be index URL" ); ops::registry_logout(gctx, reg)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/metadata.rs ================================================ use cargo::ops::{self, OutputMetadataOptions}; use crate::command_prelude::*; pub fn cli() -> Command { subcommand("metadata") .about( "Output the resolved dependencies of a package, \ the concrete used versions including overrides, \ in machine-readable format", ) .arg(multi_opt( "filter-platform", "TRIPLE", "Only include resolve dependencies matching the given target-triple", )) .arg(flag( "no-deps", "Output information only about the workspace members \ and don't fetch dependencies", )) .arg( opt("format-version", "Format version") .value_name("VERSION") .value_parser(["1"]), ) .arg_silent_suggestion() .arg_features() .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help metadata` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let version = match args.get_one::("format-version") { None => { gctx.shell().warn( "please specify `--format-version` flag explicitly \ to avoid compatibility problems", )?; 1 } Some(version) => version.parse().unwrap(), }; let options = OutputMetadataOptions { cli_features: args.cli_features()?, no_deps: args.flag("no-deps"), filter_platforms: args._values_of("filter-platform"), version, }; let result = ops::output_metadata(&ws, &options)?; gctx.shell().print_json(&result)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/mod.rs ================================================ use crate::command_prelude::*; pub fn builtin() -> Vec { vec![ add::cli(), bench::cli(), build::cli(), check::cli(), clean::cli(), config::cli(), doc::cli(), fetch::cli(), fix::cli(), generate_lockfile::cli(), git_checkout::cli(), help::cli(), info::cli(), init::cli(), install::cli(), locate_project::cli(), login::cli(), logout::cli(), metadata::cli(), new::cli(), owner::cli(), package::cli(), pkgid::cli(), publish::cli(), read_manifest::cli(), remove::cli(), report::cli(), run::cli(), rustc::cli(), rustdoc::cli(), search::cli(), test::cli(), tree::cli(), uninstall::cli(), update::cli(), vendor::cli(), verify_project::cli(), version::cli(), yank::cli(), ] } pub type Exec = fn(&mut GlobalContext, &ArgMatches) -> CliResult; pub fn builtin_exec(cmd: &str) -> Option { let f = match cmd { "add" => add::exec, "bench" => bench::exec, "build" => build::exec, "check" => check::exec, "clean" => clean::exec, "config" => config::exec, "doc" => doc::exec, "fetch" => fetch::exec, "fix" => fix::exec, "generate-lockfile" => generate_lockfile::exec, "git-checkout" => git_checkout::exec, "help" => help::exec, "info" => info::exec, "init" => init::exec, "install" => install::exec, "locate-project" => locate_project::exec, "login" => login::exec, "logout" => logout::exec, "metadata" => metadata::exec, "new" => new::exec, "owner" => owner::exec, "package" => package::exec, "pkgid" => pkgid::exec, "publish" => publish::exec, "read-manifest" => read_manifest::exec, "remove" => remove::exec, "report" => report::exec, "run" => run::exec, "rustc" => rustc::exec, "rustdoc" => rustdoc::exec, "search" => search::exec, "test" => test::exec, "tree" => tree::exec, "uninstall" => uninstall::exec, "update" => update::exec, "vendor" => vendor::exec, "verify-project" => verify_project::exec, "version" => version::exec, "yank" => yank::exec, _ => return None, }; Some(f) } pub mod add; pub mod bench; pub mod build; pub mod check; pub mod clean; pub mod config; pub mod doc; pub mod fetch; pub mod fix; pub mod generate_lockfile; pub mod git_checkout; pub mod help; pub mod info; pub mod init; pub mod install; pub mod locate_project; pub mod login; pub mod logout; pub mod metadata; pub mod new; pub mod owner; pub mod package; pub mod pkgid; pub mod publish; pub mod read_manifest; pub mod remove; pub mod report; pub mod run; pub mod rustc; pub mod rustdoc; pub mod search; pub mod test; pub mod tree; pub mod uninstall; pub mod update; pub mod vendor; pub mod verify_project; pub mod version; pub mod yank; ================================================ FILE: src/bin/cargo/commands/new.rs ================================================ use crate::command_prelude::*; use cargo::ops; pub fn cli() -> Command { subcommand("new") .about("Create a new cargo package at ") .arg( Arg::new("path") .value_name("PATH") .action(ArgAction::Set) .required(true), ) .arg_new_opts() .arg_registry("Registry to use") .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help new` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let opts = args.new_options(gctx)?; ops::new(&opts, gctx)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/owner.rs ================================================ use crate::command_prelude::*; use cargo::ops::{self, OwnersOptions}; use cargo_credential::Secret; pub fn cli() -> Command { subcommand("owner") .about("Manage the owners of a crate on the registry") .arg(Arg::new("crate").value_name("CRATE").action(ArgAction::Set)) .arg( multi_opt( "add", "LOGIN", "Name of a user or team to invite as an owner", ) .short('a'), ) .arg( multi_opt( "remove", "LOGIN", "Name of a user or team to remove as an owner", ) .short('r'), ) .arg(flag("list", "List owners of a crate").short('l')) .arg_index("Registry index URL to modify owners for") .arg_registry("Registry to modify owners for") .arg(opt("token", "API token to use when authenticating").value_name("TOKEN")) .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help owner` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let opts = OwnersOptions { krate: args.get_one::("crate").cloned(), token: args.get_one::("token").cloned().map(Secret::from), reg_or_index: args.registry_or_index(gctx)?, to_add: args .get_many::("add") .map(|xs| xs.cloned().collect()), to_remove: args .get_many::("remove") .map(|xs| xs.cloned().collect()), list: args.flag("list"), }; ops::modify_owners(gctx, &opts)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/package.rs ================================================ use crate::command_prelude::*; use cargo::ops; use cargo::ops::PackageMessageFormat; use cargo::ops::PackageOpts; use clap_complete::ArgValueCandidates; pub fn cli() -> Command { subcommand("package") .about("Assemble the local package into a distributable tarball") .arg_index("Registry index URL to prepare the package for") .arg_registry("Registry to prepare the package for") .arg( flag( "list", "Print files included in a package without making one", ) .short('l'), ) .arg(flag( "no-verify", "Don't verify the contents by building them", )) .arg(flag( "no-metadata", "Ignore warnings about a lack of human-usable metadata", )) .arg(flag( "allow-dirty", "Allow dirty working directories to be packaged", )) .arg(flag( "exclude-lockfile", "Don't include the lock file when packaging", )) .arg( opt("message-format", "Output representation (unstable)") .value_name("FMT") // This currently requires and only works with `--list`. .requires("list") .value_parser(PackageMessageFormat::POSSIBLE_VALUES), ) .arg_silent_suggestion() .arg_package_spec_no_all( "Package(s) to assemble", "Assemble all packages in the workspace", "Don't assemble specified packages", ArgValueCandidates::new(get_ws_member_candidates), ) .arg_features() .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_parallel() .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help package` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let reg_or_index = args.registry_or_index(gctx)?; let ws = args.workspace(gctx)?; if ws.root_maybe().is_embedded() { return Err(anyhow::format_err!( "{} is unsupported by `cargo package`", ws.root_manifest().display() ) .into()); } let specs = args.packages_from_flags()?; let fmt = if let Some(fmt) = args._value_of("message-format") { gctx.cli_unstable() .fail_if_stable_opt("--message-format", 15353)?; fmt.parse()? } else { PackageMessageFormat::Human }; ops::package( &ws, &PackageOpts { gctx, verify: !args.flag("no-verify"), list: args.flag("list"), fmt, check_metadata: !args.flag("no-metadata"), allow_dirty: args.flag("allow-dirty"), include_lockfile: !args.flag("exclude-lockfile"), to_package: specs, targets: args.targets()?, jobs: args.jobs()?, keep_going: args.keep_going(), cli_features: args.cli_features()?, reg_or_index, dry_run: false, }, )?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/pkgid.rs ================================================ use crate::command_prelude::*; use cargo::ops; use cargo::util::print_available_packages; pub fn cli() -> Command { subcommand("pkgid") .about("Print a fully qualified package specification") .arg(Arg::new("spec").value_name("SPEC").action(ArgAction::Set)) .arg_silent_suggestion() .arg_package("Argument to get the package ID specifier for") .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help pkgid` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; if args.is_present_with_zero_values("package") { print_available_packages(&ws)? } let spec = args .get_one::("spec") .or_else(|| args.get_one::("package")) .map(String::as_str); let spec = ops::pkgid(&ws, spec)?; cargo::drop_println!(gctx, "{}", spec); Ok(()) } ================================================ FILE: src/bin/cargo/commands/publish.rs ================================================ use crate::command_prelude::*; use cargo::ops::{self, PublishOpts}; use cargo_credential::Secret; use clap_complete::ArgValueCandidates; pub fn cli() -> Command { subcommand("publish") .about("Upload a package to the registry") .arg_dry_run("Perform all checks without uploading") .arg_index("Registry index URL to upload the package to") .arg_registry("Registry to upload the package to") .arg( opt("token", "Token to use when uploading") .value_name("TOKEN") .hide(true), ) .arg(flag( "no-verify", "Don't verify the contents by building them", )) .arg(flag( "allow-dirty", "Allow dirty working directories to be packaged", )) .arg_silent_suggestion() .arg_package_spec_no_all( "Package(s) to publish", "Publish all packages in the workspace", "Don't publish specified packages", ArgValueCandidates::new(get_ws_member_candidates), ) .arg_features() .arg_parallel() .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help publish` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let reg_or_index = args.registry_or_index(gctx)?; let ws = args.workspace(gctx)?; if ws.root_maybe().is_embedded() { return Err(anyhow::format_err!( "{} is unsupported by `cargo publish`", ws.root_manifest().display() ) .into()); } let token = args.get_one::("token").cloned().map(Secret::from); if token.is_some() { let _ = gctx.shell().warn("`cargo publish --token` is deprecated in favor of using `cargo login` and environment variables"); } ops::publish( &ws, &PublishOpts { gctx, token, reg_or_index, verify: !args.flag("no-verify"), allow_dirty: args.flag("allow-dirty"), to_publish: args.packages_from_flags()?, targets: args.targets()?, jobs: args.jobs()?, keep_going: args.keep_going(), dry_run: args.dry_run(), cli_features: args.cli_features()?, }, )?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/read_manifest.rs ================================================ //! Deprecated. use crate::command_prelude::*; pub fn cli() -> Command { subcommand("read-manifest") .hide(true) .about(color_print::cstr!( "\ DEPRECATED: Print a JSON representation of a Cargo.toml manifest. Use `cargo metadata --no-deps` instead.\ " )) .arg_silent_suggestion() .arg_manifest_path() } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; gctx.shell().print_json( &ws.current()? .serialized(gctx.cli_unstable(), ws.unstable_features()), )?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/remove.rs ================================================ use cargo::CargoResult; use cargo::core::PackageIdSpec; use cargo::core::PackageIdSpecQuery; use cargo::core::Resolve; use cargo::core::Workspace; use cargo::core::dependency::DepKind; use cargo::ops::cargo_remove::RemoveOptions; use cargo::ops::cargo_remove::remove; use cargo::ops::resolve_ws; use cargo::util::command_prelude::*; use cargo::util::print_available_packages; use cargo::util::toml_mut::dependency::Dependency; use cargo::util::toml_mut::dependency::MaybeWorkspace; use cargo::util::toml_mut::dependency::Source; use cargo::util::toml_mut::manifest::DepTable; use cargo::util::toml_mut::manifest::LocalManifest; pub fn cli() -> clap::Command { clap::Command::new("remove") // Subcommand aliases are handled in `aliased_command()`. // .alias("rm") .about("Remove dependencies from a Cargo.toml manifest file") .args([clap::Arg::new("dependencies") .action(clap::ArgAction::Append) .required(true) .num_args(1..) .value_name("DEP_ID") .help("Dependencies to be removed") .add(clap_complete::ArgValueCandidates::new( get_direct_dependencies_pkg_name_candidates, ))]) .arg_dry_run("Don't actually write the manifest") .arg_silent_suggestion() .next_help_heading("Section") .args([ clap::Arg::new("dev") .long("dev") .conflicts_with("build") .action(clap::ArgAction::SetTrue) .group("section") .help("Remove from dev-dependencies"), clap::Arg::new("build") .long("build") .conflicts_with("dev") .action(clap::ArgAction::SetTrue) .group("section") .help("Remove from build-dependencies"), clap::Arg::new("target") .long("target") .num_args(1) .value_name("TARGET") .value_parser(clap::builder::NonEmptyStringValueParser::new()) .help("Remove from target-dependencies"), ]) .arg_package("Package to remove from") .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help remove` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let dry_run = args.dry_run(); let workspace = args.workspace(gctx)?; if args.is_present_with_zero_values("package") { print_available_packages(&workspace)?; } let packages = args.packages_from_flags()?; let packages = packages.get_packages(&workspace)?; let spec = match packages.len() { 0 => { return Err(CliError::new( anyhow::format_err!( "no package selected to modify help: specify a package with `-p `" ), 101, )); } 1 => packages[0], _ => { let names = packages.iter().map(|p| p.name()).collect::>(); return Err(CliError::new( anyhow::format_err!( "no package selected to modify help: specify a package with `-p ` available packages: {}", names.join(", ") ), 101, )); } }; let dependencies = args .get_many::("dependencies") .expect("required(true)") .cloned() .collect::>(); let section = parse_section(args); let options = RemoveOptions { gctx, spec, dependencies, section, dry_run, }; remove(&options)?; if !dry_run { // Clean up the workspace gc_workspace(&workspace)?; // Reload the workspace since we've changed dependencies let ws = args.workspace(gctx)?; let resolve = { // HACK: Avoid unused patch warnings by temporarily changing the verbosity. // In rare cases, this might cause index update messages to not show up let verbosity = ws.gctx().shell().verbosity(); ws.gctx() .shell() .set_verbosity(cargo::core::Verbosity::Quiet); let resolve = resolve_ws(&ws, dry_run); ws.gctx().shell().set_verbosity(verbosity); resolve?.1 }; // Attempt to gc unused patches and re-resolve if anything is removed if gc_unused_patches(&workspace, &resolve)? { let ws = args.workspace(gctx)?; resolve_ws(&ws, dry_run)?; } } Ok(()) } fn parse_section(args: &ArgMatches) -> DepTable { let dev = args.flag("dev"); let build = args.flag("build"); let kind = if dev { DepKind::Development } else if build { DepKind::Build } else { DepKind::Normal }; let mut table = DepTable::new().set_kind(kind); if let Some(target) = args.get_one::("target") { assert!(!target.is_empty(), "Target specification may not be empty"); table = table.set_target(target); } table } /// Clean up the workspace.dependencies, profile, patch, and replace sections of the root manifest /// by removing dependencies which no longer have a reference to them. fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { let mut workspace_manifest = LocalManifest::try_new(workspace.root_manifest())?; let mut is_modified = true; let members = workspace .members() .map(|p| { Ok(( LocalManifest::try_new(p.manifest_path())?, p.manifest().unstable_features(), )) }) .collect::>>()?; let mut dependencies = members .into_iter() .flat_map(|(member_manifest, unstable_features)| { member_manifest .get_sections() .into_iter() .flat_map(move |(_, table)| { table .as_table_like() .unwrap() .iter() .map(|(key, item)| { Dependency::from_toml( workspace.gctx(), workspace.root(), &member_manifest.path, &unstable_features, key, item, ) }) .collect::>() }) }) .collect::>>()?; // Clean up the workspace.dependencies section and replace instances of // workspace dependencies with their definitions if let Some(toml_edit::Item::Table(deps_table)) = workspace_manifest .data .get_mut("workspace") .and_then(|t| t.get_mut("dependencies")) { deps_table.set_implicit(true); for (key, item) in deps_table.iter_mut() { let ws_dep = Dependency::from_toml( workspace.gctx(), workspace.root(), &workspace.root(), workspace.unstable_features(), key.get(), item, )?; // search for uses of this workspace dependency let mut is_used = false; for dep in dependencies.iter_mut().filter(|d| { d.toml_key() == key.get() && matches!(d.source(), Some(Source::Workspace(_))) }) { // HACK: Replace workspace references in `dependencies` to simplify later GC steps: // 1. Avoid having to look it up again to determine the dependency source / spec // 2. The entry might get deleted, preventing us from looking it up again // // This does lose extra information, like features enabled, but that shouldn't be a // problem for GC *dep = ws_dep.clone(); is_used = true; } if !is_used { *item = toml_edit::Item::None; is_modified = true; } } } // Clean up the profile section // // Example tables: // - profile.dev.package.foo // - profile.release.package."foo:2.1.0" if let Some(toml_edit::Item::Table(profile_section_table)) = workspace_manifest.data.get_mut("profile") { profile_section_table.set_implicit(true); for (_, item) in profile_section_table.iter_mut() { if let toml_edit::Item::Table(profile_table) = item { profile_table.set_implicit(true); if let Some(toml_edit::Item::Table(package_table)) = profile_table.get_mut("package") { package_table.set_implicit(true); for (key, item) in package_table.iter_mut() { let key = key.get(); // Skip globs. Can't do anything with them. // For example, profile.release.package."*". if crate::util::restricted_names::is_glob_pattern(key) { continue; } if !spec_has_match( &PackageIdSpec::parse(key)?, &dependencies, workspace.gctx(), )? { *item = toml_edit::Item::None; is_modified = true; } } } } } } // Clean up the replace section if let Some(toml_edit::Item::Table(table)) = workspace_manifest.data.get_mut("replace") { table.set_implicit(true); for (key, item) in table.iter_mut() { if !spec_has_match( &PackageIdSpec::parse(key.get())?, &dependencies, workspace.gctx(), )? { *item = toml_edit::Item::None; is_modified = true; } } } if is_modified { workspace_manifest.write()?; } Ok(()) } /// Check whether or not a package ID spec matches any non-workspace dependencies. fn spec_has_match( spec: &PackageIdSpec, dependencies: &[Dependency], gctx: &GlobalContext, ) -> CargoResult { for dep in dependencies { if spec.name() != &dep.name { continue; } let version_matches = match (spec.version(), dep.version()) { (Some(v), Some(vq)) => semver::VersionReq::parse(vq)?.matches(&v), (Some(_), None) => false, (None, None | Some(_)) => true, }; if !version_matches { continue; } match dep.source_id(gctx)? { MaybeWorkspace::Other(source_id) => { if spec.url().map(|u| u == source_id.url()).unwrap_or(true) { return Ok(true); } } MaybeWorkspace::Workspace(_) => {} } } Ok(false) } /// Removes unused patches from the manifest fn gc_unused_patches(workspace: &Workspace<'_>, resolve: &Resolve) -> CargoResult { let mut workspace_manifest = LocalManifest::try_new(workspace.root_manifest())?; let mut modified = false; // Clean up the patch section if let Some(toml_edit::Item::Table(patch_section_table)) = workspace_manifest.data.get_mut("patch") { patch_section_table.set_implicit(true); for (_, item) in patch_section_table.iter_mut() { if let toml_edit::Item::Table(patch_table) = item { patch_table.set_implicit(true); for (key, item) in patch_table.iter_mut() { let dep = Dependency::from_toml( workspace.gctx(), workspace.root(), &workspace.root_manifest(), workspace.unstable_features(), key.get(), item, )?; // Generate a PackageIdSpec url for querying let url = if let MaybeWorkspace::Other(source_id) = dep.source_id(workspace.gctx())? { format!("{}#{}", source_id.url(), dep.name) } else { continue; }; if PackageIdSpec::query_str(&url, resolve.unused_patches().iter().cloned()) .is_ok() { *item = toml_edit::Item::None; modified = true; } } } } } if modified { workspace_manifest.write()?; } Ok(modified) } ================================================ FILE: src/bin/cargo/commands/report.rs ================================================ use crate::command_prelude::*; use cargo::CargoResult; use cargo::core::compiler::future_incompat::OnDiskReports; use cargo::core::compiler::future_incompat::REPORT_PREAMBLE; use cargo::drop_println; use cargo::ops; pub fn cli() -> Command { subcommand("report") .about("Generate and display various kinds of reports") .after_help(color_print::cstr!( "Run `cargo help report` for more detailed information.\n" )) .subcommand_required(true) .arg_required_else_help(true) .subcommand( subcommand("future-incompatibilities") .alias("future-incompat") .about("Reports any crates which will eventually stop compiling") .arg( opt( "id", "identifier of the report generated by a Cargo command invocation", ) .value_name("id"), ) .arg_package("Package to display a report for") .after_help(color_print::cstr!( "Run `cargo help report future-incompatibilities` for more detailed information.\n" )), ) .subcommand( subcommand("timings") .about("Reports the build timings of previous sessions (unstable)") .arg_manifest_path() .arg(flag("open", "Opens the timing report in a browser")) .arg(opt("id", "Session ID to report on").value_name("ID")), ) .subcommand( subcommand("sessions") .about("Reports the previous sessions (unstable)") .arg_manifest_path() .arg( opt("limit", "Limit the number of results") .value_name("N") .value_parser(clap::value_parser!(u64).range(1..)) .default_value("10"), ), ) .subcommand( subcommand("rebuilds") .about("Reports rebuild reasons from previous sessions (unstable)") .arg_manifest_path() .arg(opt("id", "Session ID to report on").value_name("ID")), ) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { match args.subcommand() { Some(("future-incompatibilities", args)) => report_future_incompatibilities(gctx, args), Some(("timings", args)) => { gctx.cli_unstable().fail_if_stable_command( gctx, "report timings", 15844, "build-analysis", gctx.cli_unstable().build_analysis, )?; let opts = timings_opts(gctx, args)?; let ws = args.workspace(gctx).ok(); ops::report_timings(gctx, ws.as_ref(), opts)?; Ok(()) } Some(("sessions", args)) => { gctx.cli_unstable().fail_if_stable_command( gctx, "report sessions", 15844, "build-analysis", gctx.cli_unstable().build_analysis, )?; let ws = args.workspace(gctx).ok(); let opts = sessions_opts(args)?; ops::report_sessions(gctx, ws.as_ref(), opts)?; Ok(()) } Some(("rebuilds", args)) => { gctx.cli_unstable().fail_if_stable_command( gctx, "report rebuilds", 15844, "build-analysis", gctx.cli_unstable().build_analysis, )?; let ws = args.workspace(gctx).ok(); let opts = rebuilds_opts(args)?; ops::report_rebuilds(gctx, ws.as_ref(), opts)?; Ok(()) } Some((cmd, _)) => { unreachable!("unexpected command {}", cmd) } None => { unreachable!("unexpected command") } } } fn report_future_incompatibilities(gctx: &GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let reports = OnDiskReports::load(&ws)?; let id = args .value_of_u32("id")? .unwrap_or_else(|| reports.last_id()); let krate = args.get_one::("package").map(String::as_str); let report = reports.get_report(id, krate)?; drop_println!(gctx, "{}", REPORT_PREAMBLE); drop(gctx.shell().print_ansi_stdout(report.as_bytes())); Ok(()) } fn timings_opts<'a>( gctx: &'a GlobalContext, args: &ArgMatches, ) -> CargoResult> { let open_result = args.get_flag("open"); let id = args .get_one::("id") .map(|s| s.parse()) .transpose()?; Ok(ops::ReportTimingsOptions { open_result, gctx, id, }) } fn sessions_opts(args: &ArgMatches) -> CargoResult { let limit = *args.get_one::("limit").unwrap_or(&10); let limit = limit.min(usize::MAX as u64) as usize; Ok(ops::ReportSessionsOptions { limit }) } fn rebuilds_opts(args: &ArgMatches) -> CargoResult { let id = args .get_one::("id") .map(|s| s.parse()) .transpose()?; Ok(ops::ReportRebuildsOptions { id }) } ================================================ FILE: src/bin/cargo/commands/run.rs ================================================ use std::ffi::OsStr; use std::ffi::OsString; use std::path::Path; use crate::command_prelude::*; use crate::util::restricted_names::is_glob_pattern; use cargo::core::Verbosity; use cargo::core::Workspace; use cargo::ops::{self, CompileFilter, Packages}; use cargo::util::closest; use cargo_util::ProcessError; use itertools::Itertools as _; pub fn cli() -> Command { subcommand("run") // subcommand aliases are handled in aliased_command() // .alias("r") .about("Run a binary or example of the local package") .arg( Arg::new("args") .value_name("ARGS") .help("Arguments for the binary or example to run") .value_parser(value_parser!(OsString)) .num_args(0..) .trailing_var_arg(true), ) .arg_message_format() .arg_silent_suggestion() .arg_package("Package with the target to run") .arg_targets_bin_example( "Name of the bin target to run", "Name of the example target to run", ) .arg_features() .arg_parallel() .arg_release("Build artifacts in release mode, with optimizations") .arg_profile("Build artifacts with the specified profile") .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_manifest_path() .arg_ignore_rust_version() .arg_unit_graph() .arg_timings() .after_help(color_print::cstr!( "Run `cargo help run` for more detailed information.\n\ To pass `--help` to the specified binary, use `-- --help`.\n", )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let mut compile_opts = args.compile_options(gctx, UserIntent::Build, Some(&ws), ProfileChecking::Custom)?; // Disallow `spec` to be an glob pattern if let Packages::Packages(opt_in) = &compile_opts.spec { if let Some(pattern) = opt_in.iter().find(|s| is_glob_pattern(s)) { return Err(anyhow::anyhow!( "`cargo run` does not support glob pattern `{}` on package selection", pattern, ) .into()); } } if !args.contains_id("example") && !args.contains_id("bin") { let default_runs: Vec<_> = compile_opts .spec .get_packages(&ws)? .iter() .filter_map(|pkg| pkg.manifest().default_run()) .collect(); if let [bin] = &default_runs[..] { compile_opts.filter = CompileFilter::single_bin(bin.to_string()); } else { // ops::run will take care of errors if len pkgs != 1. compile_opts.filter = CompileFilter::Default { // Force this to false because the code in ops::run is not // able to pre-check features before compilation starts to // enforce that only 1 binary is built. required_features_filterable: false, }; } }; ops::run(&ws, &compile_opts, &values_os(args, "args")).map_err(|err| to_run_error(gctx, err)) } /// See also `util/toml/mod.rs`s `is_embedded` pub fn is_manifest_command(arg: &str) -> bool { let path = Path::new(arg); 1 < path.components().count() || path.extension() == Some(OsStr::new("rs")) } pub fn exec_manifest_command(gctx: &mut GlobalContext, cmd: &str, args: &[OsString]) -> CliResult { let manifest_path = Path::new(cmd); match (manifest_path.is_file(), gctx.cli_unstable().script) { (true, true) => {} (true, false) => { return Err(anyhow::anyhow!("running the file `{cmd}` requires `-Zscript`").into()); } (false, true) => { let possible_commands = crate::list_commands(gctx); let is_dir = if manifest_path.is_dir() { format!(": `{cmd}` is a directory") } else { "".to_owned() }; let suggested_command = if let Some(suggested_command) = possible_commands .keys() .filter(|c| cmd.starts_with(c.as_str())) .max_by_key(|c| c.len()) { let actual_args = cmd.strip_prefix(suggested_command).unwrap(); let args = if args.is_empty() { "".to_owned() } else { format!( " {}", args.into_iter().map(|os| os.to_string_lossy()).join(" ") ) }; format!( "\nhelp: there is a command with a similar name: `{suggested_command} {actual_args}{args}`" ) } else { "".to_owned() }; let suggested_script = if let Some(suggested_script) = suggested_script(cmd) { format!("\nhelp: there is a script with a similar name: `{suggested_script}`") } else { "".to_owned() }; return Err(anyhow::anyhow!( "no such file or subcommand `{cmd}`{is_dir}{suggested_command}{suggested_script}" ) .into()); } (false, false) => { // HACK: duplicating the above for minor tweaks but this will all go away on // stabilization let possible_commands = crate::list_commands(gctx); let suggested_command = if let Some(suggested_command) = possible_commands .keys() .filter(|c| cmd.starts_with(c.as_str())) .max_by_key(|c| c.len()) { let actual_args = cmd.strip_prefix(suggested_command).unwrap(); let args = if args.is_empty() { "".to_owned() } else { format!( " {}", args.into_iter().map(|os| os.to_string_lossy()).join(" ") ) }; format!( "\nhelp: there is a command with a similar name: `{suggested_command} {actual_args}{args}`" ) } else { "".to_owned() }; let suggested_script = if let Some(suggested_script) = suggested_script(cmd) { format!( "\nhelp: there is a script with a similar name: `{suggested_script}` (requires `-Zscript`)" ) } else { "".to_owned() }; return Err(anyhow::anyhow!( "no such subcommand `{cmd}`{suggested_command}{suggested_script}" ) .into()); } } let manifest_path = root_manifest(Some(manifest_path), gctx)?; // Treat `cargo foo.rs` like `cargo install --path foo` and re-evaluate the config based on the // location where the script resides, rather than the environment from where it's being run. let parent_path = manifest_path .parent() .expect("a file should always have a parent"); gctx.reload_rooted_at(parent_path)?; let mut ws = Workspace::new(&manifest_path, gctx)?; if gctx.cli_unstable().avoid_dev_deps { ws.set_require_optional_deps(false); } let mut compile_opts = cargo::ops::CompileOptions::new(gctx, cargo::core::compiler::UserIntent::Build)?; compile_opts.spec = cargo::ops::Packages::Default; cargo::ops::run(&ws, &compile_opts, args).map_err(|err| to_run_error(gctx, err)) } fn suggested_script(cmd: &str) -> Option { let cmd_path = Path::new(cmd); let mut suggestion = Path::new(".").to_owned(); for cmd_part in cmd_path.components() { let exact_match = suggestion.join(cmd_part); suggestion = if exact_match.exists() { exact_match } else { let possible: Vec<_> = std::fs::read_dir(suggestion) .into_iter() .flatten() .filter_map(|e| e.ok()) .map(|e| e.path()) .filter(|p| p.to_str().is_some()) .collect(); if let Some(possible) = closest( cmd_part.as_os_str().to_str().unwrap(), possible.iter(), |p| p.file_name().unwrap().to_str().unwrap(), ) { possible.to_owned() } else { return None; } }; } if suggestion.is_dir() { None } else { suggestion.into_os_string().into_string().ok() } } fn to_run_error(gctx: &GlobalContext, err: anyhow::Error) -> CliError { let proc_err = match err.downcast_ref::() { Some(e) => e, None => return CliError::new(err, 101), }; // If we never actually spawned the process then that sounds pretty // bad and we always want to forward that up. let exit_code = match proc_err.code { Some(exit) => exit, None => return CliError::new(err, 101), }; // If `-q` was passed then we suppress extra error information about // a failed process, we assume the process itself printed out enough // information about why it failed so we don't do so as well let is_quiet = gctx.shell().verbosity() == Verbosity::Quiet; if is_quiet { CliError::code(exit_code) } else { CliError::new(err, exit_code) } } ================================================ FILE: src/bin/cargo/commands/rustc.rs ================================================ use crate::command_prelude::*; use cargo::ops; const PRINT_ARG_NAME: &str = "print"; const CRATE_TYPE_ARG_NAME: &str = "crate-type"; pub fn cli() -> Command { subcommand("rustc") .about("Compile a package, and pass extra options to the compiler") .arg( Arg::new("args") .value_name("ARGS") .num_args(0..) .help("Extra rustc flags") .trailing_var_arg(true), ) .arg( opt( PRINT_ARG_NAME, "Output compiler information without compiling", ) .value_name("INFO"), ) .arg(multi_opt( CRATE_TYPE_ARG_NAME, "CRATE-TYPE", "Comma separated list of types of crates for the compiler to emit", )) .arg_future_incompat_report() .arg_message_format() .arg_silent_suggestion() .arg_package("Package to build") .arg_targets_all( "Build only this package's library", "Build only the specified binary", "Build all binaries", "Build only the specified example", "Build all examples", "Build only the specified test target", "Build all targets that have `test = true` set", "Build only the specified bench target", "Build all targets that have `bench = true` set", "Build all targets", ) .arg_features() .arg_parallel() .arg_release("Build artifacts in release mode, with optimizations") .arg_profile("Build artifacts with the specified profile") .arg_target_triple("Target triple which compiles will be for") .arg_target_dir() .arg_unit_graph() .arg_timings() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help rustc` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; // This is a legacy behavior that changes the behavior based on the profile. // If we want to support this more formally, I think adding a --mode flag // would be warranted. let intent = match args.get_one::("profile").map(String::as_str) { Some("test") => UserIntent::Test, Some("bench") => UserIntent::Bench, Some("check") => UserIntent::Check { test: false }, _ => UserIntent::Build, }; let mut compile_opts = args.compile_options_for_single_package( gctx, intent, Some(&ws), ProfileChecking::LegacyRustc, )?; if compile_opts.build_config.requested_profile == "check" { compile_opts.build_config.requested_profile = "dev".into(); } let target_args = values(args, "args"); compile_opts.target_rustc_args = if target_args.is_empty() { None } else { Some(target_args) }; if let Some(opt_value) = args.get_one::(PRINT_ARG_NAME) { gctx.cli_unstable() .fail_if_stable_opt(PRINT_ARG_NAME, 9357)?; ops::print(&ws, &compile_opts, opt_value)?; return Ok(()); } let crate_types = { let mut seen = std::collections::HashSet::new(); args.get_many::(CRATE_TYPE_ARG_NAME) .into_iter() .flatten() .flat_map(|s| s.split(',')) .filter(|s| !s.is_empty()) .map(String::from) .filter(|s| seen.insert(s.clone())) .collect::>() }; compile_opts.target_rustc_crate_types = if crate_types.is_empty() { None } else { Some(crate_types) }; ops::compile(&ws, &compile_opts)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/rustdoc.rs ================================================ use cargo::ops::{self, DocOptions, OutputFormat}; use crate::command_prelude::*; pub fn cli() -> Command { subcommand("rustdoc") .about("Build a package's documentation, using specified custom flags.") .arg( Arg::new("args") .value_name("ARGS") .help("Extra rustdoc flags") .num_args(0..) .trailing_var_arg(true), ) .arg(flag( "open", "Opens the docs in a browser after the operation", )) .arg_message_format() .arg_silent_suggestion() .arg_package("Package to document") .arg_targets_all( "Build only this package's library", "Build only the specified binary", "Build all binaries", "Build only the specified example", "Build all examples", "Build only the specified test target", "Build all targets that have `test = true` set", "Build only the specified bench target", "Build all targets that have `bench = true` set", "Build all targets", ) .arg_features() .arg_parallel() .arg_release("Build artifacts in release mode, with optimizations") .arg_profile("Build artifacts with the specified profile") .arg_target_triple("Build for the target triple") .arg_target_dir() .arg( opt("output-format", "The output type to write (unstable)") .value_name("FMT") .value_parser(OutputFormat::POSSIBLE_VALUES), ) .arg_unit_graph() .arg_timings() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help rustdoc` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let output_format = if let Some(output_format) = args._value_of("output-format") { gctx.cli_unstable() .fail_if_stable_opt("--output-format", 12103)?; output_format.parse()? } else { OutputFormat::Html }; let mut compile_opts = args.compile_options_for_single_package( gctx, UserIntent::Doc { deps: false, json: matches!(output_format, OutputFormat::Json), }, Some(&ws), ProfileChecking::Custom, )?; let target_args = values(args, "args"); compile_opts.target_rustdoc_args = if target_args.is_empty() { None } else { Some(target_args) }; let doc_opts = DocOptions { open_result: args.flag("open"), output_format, compile_opts, }; ops::doc(&ws, &doc_opts)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/search.rs ================================================ use crate::command_prelude::*; use std::cmp::min; use cargo::ops; pub fn cli() -> Command { subcommand("search") .about("Search packages in the registry. Default registry is crates.io") .arg(Arg::new("query").value_name("QUERY").num_args(0..)) .arg( opt( "limit", "Limit the number of results (default: 10, max: 100)", ) .value_name("LIMIT"), ) .arg_index("Registry index URL to search packages in") .arg_registry("Registry to search packages in") .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help search` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let reg_or_index = args.registry_or_index(gctx)?; let limit = args.value_of_u32("limit")?; let limit = min(100, limit.unwrap_or(10)); let query: Vec<&str> = args .get_many::("query") .unwrap_or_default() .map(String::as_str) .collect(); let query: String = query.join("+"); ops::search(&query, gctx, reg_or_index, limit)?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/test.rs ================================================ use crate::command_prelude::*; use cargo::ops; pub fn cli() -> Command { subcommand("test") // Subcommand aliases are handled in `aliased_command()`. // .alias("t") .about("Execute all unit and integration tests and build examples of a local package") .arg( Arg::new("TESTNAME") .action(ArgAction::Set) .help("If specified, only run tests containing this string in their names"), ) .arg( Arg::new("args") .value_name("ARGS") .help("Arguments for the test binary") .num_args(0..) .last(true), ) .arg(flag("no-run", "Compile, but don't run tests")) .arg(flag("no-fail-fast", "Run all tests regardless of failure")) .arg_future_incompat_report() .arg_message_format() .arg( flag( "quiet", "Display one character per test instead of one line", ) .short('q'), ) .arg_package_spec( "Package to run tests for", "Test all packages in the workspace", "Exclude packages from the test", ) .arg_targets_all( "Test only this package's library", "Test only the specified binary", "Test all binaries", "Test only the specified example", "Test all examples", "Test only the specified test target", "Test all targets that have `test = true` set", "Test only the specified bench target", "Test all targets that have `bench = true` set", "Test all targets (does not include doctests)", ) .arg( flag("doc", "Test only this library's documentation") .help_heading(heading::TARGET_SELECTION), ) .arg_features() .arg_jobs() .arg_unsupported_keep_going() .arg_release("Build artifacts in release mode, with optimizations") .arg_profile("Build artifacts with the specified profile") .arg_target_triple("Build for the target triple") .arg_target_dir() .arg_unit_graph() .arg_timings() .arg_manifest_path() .arg_ignore_rust_version() .after_help(color_print::cstr!( "Run `cargo help test` for more detailed information.\n\ Run `cargo test -- --help` for test binary options.\n", )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let ws = args.workspace(gctx)?; let mut compile_opts = args.compile_options(gctx, UserIntent::Test, Some(&ws), ProfileChecking::Custom)?; compile_opts.build_config.requested_profile = args.get_profile_name("test", ProfileChecking::Custom)?; // `TESTNAME` is actually an argument of the test binary, but it's // important, so we explicitly mention it and reconfigure. let test_name = args.get_one::("TESTNAME"); let test_args = args.get_one::("TESTNAME").into_iter(); let test_args = test_args.chain(args.get_many::("args").unwrap_or_default()); let test_args = test_args.map(String::as_str).collect::>(); let no_run = args.flag("no-run"); let doc = args.flag("doc"); if doc { if compile_opts.filter.is_specific() { return Err( anyhow::format_err!("can't mix --doc with other target selecting options").into(), ); } if no_run { return Err(anyhow::format_err!("can't skip running doc tests with --no-run").into()); } compile_opts.build_config.intent = UserIntent::Doctest; compile_opts.filter = ops::CompileFilter::lib_only(); } else if test_name.is_some() && !compile_opts.filter.is_specific() { // If arg `TESTNAME` is provided, assumed that the user knows what // exactly they wants to test, so we use `all_test_targets` to // avoid compiling unnecessary targets such as examples, which are // included by the logic of default target filter. compile_opts.filter = ops::CompileFilter::all_test_targets(); } let ops = ops::TestOptions { no_run, no_fail_fast: args.flag("no-fail-fast"), compile_opts, }; ops::run_tests(&ws, &ops, &test_args) } ================================================ FILE: src/bin/cargo/commands/tree.rs ================================================ use crate::cli; use crate::command_prelude::*; use annotate_snippets::Level; use anyhow::{bail, format_err}; use cargo::core::dependency::DepKind; use cargo::ops::Packages; use cargo::ops::tree::{self, DisplayDepth, EdgeKind}; use cargo::util::CargoResult; use cargo::util::print_available_packages; use clap_complete::ArgValueCandidates; use std::collections::HashSet; use std::str::FromStr; pub fn cli() -> Command { subcommand("tree") .about("Display a tree visualization of a dependency graph") .arg( flag("all", "Deprecated, use --no-dedupe instead") .short('a') .hide(true), ) .arg_silent_suggestion() .arg(flag("no-dev-dependencies", "Deprecated, use -e=no-dev instead").hide(true)) .arg( multi_opt("edges", "KINDS", "The kinds of dependencies to display") .short('e') .value_delimiter(',') .value_parser([ "all", "normal", "build", "dev", "features", "public", "no-normal", "no-build", "no-dev", "no-proc-macro", ]), ) .arg( optional_multi_opt( "invert", "SPEC", "Invert the tree direction and focus on the given package", ) .short('i') .add(clap_complete::ArgValueCandidates::new( get_pkg_id_spec_candidates, )), ) .arg( multi_opt( "prune", "SPEC", "Prune the given package from the display of the dependency tree", ) .add(clap_complete::ArgValueCandidates::new( get_pkg_id_spec_candidates, )), ) .arg(opt("depth", "Maximum display depth of the dependency tree").value_name("DEPTH")) .arg(flag("no-indent", "Deprecated, use --prefix=none instead").hide(true)) .arg(flag("prefix-depth", "Deprecated, use --prefix=depth instead").hide(true)) .arg( opt( "prefix", "Change the prefix (indentation) of how each entry is displayed", ) .value_name("PREFIX") .value_parser(["depth", "indent", "none"]) .default_value("indent"), ) .arg(flag( "no-dedupe", "Do not de-duplicate (repeats all shared dependencies)", )) .arg( flag( "duplicates", "Show only dependencies which come in multiple versions (implies -i)", ) .short('d') .alias("duplicate"), ) .arg( opt("charset", "Character set to use in output") .value_name("CHARSET") .value_parser(["utf8", "ascii"]), ) .arg( opt("format", "Format string used for printing dependencies") .value_name("FORMAT") .short('f') .default_value("{p}"), ) .arg( // Backwards compatibility with old cargo-tree. flag("version", "Print version info and exit") .short('V') .hide(true), ) .arg_package_spec_no_all( "Package to be used as the root of the tree", "Display the tree for all packages in the workspace", "Exclude specific workspace members", ArgValueCandidates::new(get_pkg_id_spec_candidates), ) .arg_features() .arg(flag("all-targets", "Deprecated, use --target=all instead").hide(true)) .arg_target_triple_with_candidates( "Filter dependencies matching the given target-triple (default host platform). \ Pass `all` to include all targets.", ArgValueCandidates::new(get_target_triples_with_all), ) .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help tree` for more detailed information.\n" )) } #[derive(Copy, Clone)] pub enum Charset { Utf8, Ascii, } impl FromStr for Charset { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "utf8" => Ok(Charset::Utf8), "ascii" => Ok(Charset::Ascii), _ => Err("invalid charset"), } } } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { if args.flag("version") { let verbose = args.verbose() > 0; let version = cli::get_version_string(verbose); cargo::drop_print!(gctx, "{}", version); return Ok(()); } let prefix = if args.flag("no-indent") { gctx.shell() .warn("the --no-indent flag has been changed to --prefix=none")?; "none" } else if args.flag("prefix-depth") { gctx.shell() .warn("the --prefix-depth flag has been changed to --prefix=depth")?; "depth" } else { args.get_one::("prefix").unwrap().as_str() }; let prefix = tree::Prefix::from_str(prefix).map_err(|e| anyhow::anyhow!("{}", e))?; let no_dedupe = args.flag("no-dedupe") || args.flag("all"); if args.flag("all") { gctx.shell().print_report( &[Level::WARNING .secondary_title( "the `cargo tree` --all flag has been changed to --no-dedupe, \ and may be removed in a future version", ) .element(Level::HELP.message( "if you are looking to display all workspace members, use the --workspace flag", ))], false, )?; } let targets = if args.flag("all-targets") { gctx.shell() .warn("the --all-targets flag has been changed to --target=all")?; vec!["all".to_string()] } else { args.targets()? }; let target = tree::Target::from_cli(targets); let (edge_kinds, no_proc_macro, public) = parse_edge_kinds(gctx, args)?; let graph_features = edge_kinds.contains(&EdgeKind::Feature); let pkgs_to_prune = args._values_of("prune"); let display_depth = args ._value_of("depth") .map(|s| s.parse::()) .transpose()? .unwrap_or(DisplayDepth::MaxDisplayDepth(u32::MAX)); let packages = args.packages_from_flags()?; let mut invert = args .get_many::("invert") .map_or_else(|| Vec::new(), |is| is.map(|s| s.to_string()).collect()); if args.is_present_with_zero_values("invert") { match &packages { Packages::Packages(ps) => { // Backwards compatibility with old syntax of `cargo tree -i -p foo`. invert.extend(ps.clone()); } _ => { return Err(format_err!( "The `-i` flag requires a package name.\n\ \n\ The `-i` flag is used to inspect the reverse dependencies of a specific\n\ package. It will invert the tree and display the packages that depend on the\n\ given package.\n\ \n\ Note that in a workspace, by default it will only display the package's\n\ reverse dependencies inside the tree of the workspace member in the current\n\ directory. The --workspace flag can be used to extend it so that it will show\n\ the package's reverse dependencies across the entire workspace. The -p flag\n\ can be used to display the package's reverse dependencies only with the\n\ subtree of the package given to -p.\n\ " ) .into()); } } } let ws = args.workspace(gctx)?; if args.is_present_with_zero_values("package") { print_available_packages(&ws)?; } let charset = args.get_one::("charset"); if let Some(charset) = charset .map(|c| Charset::from_str(c)) .transpose() .map_err(|e| anyhow::anyhow!("{}", e))? { match charset { Charset::Utf8 => gctx.shell().set_unicode(true)?, Charset::Ascii => gctx.shell().set_unicode(false)?, } } let opts = tree::TreeOptions { cli_features: args.cli_features()?, packages, target, edge_kinds, invert, pkgs_to_prune, prefix, no_dedupe, duplicates: args.flag("duplicates"), format: args.get_one::("format").cloned().unwrap(), graph_features, display_depth, no_proc_macro, public, }; if opts.graph_features && opts.duplicates { return Err(format_err!("the `-e features` flag does not support `--duplicates`").into()); } tree::build_and_print(&ws, &opts)?; Ok(()) } /// Parses `--edges` option. /// /// Returns a tuple of `EdgeKind` map and `no_proc_marco` flag. fn parse_edge_kinds( gctx: &GlobalContext, args: &ArgMatches, ) -> CargoResult<(HashSet, bool, bool)> { let (kinds, no_proc_macro, public) = { let mut no_proc_macro = false; let mut public = false; let mut kinds = args.get_many::("edges").map_or_else( || Vec::new(), |es| { es.map(|e| e.as_str()) .filter(|e| { if *e == "no-proc-macro" { no_proc_macro = true; false } else if *e == "public" { public = true; false } else { true } }) .collect() }, ); if args.flag("no-dev-dependencies") { gctx.shell() .warn("the --no-dev-dependencies flag has changed to -e=no-dev")?; kinds.push("no-dev"); } if kinds.is_empty() { kinds.extend(&["normal", "build", "dev"]); } if public && !gctx.cli_unstable().unstable_options { anyhow::bail!("`--edges public` requires `-Zunstable-options`"); } (kinds, no_proc_macro, public) }; let mut result = HashSet::new(); let insert_defaults = |result: &mut HashSet| { result.insert(EdgeKind::Dep(DepKind::Normal)); result.insert(EdgeKind::Dep(DepKind::Build)); result.insert(EdgeKind::Dep(DepKind::Development)); }; if kinds.iter().any(|k| k.starts_with("no-")) { insert_defaults(&mut result); for kind in &kinds { match *kind { "no-normal" => result.remove(&EdgeKind::Dep(DepKind::Normal)), "no-build" => result.remove(&EdgeKind::Dep(DepKind::Build)), "no-dev" => result.remove(&EdgeKind::Dep(DepKind::Development)), "features" => result.insert(EdgeKind::Feature), "normal" | "build" | "dev" | "all" => { bail!( "`{}` dependency kind cannot be mixed with \ \"no-normal\", \"no-build\", or \"no-dev\" \ dependency kinds", kind ) } _ => unreachable!("`{kind}` was validated by clap"), }; } return Ok((result, no_proc_macro, public)); } for kind in &kinds { match *kind { "all" => { insert_defaults(&mut result); result.insert(EdgeKind::Feature); } "features" => { result.insert(EdgeKind::Feature); } "normal" => { result.insert(EdgeKind::Dep(DepKind::Normal)); } "build" => { result.insert(EdgeKind::Dep(DepKind::Build)); } "dev" => { result.insert(EdgeKind::Dep(DepKind::Development)); } _ => unreachable!("`{kind}` was validated by clap"), } } if kinds.len() == 1 && kinds[0] == "features" { insert_defaults(&mut result); } Ok((result, no_proc_macro, public)) } ================================================ FILE: src/bin/cargo/commands/uninstall.rs ================================================ use crate::command_prelude::*; use cargo::{CargoResult, core::PackageId, ops}; use clap_complete::ArgValueCandidates; use std::collections::BTreeSet; pub fn cli() -> Command { subcommand("uninstall") .about("Remove a Rust binary") .arg( Arg::new("spec") .value_name("SPEC") .num_args(0..) .add::(clap_complete::ArgValueCandidates::new( || get_installed_crates(), )), ) .arg(opt("root", "Directory to uninstall packages from").value_name("DIR")) .arg_silent_suggestion() .arg_package_spec_simple( "Package to uninstall", ArgValueCandidates::new(get_installed_package_candidates), ) .arg( multi_opt("bin", "NAME", "Only uninstall the binary NAME") .help_heading(heading::TARGET_SELECTION), ) .after_help(color_print::cstr!( "Run `cargo help uninstall` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let root = args.get_one::("root").map(String::as_str); if args.is_present_with_zero_values("package") { return Err(anyhow::anyhow!( "\"--package \" requires a SPEC format value.\n\ Run `cargo help pkgid` for more information about SPEC format." ) .into()); } let specs = args .get_many::("spec") .unwrap_or_else(|| args.get_many::("package").unwrap_or_default()) .map(String::as_str) .collect(); ops::uninstall(root, specs, &values(args, "bin"), gctx)?; Ok(()) } fn get_installed_crates() -> Vec { get_installed_crates_().unwrap_or_default() } fn get_installed_crates_() -> Option> { let mut candidates = Vec::new(); let gctx = new_gctx_for_completions().ok()?; let root = ops::resolve_root(None, &gctx).ok()?; let tracker = ops::InstallTracker::load(&gctx, &root).ok()?; for (_, v) in tracker.all_installed_bins() { for bin in v { candidates.push(clap_complete::CompletionCandidate::new(bin)); } } Some(candidates) } fn get_installed_package_candidates() -> Vec { get_installed_packages() .unwrap_or_default() .into_iter() .map(|(pkg, bins)| { let single_binary = bins.iter().next().take_if(|_| bins.len() == 1); let help = if single_binary.is_some_and(|bin| bin == pkg.name().as_str()) { None } else { let binaries = bins.into_iter().collect::>().as_slice().join(", "); Some(binaries) }; clap_complete::CompletionCandidate::new(pkg.name().as_str()).help(help.map(From::from)) }) .collect() } fn get_installed_packages() -> CargoResult)>> { let gctx = new_gctx_for_completions()?; let root = ops::resolve_root(None, &gctx)?; let tracker = ops::InstallTracker::load(&gctx, &root)?; Ok(tracker .all_installed_bins() .map(|(package_id, bins)| (*package_id, bins.clone())) .collect()) } ================================================ FILE: src/bin/cargo/commands/update.rs ================================================ use crate::command_prelude::*; use anyhow::anyhow; use cargo::ops::{self, UpdateOptions}; use cargo::util::print_available_packages; pub fn cli() -> Command { subcommand("update") .about("Update dependencies as recorded in the local lock file") .args([clap::Arg::new("package2") .action(clap::ArgAction::Append) .num_args(1..) .value_name("SPEC") .help_heading(heading::PACKAGE_SELECTION) .group("package-group") .help("Package to update") .add(clap_complete::ArgValueCandidates::new( get_pkg_id_spec_candidates, ))]) .arg( optional_multi_opt("package", "SPEC", "Package to update") .short('p') .hide(true) .help_heading(heading::PACKAGE_SELECTION) .group("package-group"), ) .arg_dry_run("Don't actually write the lockfile") .arg( flag( "recursive", "Force updating all dependencies of [SPEC]... as well", ) .alias("aggressive") .conflicts_with("precise"), ) .arg( opt("precise", "Update [SPEC] to exactly PRECISE") .value_name("PRECISE") .requires("package-group"), ) .arg( flag( "breaking", "Update [SPEC] to latest SemVer-breaking version (unstable)", ) .short('b'), ) .arg_silent_suggestion() .arg( flag("workspace", "Only update the workspace packages") .short('w') .help_heading(heading::PACKAGE_SELECTION), ) .arg_manifest_path() .arg_ignore_rust_version_with_help("Ignore `rust-version` specification in packages") .after_help(color_print::cstr!( "Run `cargo help update` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let mut ws = args.workspace(gctx)?; if args.is_present_with_zero_values("package") { print_available_packages(&ws)?; } let to_update = if args.contains_id("package") { "package" } else { "package2" }; let to_update = values(args, to_update); for crate_name in to_update.iter() { if let Some(toolchain) = crate_name.strip_prefix("+") { return Err(anyhow!( "invalid character `+` in package name: `+{toolchain}` Use `cargo +{toolchain} update` if you meant to use the `{toolchain}` toolchain." ) .into()); } } let update_opts = UpdateOptions { recursive: args.flag("recursive"), precise: args.get_one::("precise").map(String::as_str), to_update, dry_run: args.dry_run(), workspace: args.flag("workspace"), gctx, }; if args.flag("breaking") { gctx.cli_unstable() .fail_if_stable_opt("--breaking", 12425)?; let upgrades = ops::upgrade_manifests(&mut ws, &update_opts.to_update)?; ops::resolve_ws(&ws, update_opts.dry_run)?; ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?; if update_opts.dry_run { update_opts .gctx .shell() .warn("aborting update due to dry run")?; } } else { ops::update_lockfile(&ws, &update_opts)?; } Ok(()) } ================================================ FILE: src/bin/cargo/commands/vendor.rs ================================================ use crate::command_prelude::*; use cargo::ops; use std::path::PathBuf; pub fn cli() -> Command { subcommand("vendor") .about("Vendor all dependencies for a project locally") .arg( Arg::new("path") .action(ArgAction::Set) .value_parser(clap::value_parser!(PathBuf)) .help("Where to vendor crates (`vendor` by default)"), ) .arg(flag( "no-delete", "Don't delete older crates in the vendor directory", )) .arg( Arg::new("tomls") .short('s') .long("sync") .help("Additional `Cargo.toml` to sync and vendor") .value_name("TOML") .value_parser(clap::value_parser!(PathBuf)) .action(clap::ArgAction::Append), ) .arg(flag( "respect-source-config", "Respect `[source]` config in `.cargo/config`", )) .arg(flag( "versioned-dirs", "Always include version in subdir name", )) .arg(unsupported("no-merge-sources")) .arg(unsupported("relative-path")) .arg(unsupported("only-git-deps")) .arg(unsupported("disallow-duplicates")) .arg_manifest_path() .after_help(color_print::cstr!( "Run `cargo help vendor` for more detailed information.\n" )) } fn unsupported(name: &'static str) -> Arg { // When we moved `cargo vendor` into Cargo itself we didn't stabilize a few // flags, so try to provide a helpful error message in that case to ensure // that users currently using the flag aren't tripped up. let value_parser = clap::builder::UnknownArgumentValueParser::suggest("the crates.io `cargo vendor` command has been merged into Cargo") .and_suggest(format!("and the flag `--{name}` isn't supported currently")) .and_suggest("to continue using the flag, execute `cargo-vendor vendor ...`") .and_suggest("to suggest this flag supported in Cargo, file an issue at "); flag(name, "").value_parser(value_parser).hide(true) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { // We're doing the vendoring operation ourselves, so we don't actually want // to respect any of the `source` configuration in Cargo itself. That's // intended for other consumers of Cargo, but we want to go straight to the // source, e.g. crates.io, to fetch crates. let respect_source_config = args.flag("respect-source-config"); if !respect_source_config { gctx.values_mut()?.remove("source"); } let ws = args.workspace(gctx)?; let path = args .get_one::("path") .cloned() .unwrap_or_else(|| PathBuf::from("vendor")); ops::vendor( &ws, &ops::VendorOptions { no_delete: args.flag("no-delete"), destination: &path, versioned_dirs: args.flag("versioned-dirs"), extra: args .get_many::("tomls") .unwrap_or_default() .cloned() .collect(), respect_source_config, }, )?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/verify_project.rs ================================================ //! Deprecated. use crate::command_prelude::*; use std::collections::HashMap; use std::process; pub fn cli() -> Command { subcommand("verify-project") .hide(true) .about( "\ DEPRECATED: Check correctness of crate manifest. See https://github.com/rust-lang/cargo/issues/14679.", ) .arg_silent_suggestion() .arg_manifest_path() } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { if let Err(e) = args.workspace(gctx) { gctx.shell() .print_json(&HashMap::from([("invalid", e.to_string())]))?; process::exit(1) } gctx.shell() .print_json(&HashMap::from([("success", "true")]))?; Ok(()) } ================================================ FILE: src/bin/cargo/commands/version.rs ================================================ use crate::cli; use crate::command_prelude::*; pub fn cli() -> Command { subcommand("version") .about("Show version information") .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help version` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let verbose = args.verbose() > 0; let version = cli::get_version_string(verbose); cargo::drop_print!(gctx, "{}", version); Ok(()) } ================================================ FILE: src/bin/cargo/commands/yank.rs ================================================ use crate::command_prelude::*; use anyhow::Context; use cargo::ops; use cargo_credential::Secret; pub fn cli() -> Command { subcommand("yank") .about("Remove a pushed crate from the index") .arg(Arg::new("crate").value_name("CRATE").action(ArgAction::Set)) .arg( opt("version", "The version to yank or un-yank") .alias("vers") .value_name("VERSION"), ) .arg(flag( "undo", "Undo a yank, putting a version back into the index", )) .arg_index("Registry index URL to yank from") .arg_registry("Registry to yank from") .arg(opt("token", "API token to use when authenticating").value_name("TOKEN")) .arg_silent_suggestion() .after_help(color_print::cstr!( "Run `cargo help yank` for more detailed information.\n" )) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let (krate, version) = resolve_crate( args.get_one::("crate").map(String::as_str), args.get_one::("version").map(String::as_str), )?; if version.is_none() { return Err(anyhow::format_err!("`--version` is required").into()); } ops::yank( gctx, krate.map(|s| s.to_string()), version.map(|s| s.to_string()), args.get_one::("token").cloned().map(Secret::from), args.registry_or_index(gctx)?, args.flag("undo"), )?; Ok(()) } fn resolve_crate<'k>( mut krate: Option<&'k str>, mut version: Option<&'k str>, ) -> crate::CargoResult<(Option<&'k str>, Option<&'k str>)> { if let Some((k, v)) = krate.and_then(|k| k.split_once('@')) { if version.is_some() { anyhow::bail!("cannot specify both `@{v}` and `--version`"); } if k.is_empty() { // by convention, arguments starting with `@` are response files anyhow::bail!("missing crate name for `@{v}`"); } krate = Some(k); version = Some(v); } if let Some(version) = version { semver::Version::parse(version).with_context(|| { if let Some(stripped) = version.strip_prefix("v") { return format!( "the version provided, `{version}` is not a \ valid SemVer version\n\n\ help: try changing the version to `{stripped}`", ); } format!("invalid version `{version}`") })?; } Ok((krate, version)) } ================================================ FILE: src/bin/cargo/main.rs ================================================ use cargo::core::features; use cargo::core::shell::Shell; use cargo::util::network::http::http_handle; use cargo::util::network::http::needs_custom_http_transport; use cargo::util::{self, CargoResult, closest_msg, command_prelude}; use cargo_util::{ProcessBuilder, ProcessError}; use cargo_util_schemas::manifest::StringOrVec; use std::collections::BTreeMap; use std::env; use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; mod cli; mod commands; use crate::command_prelude::*; fn main() { let _guard = setup_logger(); let mut gctx = match GlobalContext::default() { Ok(gctx) => gctx, Err(e) => { let mut shell = Shell::new(); cargo::exit_with_error(e.into(), &mut shell) } }; let nightly_features_allowed = matches!(&*features::channel(), "nightly" | "dev"); if nightly_features_allowed { let _span = tracing::span!(tracing::Level::TRACE, "completions").entered(); let args = std::env::args_os(); let current_dir = std::env::current_dir().ok(); let completer = clap_complete::CompleteEnv::with_factory(|| { let mut gctx = GlobalContext::default().expect("already loaded without errors"); cli::cli(&mut gctx) }) .var("CARGO_COMPLETE"); if completer .try_complete(args, current_dir.as_deref()) .unwrap_or_else(|e| { let mut shell = Shell::new(); cargo::exit_with_error(e.into(), &mut shell) }) { return; } } let result = if let Some(lock_addr) = cargo::ops::fix_get_proxy_lock_addr() { cargo::ops::fix_exec_rustc(&gctx, &lock_addr).map_err(|e| CliError::from(e)) } else { let _token = cargo::util::job::setup(); cli::main(&mut gctx) }; match result { Err(e) => cargo::exit_with_error(e, &mut *gctx.shell()), Ok(()) => {} } } fn setup_logger() -> Option { use tracing_subscriber::prelude::*; let env = tracing_subscriber::EnvFilter::from_env("CARGO_LOG"); let fmt_layer = tracing_subscriber::fmt::layer() .with_timer(tracing_subscriber::fmt::time::Uptime::default()) .with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr())) .with_writer(std::io::stderr) .with_filter(env); let (profile_layer, profile_guard) = chrome_layer(); let registry = tracing_subscriber::registry() .with(fmt_layer) .with(profile_layer); registry.init(); tracing::trace!(start = jiff::Timestamp::now().to_string()); profile_guard } #[cfg(target_has_atomic = "64")] type ChromeFlushGuard = tracing_chrome::FlushGuard; #[cfg(target_has_atomic = "64")] fn chrome_layer() -> ( Option>, Option, ) where S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync, { #![expect(clippy::disallowed_methods, reason = "runs before config is loaded")] if env_to_bool(std::env::var_os("CARGO_LOG_PROFILE").as_deref()) { let capture_args = env_to_bool(std::env::var_os("CARGO_LOG_PROFILE_CAPTURE_ARGS").as_deref()); let (layer, guard) = tracing_chrome::ChromeLayerBuilder::new() .include_args(capture_args) .build(); (Some(layer), Some(guard)) } else { (None, None) } } #[cfg(not(target_has_atomic = "64"))] type ChromeFlushGuard = (); #[cfg(not(target_has_atomic = "64"))] fn chrome_layer() -> ( Option, Option, ) { (None, None) } #[cfg(target_has_atomic = "64")] fn env_to_bool(os: Option<&OsStr>) -> bool { match os.and_then(|os| os.to_str()) { Some("1") | Some("true") => true, _ => false, } } /// Table for defining the aliases which come builtin in `Cargo`. /// The contents are structured as: `(alias, aliased_command, description)`. const BUILTIN_ALIASES: [(&str, &str, &str); 6] = [ ("b", "build", "alias: build"), ("c", "check", "alias: check"), ("d", "doc", "alias: doc"), ("r", "run", "alias: run"), ("t", "test", "alias: test"), ("rm", "remove", "alias: remove"), ]; /// Function which contains the list of all of the builtin aliases and it's /// corresponding execs represented as &str. fn builtin_aliases_execs(cmd: &str) -> Option<&(&str, &str, &str)> { BUILTIN_ALIASES.iter().find(|alias| alias.0 == cmd) } /// Resolve the aliased command from the [`GlobalContext`] with a given command string. /// /// The search fallback chain is: /// /// 1. Get the aliased command as a string. /// 2. If an `Err` occurs (missing key, type mismatch, or any possible error), /// try to get it as an array again. /// 3. If still cannot find any, finds one insides [`BUILTIN_ALIASES`]. fn aliased_command(gctx: &GlobalContext, command: &str) -> CargoResult>> { let alias_name = format!("alias.{}", command); let user_alias = match gctx.get_string(&alias_name) { Ok(Some(record)) => Some( record .val .split_whitespace() .map(|s| s.to_string()) .collect(), ), Ok(None) => None, Err(_) => gctx.get::>>(&alias_name)?, }; let result = user_alias.or_else(|| { builtin_aliases_execs(command).map(|command_str| vec![command_str.1.to_string()]) }); if result .as_ref() .map(|alias| alias.is_empty()) .unwrap_or_default() { anyhow::bail!("subcommand is required, but `{alias_name}` is empty"); } Ok(result) } /// List all runnable commands fn list_commands(gctx: &GlobalContext) -> BTreeMap { let mut commands = third_party_subcommands(gctx); for cmd in commands::builtin() { commands.insert( cmd.get_name().to_string(), CommandInfo::BuiltIn { about: cmd.get_about().map(|s| s.to_string()), }, ); } // Add the builtin_aliases and them descriptions to the // `commands` `BTreeMap`. for command in &BUILTIN_ALIASES { commands.insert( command.0.to_string(), CommandInfo::BuiltIn { about: Some(command.2.to_string()), }, ); } // Add the user-defined aliases let alias_commands = user_defined_aliases(gctx); commands.extend(alias_commands); // `help` is special, so it needs to be inserted separately. commands.insert( "help".to_string(), CommandInfo::BuiltIn { about: Some("Displays help for a cargo command".to_string()), }, ); commands } fn third_party_subcommands(gctx: &GlobalContext) -> BTreeMap { let prefix = "cargo-"; let suffix = env::consts::EXE_SUFFIX; let mut commands = BTreeMap::new(); for dir in search_directories(gctx) { let entries = match fs::read_dir(dir) { Ok(entries) => entries, _ => continue, }; for entry in entries.filter_map(|e| e.ok()) { let path = entry.path(); let Some(filename) = path.file_name().and_then(|s| s.to_str()) else { continue; }; let Some(name) = filename .strip_prefix(prefix) .and_then(|s| s.strip_suffix(suffix)) else { continue; }; if is_executable(entry.path()) { commands.insert( name.to_string(), CommandInfo::External { path: path.clone() }, ); } } } commands } fn user_defined_aliases(gctx: &GlobalContext) -> BTreeMap { let mut commands = BTreeMap::new(); if let Ok(aliases) = gctx.get::>("alias") { for (name, target) in aliases.iter() { commands.insert( name.to_string(), CommandInfo::Alias { target: target.clone(), }, ); } } commands } fn find_external_subcommand(gctx: &GlobalContext, cmd: &str) -> Option { let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX); search_directories(gctx) .iter() .map(|dir| dir.join(&command_exe)) .find(|file| is_executable(file)) } fn execute_external_subcommand(gctx: &GlobalContext, cmd: &str, args: &[&OsStr]) -> CliResult { let path = find_external_subcommand(gctx, cmd); let command = match path { Some(command) => command, None => { let script_suggestion = if gctx.cli_unstable().script && std::path::Path::new(cmd).is_file() { let sep = std::path::MAIN_SEPARATOR; format!( "\nhelp: to run the file `{cmd}`, provide a relative path like `.{sep}{cmd}`" ) } else { "".to_owned() }; let err = if cmd.starts_with('+') { anyhow::format_err!( "no such command: `{cmd}`\n\n\ help: invoke `cargo` through `rustup` to handle `+toolchain` directives{script_suggestion}", ) } else { let suggestions = list_commands(gctx); let did_you_mean = closest_msg(cmd, suggestions.keys(), |c| c, "command"); anyhow::format_err!( "no such command: `{cmd}`{did_you_mean}\n\n\ help: view all installed commands with `cargo --list`\n\ help: find a package to install `{cmd}` with `cargo search cargo-{cmd}`{script_suggestion}", ) }; return Err(CliError::new(err, 101)); } }; execute_subcommand(gctx, Some(&command), args) } fn execute_internal_subcommand(gctx: &GlobalContext, args: &[&OsStr]) -> CliResult { execute_subcommand(gctx, None, args) } // This function is used to execute a subcommand. It is used to execute both // internal and external subcommands. // If `cmd_path` is `None`, then the subcommand is an internal subcommand. fn execute_subcommand( gctx: &GlobalContext, cmd_path: Option<&PathBuf>, args: &[&OsStr], ) -> CliResult { let cargo_exe = gctx.cargo_exe()?; let mut cmd = match cmd_path { Some(cmd_path) => ProcessBuilder::new(cmd_path), None => ProcessBuilder::new(&cargo_exe), }; cmd.env(cargo::CARGO_ENV, cargo_exe).args(args); if let Some(client) = gctx.jobserver_from_env() { cmd.inherit_jobserver(client); } let err = match cmd.exec_replace() { Ok(()) => return Ok(()), Err(e) => e, }; if let Some(perr) = err.downcast_ref::() { if let Some(code) = perr.code { return Err(CliError::code(code)); } } Err(CliError::new(err, 101)) } #[cfg(unix)] fn is_executable>(path: P) -> bool { use std::os::unix::prelude::*; fs::metadata(path) .map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0) .unwrap_or(false) } #[cfg(windows)] fn is_executable>(path: P) -> bool { path.as_ref().is_file() } fn search_directories(gctx: &GlobalContext) -> Vec { let mut path_dirs = if let Some(val) = gctx.get_env_os("PATH") { env::split_paths(&val).collect() } else { vec![] }; let home_bin = gctx.home().clone().into_path_unlocked().join("bin"); // If any of that PATH elements contains `home_bin`, do not // add it again. This is so that the users can control priority // of it using PATH, while preserving the historical // behavior of preferring it over system global directories even // when not in PATH at all. // See https://github.com/rust-lang/cargo/issues/11020 for details. // // Note: `p == home_bin` will ignore trailing slash, but we don't // `canonicalize` the paths. if !path_dirs.iter().any(|p| p == &home_bin) { path_dirs.insert(0, home_bin); }; path_dirs } /// Initialize libgit2. #[tracing::instrument(skip_all)] fn init_git(gctx: &GlobalContext) { // Disabling the owner validation in git can, in theory, lead to code execution // vulnerabilities. However, libgit2 does not launch executables, which is the foundation of // the original security issue. Meanwhile, issues with refusing to load git repos in // `CARGO_HOME` for example will likely be very frustrating for users. So, we disable the // validation. // // For further discussion of Cargo's current interactions with git, see // // https://github.com/rust-lang/rfcs/pull/3279 // // and in particular the subsection on "Git support". // // Note that we only disable this when Cargo is run as a binary. If Cargo is used as a library, // this code won't be invoked. Instead, developers will need to explicitly disable the // validation in their code. This is inconvenient, but won't accidentally open consuming // applications up to security issues if they use git2 to open repositories elsewhere in their // code. unsafe { git2::opts::set_verify_owner_validation(false) .expect("set_verify_owner_validation should never fail"); } init_git_transports(gctx); } /// Configure libgit2 to use libcurl if necessary. /// /// If the user has a non-default network configuration, then libgit2 will be /// configured to use libcurl instead of the built-in networking support so /// that those configuration settings can be used. #[tracing::instrument(skip_all)] fn init_git_transports(gctx: &GlobalContext) { match needs_custom_http_transport(gctx) { Ok(true) => {} _ => return, } let handle = match http_handle(gctx) { Ok(handle) => handle, Err(..) => return, }; // The unsafety of the registration function derives from two aspects: // // 1. This call must be synchronized with all other registration calls as // well as construction of new transports. // 2. The argument is leaked. // // We're clear on point (1) because this is only called at the start of this // binary (we know what the state of the world looks like) and we're mostly // clear on point (2) because we'd only free it after everything is done // anyway unsafe { git2_curl::register(handle); } } ================================================ FILE: src/cargo/core/compiler/artifact.rs ================================================ //! Generate artifact information from unit dependencies for configuring the compiler environment. use crate::CargoResult; use crate::core::compiler::unit_graph::UnitDep; use crate::core::compiler::{BuildRunner, CrateType, FileFlavor, Unit}; use crate::core::dependency::ArtifactKind; use crate::core::{Dependency, Target, TargetKind}; use std::collections::{HashMap, HashSet}; use std::ffi::OsString; /// Return all environment variables for the given unit-dependencies /// if artifacts are present. pub fn get_env( build_runner: &BuildRunner<'_, '_>, unit: &Unit, dependencies: &[UnitDep], ) -> CargoResult> { let mut env = HashMap::new(); // Add `CARGO_BIN_EXE_` environment variables for building tests. // // These aren't built for `cargo check`, so can't use `dependencies` if unit.target.is_test() || unit.target.is_bench() { for bin_target in unit .pkg .manifest() .targets() .iter() .filter(|target| target.is_bin()) { let name = bin_target .binary_filename() .unwrap_or_else(|| bin_target.name().to_string()); // For `cargo check` builds we do not uplift the CARGO_BIN_EXE_ artifacts to the // artifact-dir. We do not want to provide a path to a non-existent binary but we still // need to provide *something* so `env!("CARGO_BIN_EXE_...")` macros will compile. let exe_path = build_runner .files() .bin_link_for_target(bin_target, unit.kind, build_runner.bcx)? .map(|path| path.as_os_str().to_os_string()) .unwrap_or_else(|| OsString::from(format!("placeholder:{name}"))); let key = format!("CARGO_BIN_EXE_{name}"); env.insert(key, exe_path); } } for unit_dep in dependencies.iter().filter(|d| d.unit.artifact.is_true()) { for artifact_path in build_runner .outputs(&unit_dep.unit)? .iter() .filter_map(|f| (f.flavor == FileFlavor::Normal).then(|| &f.path)) { let artifact_type_upper = unit_artifact_type_name_upper(&unit_dep.unit); let dep_name = unit_dep.dep_name.unwrap_or(unit_dep.unit.pkg.name()); let dep_name_upper = dep_name.to_uppercase().replace("-", "_"); let var = format!("CARGO_{}_DIR_{}", artifact_type_upper, dep_name_upper); let path = artifact_path.parent().expect("parent dir for artifacts"); env.insert(var, path.to_owned().into()); let var_file = format!( "CARGO_{}_FILE_{}_{}", artifact_type_upper, dep_name_upper, unit_dep.unit.target.name() ); // In older releases, lib-targets defaulted to the name of the package. Newer releases // use the same name as default, but with dashes replaced. Hence, if the name of the // target was inferred by Cargo, we also set the env-var with the unconverted name for // backwards compatibility. let need_compat = unit_dep.unit.target.is_lib() && unit_dep.unit.target.name_inferred(); if need_compat { let var_compat = format!( "CARGO_{}_FILE_{}_{}", artifact_type_upper, dep_name_upper, unit_dep.unit.pkg.name(), ); if var_compat != var_file { env.insert(var_compat, artifact_path.to_owned().into()); } } env.insert(var_file, artifact_path.to_owned().into()); // If the name of the target matches the name of the dependency, we strip the // repetition and provide the simpler env-var as well. // For backwards-compatibility of inferred names, we compare against the name of the // package as well, since that used to be the default for library targets. if unit_dep.unit.target.name() == dep_name.as_str() || (need_compat && unit_dep.unit.pkg.name() == dep_name.as_str()) { let var = format!("CARGO_{}_FILE_{}", artifact_type_upper, dep_name_upper,); env.insert(var, artifact_path.to_owned().into()); } } } Ok(env) } fn unit_artifact_type_name_upper(unit: &Unit) -> &'static str { match unit.target.kind() { TargetKind::Lib(kinds) => match kinds.as_slice() { &[CrateType::Cdylib] => "CDYLIB", &[CrateType::Staticlib] => "STATICLIB", invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid), }, TargetKind::Bin => "BIN", invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid), } } /// Given a dependency with an artifact `artifact_dep` and a set of available `targets` /// of its package, find a target for each kind of artifacts that are to be built. /// /// Failure to match any target results in an error mentioning the parent manifests /// `parent_package` name. pub(crate) fn match_artifacts_kind_with_targets<'t, 'd>( artifact_dep: &'d Dependency, targets: &'t [Target], parent_package: &str, ) -> CargoResult> { let mut out = HashSet::new(); let artifact_requirements = artifact_dep.artifact().expect("artifact present"); for artifact_kind in artifact_requirements.kinds() { let mut extend = |kind, filter: &dyn Fn(&&Target) -> bool| { let mut iter = targets.iter().filter(filter).peekable(); let found = iter.peek().is_some(); out.extend(std::iter::repeat(kind).zip(iter)); found }; let found = match artifact_kind { ArtifactKind::Cdylib => extend(artifact_kind, &|t| t.is_cdylib()), ArtifactKind::Staticlib => extend(artifact_kind, &|t| t.is_staticlib()), ArtifactKind::AllBinaries => extend(artifact_kind, &|t| t.is_bin()), ArtifactKind::SelectedBinary(bin_name) => extend(artifact_kind, &|t| { t.is_bin() && t.name() == bin_name.as_str() }), }; if !found { anyhow::bail!( "dependency `{}` in package `{}` requires a `{}` artifact to be present.", artifact_dep.name_in_toml(), parent_package, artifact_kind ); } } Ok(out) } ================================================ FILE: src/cargo/core/compiler/build_config.rs ================================================ use crate::core::compiler::CompileKind; use crate::util::context::JobsConfig; use crate::util::interning::InternedString; use crate::util::{CargoResult, GlobalContext, RustfixDiagnosticServer}; use anyhow::{Context as _, bail}; use cargo_util::ProcessBuilder; use serde::ser; use std::cell::RefCell; use std::path::PathBuf; use std::rc::Rc; use std::thread::available_parallelism; /// Configuration information for a rustc build. #[derive(Debug, Clone)] pub struct BuildConfig { /// The requested kind of compilation for this session pub requested_kinds: Vec, /// Number of rustc jobs to run in parallel. pub jobs: u32, /// Do not abort the build as soon as there is an error. pub keep_going: bool, /// Build profile pub requested_profile: InternedString, /// The intent we are compiling in. pub intent: UserIntent, /// `true` to print stdout in JSON format (for machine reading). pub message_format: MessageFormat, /// Force Cargo to do a full rebuild and treat each target as changed. pub force_rebuild: bool, /// Output the unit graph to stdout instead of actually compiling. pub unit_graph: bool, /// `true` to avoid really compiling. pub dry_run: bool, /// An optional override of the rustc process for primary units pub primary_unit_rustc: Option, /// A thread used by `cargo fix` to receive messages on a socket regarding /// the success/failure of applying fixes. pub rustfix_diagnostic_server: Rc>>, /// The directory to copy final artifacts to. Note that even if /// `artifact-dir` is set, a copy of artifacts still can be found at /// `target/(debug\release)` as usual. /// Named `export_dir` to avoid confusion with /// `CompilationFiles::artifact_dir`. pub export_dir: Option, /// `true` to output a future incompatibility report at the end of the build pub future_incompat_report: bool, /// Output timing report at the end of the build pub timing_report: bool, /// Output SBOM precursor files. pub sbom: bool, /// Build compile time dependencies only, e.g., build scripts and proc macros pub compile_time_deps_only: bool, } fn default_parallelism() -> CargoResult { Ok(available_parallelism() .context("failed to determine the amount of parallelism available")? .get() as u32) } impl BuildConfig { /// Parses all config files to learn about build configuration. Currently /// configured options are: /// /// * `build.jobs` /// * `build.target` /// * `target.$target.ar` /// * `target.$target.linker` /// * `target.$target.libfoo.metadata` pub fn new( gctx: &GlobalContext, jobs: Option, keep_going: bool, requested_targets: &[String], intent: UserIntent, ) -> CargoResult { let cfg = gctx.build_config()?; let requested_kinds = CompileKind::from_requested_targets(gctx, requested_targets)?; if jobs.is_some() && gctx.jobserver_from_env().is_some() { gctx.shell().warn( "a `-j` argument was passed to Cargo but Cargo is \ also configured with an external jobserver in \ its environment, ignoring the `-j` parameter", )?; } let jobs = match jobs.or(cfg.jobs.clone()) { None => default_parallelism()?, Some(value) => match value { JobsConfig::Integer(j) => match j { 0 => anyhow::bail!("jobs may not be 0"), j if j < 0 => (default_parallelism()? as i32 + j).max(1) as u32, j => j as u32, }, JobsConfig::String(j) => match j.as_str() { "default" => default_parallelism()?, _ => { anyhow::bail!(format!( "could not parse `{j}`. Number of parallel jobs should be `default` or a number." )) } }, }, }; // If sbom flag is set, it requires the unstable feature let sbom = match (cfg.sbom, gctx.cli_unstable().sbom) { (Some(sbom), true) => sbom, (Some(_), false) => { gctx.shell() .warn("ignoring 'sbom' config, pass `-Zsbom` to enable it")?; false } (None, _) => false, }; Ok(BuildConfig { requested_kinds, jobs, keep_going, requested_profile: "dev".into(), intent, message_format: MessageFormat::Human, force_rebuild: false, unit_graph: false, dry_run: false, primary_unit_rustc: None, rustfix_diagnostic_server: Rc::new(RefCell::new(None)), export_dir: None, future_incompat_report: false, timing_report: false, sbom, compile_time_deps_only: false, }) } /// Whether or not the *user* wants JSON output. Whether or not rustc /// actually uses JSON is decided in `add_error_format`. pub fn emit_json(&self) -> bool { matches!(self.message_format, MessageFormat::Json { .. }) } pub fn single_requested_kind(&self) -> CargoResult { match self.requested_kinds.len() { 1 => Ok(self.requested_kinds[0]), _ => bail!("only one `--target` argument is supported"), } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum MessageFormat { Human, Json { /// Whether rustc diagnostics are rendered by cargo or included into the /// output stream. render_diagnostics: bool, /// Whether the `rendered` field of rustc diagnostics are using the /// "short" rendering. short: bool, /// Whether the `rendered` field of rustc diagnostics embed ansi color /// codes. ansi: bool, }, Short, } /// The specific action to be performed on each `Unit` of work. #[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)] pub enum CompileMode { /// Test with `rustc`. Test, /// Compile with `rustc`. Build, /// Type-check with `rustc` by emitting `rmeta` metadata only. /// /// If `test` is true, then it is also compiled with `--test` to check it like /// a test. Check { test: bool }, /// Document with `rustdoc`. Doc, /// Test with `rustdoc`. Doctest, /// Scrape for function calls by `rustdoc`. Docscrape, /// Execute the binary built from the `build.rs` script. RunCustomBuild, } impl ser::Serialize for CompileMode { fn serialize(&self, s: S) -> Result where S: ser::Serializer, { use self::CompileMode::*; match *self { Test => "test".serialize(s), Build => "build".serialize(s), Check { .. } => "check".serialize(s), Doc { .. } => "doc".serialize(s), Doctest => "doctest".serialize(s), Docscrape => "docscrape".serialize(s), RunCustomBuild => "run-custom-build".serialize(s), } } } impl<'de> serde::Deserialize<'de> for CompileMode { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; match s.as_str() { "test" => Ok(CompileMode::Test), "build" => Ok(CompileMode::Build), "check" => Ok(CompileMode::Check { test: false }), "doc" => Ok(CompileMode::Doc), "doctest" => Ok(CompileMode::Doctest), "docscrape" => Ok(CompileMode::Docscrape), "run-custom-build" => Ok(CompileMode::RunCustomBuild), other => Err(serde::de::Error::unknown_variant( other, &[ "test", "build", "check", "doc", "doctest", "docscrape", "run-custom-build", ], )), } } } impl CompileMode { /// Returns `true` if the unit is being checked. pub fn is_check(self) -> bool { matches!(self, CompileMode::Check { .. }) } /// Returns `true` if this is generating documentation. pub fn is_doc(self) -> bool { matches!(self, CompileMode::Doc { .. }) } /// Returns `true` if this a doc test. pub fn is_doc_test(self) -> bool { self == CompileMode::Doctest } /// Returns `true` if this is scraping examples for documentation. pub fn is_doc_scrape(self) -> bool { self == CompileMode::Docscrape } /// Returns `true` if this is any type of test (test, benchmark, doc test, or /// check test). pub fn is_any_test(self) -> bool { matches!( self, CompileMode::Test | CompileMode::Check { test: true } | CompileMode::Doctest ) } /// Returns `true` if this is something that passes `--test` to rustc. pub fn is_rustc_test(self) -> bool { matches!(self, CompileMode::Test | CompileMode::Check { test: true }) } /// Returns `true` if this is the *execution* of a `build.rs` script. pub fn is_run_custom_build(self) -> bool { self == CompileMode::RunCustomBuild } /// Returns `true` if this mode may generate an executable. /// /// Note that this also returns `true` for building libraries, so you also /// have to check the target. pub fn generates_executable(self) -> bool { matches!(self, CompileMode::Test | CompileMode::Build) } } /// Represents the high-level operation requested by the user. /// /// It determines which "Cargo targets" are selected by default and influences /// how they will be processed. This is derived from the Cargo command the user /// invoked (like `cargo build` or `cargo test`). /// /// Unlike [`CompileMode`], which describes the specific compilation steps for /// individual units, [`UserIntent`] represents the overall goal of the build /// process as specified by the user. /// /// For example, when a user runs `cargo test`, the intent is [`UserIntent::Test`], /// but this might result in multiple [`CompileMode`]s for different units. #[derive(Clone, Copy, Debug)] pub enum UserIntent { /// Build benchmark binaries, e.g., `cargo bench` Bench, /// Build binaries and libraries, e.g., `cargo run`, `cargo install`, `cargo build`. Build, /// Perform type-check, e.g., `cargo check`. Check { test: bool }, /// Document packages. /// /// If `deps` is true, then it will also document all dependencies. /// if `json` is true, the documentation output is in json format. Doc { deps: bool, json: bool }, /// Build doctest binaries, e.g., `cargo test --doc` Doctest, /// Build test binaries, e.g., `cargo test` Test, } impl UserIntent { /// Returns `true` if this is generating documentation. pub fn is_doc(self) -> bool { matches!(self, UserIntent::Doc { .. }) } /// User wants rustdoc output in JSON format. pub fn wants_doc_json_output(self) -> bool { matches!(self, UserIntent::Doc { json: true, .. }) } /// User wants to document also for dependencies. pub fn wants_deps_docs(self) -> bool { matches!(self, UserIntent::Doc { deps: true, .. }) } /// Returns `true` if this is any type of test (test, benchmark, doc test, or /// check test). pub fn is_any_test(self) -> bool { matches!( self, UserIntent::Test | UserIntent::Bench | UserIntent::Check { test: true } | UserIntent::Doctest ) } /// Returns `true` if this is something that passes `--test` to rustc. pub fn is_rustc_test(self) -> bool { matches!( self, UserIntent::Test | UserIntent::Bench | UserIntent::Check { test: true } ) } } ================================================ FILE: src/cargo/core/compiler/build_context/mod.rs ================================================ //! [`BuildContext`] is a (mostly) static information about a build task. use crate::core::PackageSet; use crate::core::Workspace; use crate::core::compiler::BuildConfig; use crate::core::compiler::CompileKind; use crate::core::compiler::Unit; use crate::core::compiler::UnitIndex; use crate::core::compiler::unit_graph::UnitGraph; use crate::core::profiles::Profiles; use crate::util::Rustc; use crate::util::context::GlobalContext; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::logger::BuildLogger; use std::collections::{HashMap, HashSet}; mod target_info; pub use self::target_info::FileFlavor; pub use self::target_info::FileType; pub use self::target_info::RustcTargetData; pub use self::target_info::TargetInfo; /// The build context, containing complete information needed for a build task /// before it gets started. /// /// It is intended that this is mostly static information. Stuff that mutates /// during the build can be found in the parent [`BuildRunner`]. (I say mostly, /// because this has internal caching, but nothing that should be observable /// or require &mut.) /// /// As a result, almost every field on `BuildContext` is public, including /// /// * a resolved [`UnitGraph`] of your dependencies, /// * a [`Profiles`] containing compiler flags presets, /// * a [`RustcTargetData`] containing host and target platform information, /// * and a [`PackageSet`] for further package downloads, /// /// just to name a few. Learn more on each own documentation. /// /// # How to use /// /// To prepare a build task, you may not want to use [`BuildContext::new`] directly, /// since it is often too lower-level. /// Instead, [`ops::create_bcx`] is usually what you are looking for. /// /// After a `BuildContext` is built, the next stage of building is handled in [`BuildRunner`]. /// /// [`BuildRunner`]: crate::core::compiler::BuildRunner /// [`ops::create_bcx`]: crate::ops::create_bcx pub struct BuildContext<'a, 'gctx> { /// The workspace the build is for. pub ws: &'a Workspace<'gctx>, /// The cargo context. pub gctx: &'gctx GlobalContext, /// Build logger for `-Zbuild-analysis`. pub logger: Option<&'a BuildLogger>, /// This contains a collection of compiler flags presets. pub profiles: Profiles, /// Configuration information for a rustc build. pub build_config: &'a BuildConfig, /// Extra compiler args for either `rustc` or `rustdoc`. pub extra_compiler_args: HashMap>, /// Package downloader. /// /// This holds ownership of the `Package` objects. pub packages: PackageSet<'gctx>, /// Information about rustc and the target platform. pub target_data: RustcTargetData<'gctx>, /// The root units of `unit_graph` (units requested on the command-line). pub roots: Vec, /// The dependency graph of units to compile. pub unit_graph: UnitGraph, /// A map from unit to index. pub unit_to_index: HashMap, /// Reverse-dependencies of documented units, used by the `rustdoc --scrape-examples` flag. pub scrape_units: Vec, /// The list of all kinds that are involved in this build pub all_kinds: HashSet, } impl<'a, 'gctx> BuildContext<'a, 'gctx> { pub fn new( ws: &'a Workspace<'gctx>, logger: Option<&'a BuildLogger>, packages: PackageSet<'gctx>, build_config: &'a BuildConfig, profiles: Profiles, extra_compiler_args: HashMap>, target_data: RustcTargetData<'gctx>, roots: Vec, unit_graph: UnitGraph, unit_to_index: HashMap, scrape_units: Vec, ) -> CargoResult> { let all_kinds = unit_graph .keys() .map(|u| u.kind) .chain(build_config.requested_kinds.iter().copied()) .chain(std::iter::once(CompileKind::Host)) .collect(); Ok(BuildContext { ws, gctx: ws.gctx(), logger, packages, build_config, profiles, extra_compiler_args, target_data, roots, unit_graph, unit_to_index, scrape_units, all_kinds, }) } /// Information of the `rustc` this build task will use. pub fn rustc(&self) -> &Rustc { &self.target_data.rustc } /// Gets the host architecture triple. /// /// For example, `x86_64-unknown-linux-gnu`, would be /// - machine: `x86_64`, /// - hardware-platform: `unknown`, /// - operating system: `linux-gnu`. pub fn host_triple(&self) -> InternedString { self.target_data.rustc.host } /// Gets the number of jobs specified for this build. pub fn jobs(&self) -> u32 { self.build_config.jobs } /// Extra compiler args for either `rustc` or `rustdoc`. /// /// As of now, these flags come from the trailing args of either /// `cargo rustc` or `cargo rustdoc`. pub fn extra_args_for(&self, unit: &Unit) -> Option<&Vec> { self.extra_compiler_args.get(unit) } } ================================================ FILE: src/cargo/core/compiler/build_context/target_info.rs ================================================ //! This modules contains types storing information of target platforms. //! //! Normally, call [`RustcTargetData::new`] to construct all the target //! platform once, and then query info on your demand. For example, //! //! * [`RustcTargetData::dep_platform_activated`] to check if platform is activated. //! * [`RustcTargetData::info`] to get a [`TargetInfo`] for an in-depth query. //! * [`TargetInfo::rustc_outputs`] to get a list of supported file types. use crate::core::compiler::CompileKind; use crate::core::compiler::CompileMode; use crate::core::compiler::CompileTarget; use crate::core::compiler::CrateType; use crate::core::compiler::apply_env_config; use crate::core::{Dependency, Package, Target, TargetKind, Workspace}; use crate::util::context::{GlobalContext, StringList, TargetConfig}; use crate::util::interning::InternedString; use crate::util::{CargoResult, Rustc}; use anyhow::Context as _; use cargo_platform::{Cfg, CfgExpr}; use cargo_util::ProcessBuilder; use serde::Deserialize; use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; use std::path::PathBuf; use std::rc::Rc; use std::str::{self, FromStr}; /// Information about the platform target gleaned from querying rustc. /// /// [`RustcTargetData`] keeps several of these, one for the host and the others /// for other specified targets. If no target is specified, it uses a clone from /// the host. #[derive(Clone)] pub struct TargetInfo { /// A base process builder for discovering crate type information. In /// particular, this is used to determine the output filename prefix and /// suffix for a crate type. crate_type_process: ProcessBuilder, /// Cache of output filename prefixes and suffixes. /// /// The key is the crate type name (like `cdylib`) and the value is /// `Some((prefix, suffix))`, for example `libcargo.so` would be /// `Some(("lib", ".so"))`. The value is `None` if the crate type is not /// supported. crate_types: RefCell>>, /// `cfg` information extracted from `rustc --print=cfg`. cfg: Vec, /// `supports_std` information extracted from `rustc --print=target-spec-json` pub supports_std: Option, /// Supported values for `-Csplit-debuginfo=` flag, queried from rustc support_split_debuginfo: Vec, /// Path to the sysroot. pub sysroot: PathBuf, /// Path to the "lib" directory in the sysroot which rustc uses for linking /// target libraries. pub sysroot_target_libdir: PathBuf, /// Extra flags to pass to `rustc`, see [`extra_args`]. pub rustflags: Rc<[String]>, /// Extra flags to pass to `rustdoc`, see [`extra_args`]. pub rustdocflags: Rc<[String]>, } /// Kind of each file generated by a Unit, part of `FileType`. #[derive(Clone, PartialEq, Eq, Debug)] pub enum FileFlavor { /// Not a special file type. Normal, /// Like `Normal`, but not directly executable. /// For example, a `.wasm` file paired with the "normal" `.js` file. Auxiliary, /// Something you can link against (e.g., a library). Linkable, /// An `.rmeta` Rust metadata file. Rmeta, /// Piece of external debug information (e.g., `.dSYM`/`.pdb` file). DebugInfo, /// SBOM (Software Bill of Materials pre-cursor) file (e.g. cargo-sbon.json). Sbom, /// Cross-crate info JSON files generated by rustdoc. DocParts, } /// Type of each file generated by a Unit. #[derive(Debug)] pub struct FileType { /// The kind of file. pub flavor: FileFlavor, /// The crate-type that generates this file. /// /// `None` for things that aren't associated with a specific crate type, /// for example `rmeta` files. pub crate_type: Option, /// The suffix for the file (for example, `.rlib`). /// This is an empty string for executables on Unix-like platforms. suffix: String, /// The prefix for the file (for example, `lib`). /// This is an empty string for things like executables. prefix: String, /// Flag to convert hyphen to underscore when uplifting. should_replace_hyphens: bool, } impl FileType { /// The filename for this `FileType` created by rustc. pub fn output_filename(&self, target: &Target, metadata: Option<&str>) -> String { match metadata { Some(metadata) => format!( "{}{}-{}{}", self.prefix, target.crate_name(), metadata, self.suffix ), None => format!("{}{}{}", self.prefix, target.crate_name(), self.suffix), } } /// The filename for this `FileType` that Cargo should use when "uplifting" /// it to the destination directory. pub fn uplift_filename(&self, target: &Target) -> String { let name = match target.binary_filename() { Some(name) => name, None => { // For binary crate type, `should_replace_hyphens` will always be false. if self.should_replace_hyphens { target.crate_name() } else { target.name().to_string() } } }; format!("{}{}{}", self.prefix, name, self.suffix) } /// Creates a new instance representing a `.rmeta` file. pub fn new_rmeta() -> FileType { // Note that even binaries use the `lib` prefix. FileType { flavor: FileFlavor::Rmeta, crate_type: None, suffix: ".rmeta".to_string(), prefix: "lib".to_string(), should_replace_hyphens: true, } } pub fn output_prefix_suffix(&self, target: &Target) -> (String, String) { ( format!("{}{}-", self.prefix, target.crate_name()), self.suffix.clone(), ) } } impl TargetInfo { /// Learns the information of target platform from `rustc` invocation(s). /// /// Generally, the first time calling this function is expensive, as it may /// query `rustc` several times. To reduce the cost, output of each `rustc` /// invocation is cached by [`Rustc::cached_output`]. /// /// Search `Tricky` to learn why querying `rustc` several times is needed. #[tracing::instrument(skip_all)] pub fn new( gctx: &GlobalContext, requested_kinds: &[CompileKind], rustc: &Rustc, kind: CompileKind, ) -> CargoResult { let mut rustflags = extra_args(gctx, requested_kinds, &rustc.host, None, kind, Flags::Rust)?; let mut turn = 0; loop { let extra_fingerprint = kind.fingerprint_hash(); // Query rustc for several kinds of info from each line of output: // 0) file-names (to determine output file prefix/suffix for given crate type) // 1) sysroot // 2) split-debuginfo // 3) cfg // // Search `--print` to see what we query so far. let mut process = rustc.workspace_process(); apply_env_config(gctx, &mut process)?; process .arg("-") .arg("--crate-name") .arg("___") .arg("--print=file-names") .args(&rustflags) .env_remove("RUSTC_LOG"); // Removes `FD_CLOEXEC` set by `jobserver::Client` to pass jobserver // as environment variables specify. if let Some(client) = gctx.jobserver_from_env() { process.inherit_jobserver(client); } kind.add_target_arg(&mut process); let crate_type_process = process.clone(); const KNOWN_CRATE_TYPES: &[CrateType] = &[ CrateType::Bin, CrateType::Rlib, CrateType::Dylib, CrateType::Cdylib, CrateType::Staticlib, CrateType::ProcMacro, ]; for crate_type in KNOWN_CRATE_TYPES.iter() { process.arg("--crate-type").arg(crate_type.as_str()); } process.arg("--print=sysroot"); process.arg("--print=split-debuginfo"); process.arg("--print=crate-name"); // `___` as a delimiter. process.arg("--print=cfg"); // parse_crate_type() relies on "unsupported/unknown crate type" error message, // so make warnings always emitted as warnings. process.arg("-Wwarnings"); let (output, error) = rustc .cached_output(&process, extra_fingerprint) .with_context( || "failed to run `rustc` to learn about target-specific information", )?; let mut lines = output.lines(); let mut map = HashMap::new(); for crate_type in KNOWN_CRATE_TYPES { let out = parse_crate_type(crate_type, &process, &output, &error, &mut lines)?; map.insert(crate_type.clone(), out); } let Some(line) = lines.next() else { return error_missing_print_output("sysroot", &process, &output, &error); }; let sysroot = PathBuf::from(line); let sysroot_target_libdir = { let mut libdir = sysroot.clone(); libdir.push("lib"); libdir.push("rustlib"); libdir.push(match &kind { CompileKind::Host => rustc.host.as_str(), CompileKind::Target(target) => target.short_name(), }); libdir.push("lib"); libdir }; let support_split_debuginfo = { // HACK: abuse `--print=crate-name` to use `___` as a delimiter. let mut res = Vec::new(); loop { match lines.next() { Some(line) if line == "___" => break, Some(line) => res.push(line.into()), None => { return error_missing_print_output( "split-debuginfo", &process, &output, &error, ); } } } res }; let cfg = lines .map(|line| Ok(Cfg::from_str(line)?)) .filter(TargetInfo::not_user_specific_cfg) .collect::>>() .with_context(|| { format!( "failed to parse the cfg from `rustc --print=cfg`, got:\n{}", output ) })?; // recalculate `rustflags` from above now that we have `cfg` // information let new_flags = extra_args( gctx, requested_kinds, &rustc.host, Some(&cfg), kind, Flags::Rust, )?; // Tricky: `RUSTFLAGS` defines the set of active `cfg` flags, active // `cfg` flags define which `.cargo/config` sections apply, and they // in turn can affect `RUSTFLAGS`! This is a bona fide mutual // dependency, and it can even diverge (see `cfg_paradox` test). // // So what we do here is running at most *two* iterations of // fixed-point iteration, which should be enough to cover // practically useful cases, and warn if that's not enough for // convergence. let reached_fixed_point = new_flags == rustflags; if !reached_fixed_point && turn == 0 { turn += 1; rustflags = new_flags; continue; } if !reached_fixed_point { gctx.shell().warn("non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")?; } let mut supports_std: Option = None; // The '--print=target-spec-json' is an unstable option of rustc, therefore only // try to fetch this information if rustc allows nightly features. Additionally, // to avoid making two rustc queries when not required, only try to fetch the // target-spec when the '-Zbuild-std' option is passed. if gctx.cli_unstable().build_std.is_some() { let mut target_spec_process = rustc.workspace_process(); apply_env_config(gctx, &mut target_spec_process)?; target_spec_process .arg("--print=target-spec-json") .arg("-Zunstable-options") .args(&rustflags) .env_remove("RUSTC_LOG"); kind.add_target_arg(&mut target_spec_process); #[derive(Deserialize)] struct Metadata { pub std: Option, } #[derive(Deserialize)] struct TargetSpec { pub metadata: Metadata, } if let Ok(output) = target_spec_process.output() { if let Ok(spec) = serde_json::from_slice::(&output.stdout) { supports_std = spec.metadata.std; } } } return Ok(TargetInfo { crate_type_process, crate_types: RefCell::new(map), sysroot, sysroot_target_libdir, rustflags: rustflags.into(), rustdocflags: extra_args( gctx, requested_kinds, &rustc.host, Some(&cfg), kind, Flags::Rustdoc, )? .into(), cfg, supports_std, support_split_debuginfo, }); } } fn not_user_specific_cfg(cfg: &CargoResult) -> bool { if let Ok(Cfg::Name(cfg_name)) = cfg { // This should also include "debug_assertions", but it causes // regressions. Maybe some day in the distant future it can be // added (and possibly change the warning to an error). if cfg_name == "proc_macro" { return false; } } true } /// All the target [`Cfg`] settings. pub fn cfg(&self) -> &[Cfg] { &self.cfg } /// Returns the list of file types generated by the given crate type. /// /// Returns `None` if the target does not support the given crate type. fn file_types( &self, crate_type: &CrateType, flavor: FileFlavor, target_triple: &str, ) -> CargoResult>> { let crate_type = if *crate_type == CrateType::Lib { CrateType::Rlib } else { crate_type.clone() }; let mut crate_types = self.crate_types.borrow_mut(); let entry = crate_types.entry(crate_type.clone()); let crate_type_info = match entry { Entry::Occupied(o) => &*o.into_mut(), Entry::Vacant(v) => { let value = self.discover_crate_type(v.key())?; &*v.insert(value) } }; let Some((prefix, suffix)) = crate_type_info else { return Ok(None); }; let mut ret = vec![FileType { suffix: suffix.clone(), prefix: prefix.clone(), flavor, crate_type: Some(crate_type.clone()), should_replace_hyphens: crate_type != CrateType::Bin, }]; // Window shared library import/export files. if crate_type.is_dynamic() { // Note: Custom JSON specs can alter the suffix. For now, we'll // just ignore non-DLL suffixes. if target_triple.ends_with("-windows-msvc") && suffix == ".dll" { // See https://docs.microsoft.com/en-us/cpp/build/reference/working-with-import-libraries-and-export-files // for more information about DLL import/export files. ret.push(FileType { suffix: ".dll.lib".to_string(), prefix: prefix.clone(), flavor: FileFlavor::Auxiliary, crate_type: Some(crate_type.clone()), should_replace_hyphens: true, }); // NOTE: lld does not produce these ret.push(FileType { suffix: ".dll.exp".to_string(), prefix: prefix.clone(), flavor: FileFlavor::Auxiliary, crate_type: Some(crate_type.clone()), should_replace_hyphens: true, }); } else if suffix == ".dll" && (target_triple.ends_with("windows-gnu") || target_triple.ends_with("windows-gnullvm") || target_triple.ends_with("cygwin")) { // See https://cygwin.com/cygwin-ug-net/dll.html for more // information about GNU import libraries. // LD can link DLL directly, but LLD requires the import library. ret.push(FileType { suffix: ".dll.a".to_string(), prefix: "lib".to_string(), flavor: FileFlavor::Auxiliary, crate_type: Some(crate_type.clone()), should_replace_hyphens: true, }) } } if target_triple.starts_with("wasm32-") && crate_type == CrateType::Bin && suffix == ".js" { // emscripten binaries generate a .js file, which loads a .wasm // file. ret.push(FileType { suffix: ".wasm".to_string(), prefix: prefix.clone(), flavor: FileFlavor::Auxiliary, crate_type: Some(crate_type.clone()), // Name `foo-bar` will generate a `foo_bar.js` and // `foo_bar.wasm`. Cargo will translate the underscore and // copy `foo_bar.js` to `foo-bar.js`. However, the wasm // filename is embedded in the .js file with an underscore, so // it should not contain hyphens. should_replace_hyphens: true, }); // And a map file for debugging. This is only emitted with debug=2 // (-g4 for emcc). ret.push(FileType { suffix: ".wasm.map".to_string(), prefix: prefix.clone(), flavor: FileFlavor::DebugInfo, crate_type: Some(crate_type.clone()), should_replace_hyphens: true, }); } // Handle separate debug files. let is_apple = target_triple.contains("-apple-"); if matches!( crate_type, CrateType::Bin | CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro ) { if is_apple { let suffix = if crate_type == CrateType::Bin { ".dSYM".to_string() } else { ".dylib.dSYM".to_string() }; ret.push(FileType { suffix, prefix: prefix.clone(), flavor: FileFlavor::DebugInfo, crate_type: Some(crate_type), // macOS tools like lldb use all sorts of magic to locate // dSYM files. See https://lldb.llvm.org/use/symbols.html // for some details. It seems like a `.dSYM` located next // to the executable with the same name is one method. The // dSYM should have the same hyphens as the executable for // the names to match. should_replace_hyphens: false, }) } else if target_triple.ends_with("-msvc") || target_triple.ends_with("-uefi") { ret.push(FileType { suffix: ".pdb".to_string(), prefix: prefix.clone(), flavor: FileFlavor::DebugInfo, crate_type: Some(crate_type), // The absolute path to the pdb file is embedded in the // executable. If the exe/pdb pair is moved to another // machine, then debuggers will look in the same directory // of the exe with the original pdb filename. Since the // original name contains underscores, they need to be // preserved. should_replace_hyphens: true, }) } else { // Because DWARF Package (dwp) files are produced after the // fact by another tool, there is nothing in the binary that // provides a means to locate them. By convention, debuggers // take the binary filename and append ".dwp" (including to // binaries that already have an extension such as shared libs) // to find the dwp. ret.push(FileType { // It is important to preserve the existing suffix for // e.g. shared libraries, where the dwp for libfoo.so is // expected to be at libfoo.so.dwp. suffix: format!("{suffix}.dwp"), prefix: prefix.clone(), flavor: FileFlavor::DebugInfo, crate_type: Some(crate_type.clone()), // Likewise, the dwp needs to match the primary artifact's // hyphenation exactly. should_replace_hyphens: crate_type != CrateType::Bin, }) } } Ok(Some(ret)) } fn discover_crate_type(&self, crate_type: &CrateType) -> CargoResult> { let mut process = self.crate_type_process.clone(); process.arg("--crate-type").arg(crate_type.as_str()); let output = process.exec_with_output().with_context(|| { format!( "failed to run `rustc` to learn about crate-type {} information", crate_type ) })?; let error = str::from_utf8(&output.stderr).unwrap(); let output = str::from_utf8(&output.stdout).unwrap(); parse_crate_type(crate_type, &process, output, error, &mut output.lines()) } /// Returns all the file types generated by rustc for the given `mode`/`target_kind`. /// /// The first value is a Vec of file types generated, the second value is /// a list of `CrateTypes` that are not supported by the given target. pub fn rustc_outputs( &self, mode: CompileMode, target_kind: &TargetKind, target_triple: &str, gctx: &GlobalContext, ) -> CargoResult<(Vec, Vec)> { match mode { CompileMode::Build => self.calc_rustc_outputs(target_kind, target_triple, gctx), CompileMode::Test => { match self.file_types(&CrateType::Bin, FileFlavor::Normal, target_triple)? { Some(fts) => Ok((fts, Vec::new())), None => Ok((Vec::new(), vec![CrateType::Bin])), } } CompileMode::Check { .. } => Ok((vec![FileType::new_rmeta()], Vec::new())), CompileMode::Doc { .. } | CompileMode::Doctest | CompileMode::Docscrape | CompileMode::RunCustomBuild => { panic!("asked for rustc output for non-rustc mode") } } } fn calc_rustc_outputs( &self, target_kind: &TargetKind, target_triple: &str, gctx: &GlobalContext, ) -> CargoResult<(Vec, Vec)> { let mut unsupported = Vec::new(); let mut result = Vec::new(); let crate_types = target_kind.rustc_crate_types(); for crate_type in &crate_types { let flavor = if crate_type.is_linkable() { FileFlavor::Linkable } else { FileFlavor::Normal }; let file_types = self.file_types(crate_type, flavor, target_triple)?; match file_types { Some(types) => { result.extend(types); } None => { unsupported.push(crate_type.clone()); } } } if !result.is_empty() { if gctx.cli_unstable().no_embed_metadata && crate_types .iter() .any(|ct| ct.benefits_from_no_embed_metadata()) { // Add .rmeta when we apply -Zembed-metadata=no to the unit. result.push(FileType::new_rmeta()); } else if !crate_types.iter().any(|ct| ct.requires_upstream_objects()) { // Only add rmeta if pipelining result.push(FileType::new_rmeta()); } } Ok((result, unsupported)) } /// Checks if the debuginfo-split value is supported by this target pub fn supports_debuginfo_split(&self, split: InternedString) -> bool { self.support_split_debuginfo .iter() .any(|sup| sup.as_str() == split.as_str()) } /// Checks if a target maybe support std. /// /// If no explicitly stated in target spec json, we treat it as "maybe support". /// /// This is only useful for `-Zbuild-std` to determine the default set of /// crates it is going to build. pub fn maybe_support_std(&self) -> bool { matches!(self.supports_std, Some(true) | None) } } /// Takes rustc output (using specialized command line args), and calculates the file prefix and /// suffix for the given crate type, or returns `None` if the type is not supported. (e.g., for a /// Rust library like `libcargo.rlib`, we have prefix "lib" and suffix "rlib"). /// /// The caller needs to ensure that the lines object is at the correct line for the given crate /// type: this is not checked. /// /// This function can not handle more than one file per type (with wasm32-unknown-emscripten, there /// are two files for bin (`.wasm` and `.js`)). fn parse_crate_type( crate_type: &CrateType, cmd: &ProcessBuilder, output: &str, error: &str, lines: &mut str::Lines<'_>, ) -> CargoResult> { let not_supported = error.lines().any(|line| { (line.contains("unsupported crate type") || line.contains("unknown crate type")) && line.contains(&format!("crate type `{}`", crate_type)) }); if not_supported { return Ok(None); } let Some(line) = lines.next() else { anyhow::bail!( "malformed output when learning about crate-type {} information\n{}", crate_type, output_err_info(cmd, output, error) ) }; let mut parts = line.trim().split("___"); let prefix = parts.next().unwrap(); let Some(suffix) = parts.next() else { return error_missing_print_output("file-names", cmd, output, error); }; Ok(Some((prefix.to_string(), suffix.to_string()))) } /// Helper for creating an error message for missing output from a certain `--print` request. fn error_missing_print_output( request: &str, cmd: &ProcessBuilder, stdout: &str, stderr: &str, ) -> CargoResult { let err_info = output_err_info(cmd, stdout, stderr); anyhow::bail!( "output of --print={request} missing when learning about \ target-specific information from rustc\n{err_info}", ) } /// Helper for creating an error message when parsing rustc output fails. fn output_err_info(cmd: &ProcessBuilder, stdout: &str, stderr: &str) -> String { let mut result = format!("command was: {}\n", cmd); if !stdout.is_empty() { result.push_str("\n--- stdout\n"); result.push_str(stdout); } if !stderr.is_empty() { result.push_str("\n--- stderr\n"); result.push_str(stderr); } if stdout.is_empty() && stderr.is_empty() { result.push_str("(no output received)"); } result } /// Compiler flags for either rustc or rustdoc. #[derive(Debug, Copy, Clone)] enum Flags { Rust, Rustdoc, } impl Flags { fn as_key(self) -> &'static str { match self { Flags::Rust => "rustflags", Flags::Rustdoc => "rustdocflags", } } fn as_env(self) -> &'static str { match self { Flags::Rust => "RUSTFLAGS", Flags::Rustdoc => "RUSTDOCFLAGS", } } } /// Acquire extra flags to pass to the compiler from various locations. /// /// The locations are: /// /// - the `CARGO_ENCODED_RUSTFLAGS` environment variable /// - the `RUSTFLAGS` environment variable /// /// then if none of those were found /// /// - `target.*.rustflags` from the config (.cargo/config) /// - `target.cfg(..).rustflags` from the config /// - `host.*.rustflags` from the config if compiling a host artifact or without `--target` /// (requires `-Zhost-config`) /// /// then if none of those were found /// /// - `build.rustflags` from the config /// /// The behavior differs slightly when cross-compiling (or, specifically, when `--target` is /// provided) for artifacts that are always built for the host (plugins, build scripts, ...). /// For those artifacts, _only_ `host.*.rustflags` is respected, and no other configuration /// sources, _regardless of the value of `target-applies-to-host`_. This is counterintuitive, but /// necessary to retain backwards compatibility with older versions of Cargo. /// /// Rules above also applies to rustdoc. Just the key would be `rustdocflags`/`RUSTDOCFLAGS`. fn extra_args( gctx: &GlobalContext, requested_kinds: &[CompileKind], host_triple: &str, target_cfg: Option<&[Cfg]>, kind: CompileKind, flags: Flags, ) -> CargoResult> { let target_applies_to_host = gctx.target_applies_to_host()?; // Host artifacts should not generally pick up rustflags from anywhere except [host]. // // The one exception to this is if `target-applies-to-host = true`, which opts into a // particular (inconsistent) past Cargo behavior where host artifacts _do_ pick up rustflags // set elsewhere when `--target` isn't passed. if kind.is_host() { if target_applies_to_host && requested_kinds == [CompileKind::Host] { // This is the past Cargo behavior where we fall back to the same logic as for other // artifacts without --target. } else { // In all other cases, host artifacts just get flags from [host], regardless of // --target. Or, phrased differently, no `--target` behaves the same as `--target // `, and host artifacts are always "special" (they don't pick up `RUSTFLAGS` for // example). return Ok(rustflags_from_host(gctx, flags, host_triple)?.unwrap_or_else(Vec::new)); } } // All other artifacts pick up the RUSTFLAGS, [target.*], and [build], in that order. // NOTE: It is impossible to have a [host] section and reach this logic with kind.is_host(), // since [host] implies `target-applies-to-host = false`, which always early-returns above. if let Some(rustflags) = rustflags_from_env(gctx, flags) { Ok(rustflags) } else if let Some(rustflags) = rustflags_from_target(gctx, host_triple, target_cfg, kind, flags)? { Ok(rustflags) } else if let Some(rustflags) = rustflags_from_build(gctx, flags)? { Ok(rustflags) } else { Ok(Vec::new()) } } /// Gets compiler flags from environment variables. /// See [`extra_args`] for more. fn rustflags_from_env(gctx: &GlobalContext, flags: Flags) -> Option> { // First try CARGO_ENCODED_RUSTFLAGS from the environment. // Prefer this over RUSTFLAGS since it's less prone to encoding errors. if let Ok(a) = gctx.get_env(format!("CARGO_ENCODED_{}", flags.as_env())) { if a.is_empty() { return Some(Vec::new()); } return Some(a.split('\x1f').map(str::to_string).collect()); } // Then try RUSTFLAGS from the environment if let Ok(a) = gctx.get_env(flags.as_env()) { let args = a .split(' ') .map(str::trim) .filter(|s| !s.is_empty()) .map(str::to_string); return Some(args.collect()); } // No rustflags to be collected from the environment None } /// Gets compiler flags from `[target]` section in the config. /// See [`extra_args`] for more. fn rustflags_from_target( gctx: &GlobalContext, host_triple: &str, target_cfg: Option<&[Cfg]>, kind: CompileKind, flag: Flags, ) -> CargoResult>> { let mut rustflags = Vec::new(); // Then the target.*.rustflags value... let target = match &kind { CompileKind::Host => host_triple, CompileKind::Target(target) => target.short_name(), }; let key = format!("target.{}.{}", target, flag.as_key()); if let Some(args) = gctx.get::>(&key)? { rustflags.extend(args.as_slice().iter().cloned()); } // ...including target.'cfg(...)'.rustflags if let Some(target_cfg) = target_cfg { gctx.target_cfgs()? .iter() .filter_map(|(key, cfg)| { match flag { Flags::Rust => cfg .rustflags .as_ref() .map(|rustflags| (key, &rustflags.val)), // `target.cfg(…).rustdocflags` is currently not supported. Flags::Rustdoc => None, } }) .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg)) .for_each(|(_key, cfg_rustflags)| { rustflags.extend(cfg_rustflags.as_slice().iter().cloned()); }); } if rustflags.is_empty() { Ok(None) } else { Ok(Some(rustflags)) } } /// Gets compiler flags from `[host]` section in the config. /// See [`extra_args`] for more. fn rustflags_from_host( gctx: &GlobalContext, flag: Flags, host_triple: &str, ) -> CargoResult>> { let target_cfg = gctx.host_cfg_triple(host_triple)?; let list = match flag { Flags::Rust => &target_cfg.rustflags, Flags::Rustdoc => { // host.rustdocflags is not a thing, since it does not make sense return Ok(None); } }; Ok(list.as_ref().map(|l| l.val.as_slice().to_vec())) } /// Gets compiler flags from `[build]` section in the config. /// See [`extra_args`] for more. fn rustflags_from_build(gctx: &GlobalContext, flag: Flags) -> CargoResult>> { // Then the `build.rustflags` value. let build = gctx.build_config()?; let list = match flag { Flags::Rust => &build.rustflags, Flags::Rustdoc => &build.rustdocflags, }; Ok(list.as_ref().map(|l| l.as_slice().to_vec())) } /// Collection of information about `rustc` and the host and target. pub struct RustcTargetData<'gctx> { /// Information about `rustc` itself. pub rustc: Rustc, /// Config pub gctx: &'gctx GlobalContext, requested_kinds: Vec, /// Build information for the "host", which is information about when /// `rustc` is invoked without a `--target` flag. This is used for /// selecting a linker, and applying link overrides. /// /// The configuration read into this depends on whether or not /// `target-applies-to-host=true`. host_config: TargetConfig, /// Information about the host platform. host_info: TargetInfo, /// Build information for targets that we're building for. target_config: HashMap, /// Information about the target platform that we're building for. target_info: HashMap, } impl<'gctx> RustcTargetData<'gctx> { #[tracing::instrument(skip_all)] pub fn new( ws: &Workspace<'gctx>, requested_kinds: &[CompileKind], ) -> CargoResult> { let gctx = ws.gctx(); let rustc = gctx.load_global_rustc(Some(ws))?; let mut target_config = HashMap::new(); let mut target_info = HashMap::new(); let target_applies_to_host = gctx.target_applies_to_host()?; let host_target = CompileTarget::new(&rustc.host, gctx.cli_unstable().json_target_spec)?; let host_info = TargetInfo::new(gctx, requested_kinds, &rustc, CompileKind::Host)?; // This config is used for link overrides and choosing a linker. let host_config = if target_applies_to_host { gctx.target_cfg_triple(&rustc.host)? } else { gctx.host_cfg_triple(&rustc.host)? }; // This is a hack. The unit_dependency graph builder "pretends" that // `CompileKind::Host` is `CompileKind::Target(host)` if the // `--target` flag is not specified. Since the unit_dependency code // needs access to the target config data, create a copy so that it // can be found. See `rebuild_unit_graph_shared` for why this is done. if requested_kinds.iter().any(CompileKind::is_host) { target_config.insert(host_target, gctx.target_cfg_triple(&rustc.host)?); // If target_applies_to_host is true, the host_info is the target info, // otherwise we need to build target info for the target. if target_applies_to_host { target_info.insert(host_target, host_info.clone()); } else { let host_target_info = TargetInfo::new( gctx, requested_kinds, &rustc, CompileKind::Target(host_target), )?; target_info.insert(host_target, host_target_info); } }; let mut res = RustcTargetData { rustc, gctx, requested_kinds: requested_kinds.into(), host_config, host_info, target_config, target_info, }; // Get all kinds we currently know about. // // For now, targets can only ever come from the root workspace // units and artifact dependencies, so this // correctly represents all the kinds that can happen. When we have // other ways for targets to appear at places that are not the root units, // we may have to revisit this. fn artifact_targets(package: &Package) -> impl Iterator + '_ { package .manifest() .dependencies() .iter() .filter_map(|d| d.artifact()?.target()?.to_compile_kind()) } let all_kinds = requested_kinds .iter() .copied() .chain(ws.members().flat_map(|p| { p.manifest() .default_kind() .into_iter() .chain(p.manifest().forced_kind()) .chain(artifact_targets(p)) })); for kind in all_kinds { res.merge_compile_kind(kind)?; } Ok(res) } /// Insert `kind` into our `target_info` and `target_config` members if it isn't present yet. pub fn merge_compile_kind(&mut self, kind: CompileKind) -> CargoResult<()> { if let CompileKind::Target(target) = kind { if !self.target_config.contains_key(&target) { self.target_config .insert(target, self.gctx.target_cfg_triple(target.short_name())?); } if !self.target_info.contains_key(&target) { self.target_info.insert( target, TargetInfo::new(self.gctx, &self.requested_kinds, &self.rustc, kind)?, ); } } Ok(()) } /// Returns a "short" name for the given kind, suitable for keying off /// configuration in Cargo or presenting to users. pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str { match kind { CompileKind::Host => &self.rustc.host, CompileKind::Target(target) => target.short_name(), } } /// Whether a dependency should be compiled for the host or target platform, /// specified by `CompileKind`. pub fn dep_platform_activated(&self, dep: &Dependency, kind: CompileKind) -> bool { // If this dependency is only available for certain platforms, // make sure we're only enabling it for that platform. let Some(platform) = dep.platform() else { return true; }; let name = self.short_name(&kind); platform.matches(name, self.cfg(kind)) } /// Gets the list of `cfg`s printed out from the compiler for the specified kind. pub fn cfg(&self, kind: CompileKind) -> &[Cfg] { self.info(kind).cfg() } /// Information about the given target platform, learned by querying rustc. /// /// # Panics /// /// Panics, if the target platform described by `kind` can't be found. /// See [`get_info`](Self::get_info) for a non-panicking alternative. pub fn info(&self, kind: CompileKind) -> &TargetInfo { self.get_info(kind).unwrap() } /// Information about the given target platform, learned by querying rustc. /// /// Returns `None` if the target platform described by `kind` can't be found. pub fn get_info(&self, kind: CompileKind) -> Option<&TargetInfo> { match kind { CompileKind::Host => Some(&self.host_info), CompileKind::Target(s) => self.target_info.get(&s), } } /// Gets the target configuration for a particular host or target. pub fn target_config(&self, kind: CompileKind) -> &TargetConfig { match kind { CompileKind::Host => &self.host_config, CompileKind::Target(s) => &self.target_config[&s], } } pub fn get_unsupported_std_targets(&self) -> Vec<&str> { let mut unsupported = Vec::new(); for (target, target_info) in &self.target_info { if target_info.supports_std == Some(false) { unsupported.push(target.short_name()); } } unsupported } pub fn requested_kinds(&self) -> &[CompileKind] { &self.requested_kinds } } ================================================ FILE: src/cargo/core/compiler/build_runner/compilation_files.rs ================================================ //! See [`CompilationFiles`]. use std::cell::OnceCell; use std::collections::HashMap; use std::fmt; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; use std::sync::Arc; use tracing::debug; use super::{BuildContext, BuildRunner, CompileKind, FileFlavor, Layout}; use crate::core::compiler::{CompileMode, CompileTarget, CrateType, FileType, Unit}; use crate::core::{Target, TargetKind, Workspace}; use crate::util::{self, CargoResult, OnceExt, StableHasher}; /// This is a generic version number that can be changed to make /// backwards-incompatible changes to any file structures in the output /// directory. For example, the fingerprint files or the build-script /// output files. /// /// Normally cargo updates ship with rustc updates which will /// cause a new hash due to the rustc version changing, but this allows /// cargo to be extra careful to deal with different versions of cargo that /// use the same rustc version. const METADATA_VERSION: u8 = 2; /// Uniquely identify a [`Unit`] under specific circumstances, see [`Metadata`] for more. #[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct UnitHash(u64); impl fmt::Display for UnitHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:016x}", self.0) } } impl fmt::Debug for UnitHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "UnitHash({:016x})", self.0) } } /// [`Metadata`] tracks several [`UnitHash`]s, including /// [`Metadata::unit_id`], [`Metadata::c_metadata`], and [`Metadata::c_extra_filename`]. /// /// We use a hash because it is an easy way to guarantee /// that all the inputs can be converted to a valid path. /// /// [`Metadata::unit_id`] is used to uniquely identify a unit in the build graph. /// This serves as a similar role as [`Metadata::c_extra_filename`] in that it uniquely identifies output /// on the filesystem except that its always present. /// /// [`Metadata::c_extra_filename`] is needed for cases like: /// - A project may depend on crate `A` and crate `B`, so the package name must be in the file name. /// - Similarly a project may depend on two versions of `A`, so the version must be in the file name. /// /// This also acts as the main layer of caching provided by Cargo /// so this must include all things that need to be distinguished in different parts of /// the same build. This is absolutely required or we override things before /// we get chance to use them. /// /// For example, we want to cache `cargo build` and `cargo doc` separately, so that running one /// does not invalidate the artifacts for the other. We do this by including [`CompileMode`] in the /// hash, thus the artifacts go in different folders and do not override each other. /// If we don't add something that we should have, for this reason, we get the /// correct output but rebuild more than is needed. /// /// Some things that need to be tracked to ensure the correct output should definitely *not* /// go in the `Metadata`. For example, the modification time of a file, should be tracked to make a /// rebuild when the file changes. However, it would be wasteful to include in the `Metadata`. The /// old artifacts are never going to be needed again. We can save space by just overwriting them. /// If we add something that we should not have, for this reason, we get the correct output but take /// more space than needed. This makes not including something in `Metadata` /// a form of cache invalidation. /// /// Note that the `Fingerprint` is in charge of tracking everything needed to determine if a /// rebuild is needed. /// /// [`Metadata::c_metadata`] is used for symbol mangling, because if you have two versions of /// the same crate linked together, their symbols need to be differentiated. /// /// You should avoid anything that would interfere with reproducible /// builds. For example, *any* absolute path should be avoided. This is one /// reason that `RUSTFLAGS` is not in [`Metadata::c_metadata`], because it often has /// absolute paths (like `--remap-path-prefix` which is fundamentally used for /// reproducible builds and has absolute paths in it). Also, in some cases the /// mangled symbols need to be stable between different builds with different /// settings. For example, profile-guided optimizations need to swap /// `RUSTFLAGS` between runs, but needs to keep the same symbol names. #[derive(Copy, Clone, Debug)] pub struct Metadata { unit_id: UnitHash, c_metadata: UnitHash, c_extra_filename: bool, pkg_dir: bool, } impl Metadata { /// A hash to identify a given [`Unit`] in the build graph pub fn unit_id(&self) -> UnitHash { self.unit_id } /// A hash to add to symbol naming through `-C metadata` pub fn c_metadata(&self) -> UnitHash { self.c_metadata } /// A hash to add to file names through `-C extra-filename` pub fn c_extra_filename(&self) -> Option { self.c_extra_filename.then_some(self.unit_id) } /// A hash to add to Cargo directory names pub fn pkg_dir(&self) -> Option { self.pkg_dir.then_some(self.unit_id) } } /// Collection of information about the files emitted by the compiler, and the /// output directory structure. pub struct CompilationFiles<'a, 'gctx> { /// The target directory layout for the host (and target if it is the same as host). pub(super) host: Layout, /// The target directory layout for the target (if different from then host). pub(super) target: HashMap, /// Additional directory to include a copy of the outputs. export_dir: Option, /// The root targets requested by the user on the command line (does not /// include dependencies). roots: Vec, ws: &'a Workspace<'gctx>, /// Metadata hash to use for each unit. metas: HashMap, /// For each Unit, a list all files produced. outputs: HashMap>>>, } /// Info about a single file emitted by the compiler. #[derive(Debug)] pub struct OutputFile { /// Absolute path to the file that will be produced by the build process. pub path: PathBuf, /// If it should be linked into `target`, and what it should be called /// (e.g., without metadata). pub hardlink: Option, /// If `--artifact-dir` is specified, the absolute path to the exported file. pub export_path: Option, /// Type of the file (library / debug symbol / else). pub flavor: FileFlavor, } impl OutputFile { /// Gets the hard link if present; otherwise, returns the path. pub fn bin_dst(&self) -> &PathBuf { match self.hardlink { Some(ref link_dst) => link_dst, None => &self.path, } } } impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> { pub(super) fn new( build_runner: &BuildRunner<'a, 'gctx>, host: Layout, target: HashMap, ) -> CompilationFiles<'a, 'gctx> { let mut metas = HashMap::new(); for unit in &build_runner.bcx.roots { metadata_of(unit, build_runner, &mut metas); } let outputs = metas .keys() .cloned() .map(|unit| (unit, OnceCell::new())) .collect(); CompilationFiles { ws: build_runner.bcx.ws, host, target, export_dir: build_runner.bcx.build_config.export_dir.clone(), roots: build_runner.bcx.roots.clone(), metas, outputs, } } /// Returns the appropriate directory layout for either a plugin or not. pub fn layout(&self, kind: CompileKind) -> &Layout { match kind { CompileKind::Host => &self.host, CompileKind::Target(target) => &self.target[&target], } } /// Gets the metadata for the given unit. /// /// See [`Metadata`] and [`fingerprint`] module for more. /// /// [`fingerprint`]: super::super::fingerprint#fingerprints-and-metadata pub fn metadata(&self, unit: &Unit) -> Metadata { self.metas[unit] } /// Gets the short hash based only on the `PackageId`. /// Used for the metadata when `c_extra_filename` returns `None`. fn target_short_hash(&self, unit: &Unit) -> String { let hashable = unit.pkg.package_id().stable_hash(self.ws.root()); util::short_hash(&(METADATA_VERSION, hashable)) } /// Returns the directory where the artifacts for the given unit are /// initially created. pub fn output_dir(&self, unit: &Unit) -> PathBuf { // Docscrape units need to have doc/ set as the out_dir so sources for reverse-dependencies // will be put into doc/ and not into deps/ where the *.examples files are stored. if unit.mode.is_doc() || unit.mode.is_doc_scrape() { self.layout(unit.kind) .artifact_dir() .expect("artifact-dir was not locked") .doc() .to_path_buf() } else if unit.mode.is_doc_test() { panic!("doc tests do not have an out dir"); } else if unit.target.is_custom_build() { self.build_script_dir(unit) } else if unit.target.is_example() && !self.ws.gctx().cli_unstable().build_dir_new_layout { self.layout(unit.kind).build_dir().examples().to_path_buf() } else if unit.artifact.is_true() { self.artifact_dir(unit) } else { self.deps_dir(unit).to_path_buf() } } /// Additional export directory from `--artifact-dir`. pub fn export_dir(&self) -> Option { self.export_dir.clone() } /// Directory name to use for a package in the form `{NAME}/{HASH}`. /// /// Note that some units may share the same directory, so care should be /// taken in those cases! fn pkg_dir(&self, unit: &Unit) -> String { let separator = match self.ws.gctx().cli_unstable().build_dir_new_layout { true => "/", false => "-", }; let name = unit.pkg.package_id().name(); let hash = self.unit_hash(unit); format!("{name}{separator}{hash}") } /// The directory hash to use for a given unit pub fn unit_hash(&self, unit: &Unit) -> String { self.metas[unit] .pkg_dir() .map(|h| h.to_string()) .unwrap_or_else(|| self.target_short_hash(unit)) } /// Returns the final artifact path for the host (`/…/target/debug`) pub fn host_dest(&self) -> Option<&Path> { self.host.artifact_dir().map(|v| v.dest()) } /// Returns the root of the build output tree for the host (`/…/build-dir`) pub fn host_build_root(&self) -> &Path { self.host.build_dir().root() } /// Returns the host `deps` directory path for a given build unit. pub fn host_deps(&self, unit: &Unit) -> PathBuf { let dir = self.pkg_dir(unit); self.host.build_dir().deps(&dir) } /// Returns the directories where Rust crate dependencies are found for the /// specified unit. pub fn deps_dir(&self, unit: &Unit) -> PathBuf { let dir = self.pkg_dir(unit); self.layout(unit.kind).build_dir().deps(&dir) } /// Returns the directories where Rust crate dependencies are found for the /// specified unit. (new layout) /// /// New features should consider using this so we can avoid their migrations. pub fn out_dir_new_layout(&self, unit: &Unit) -> PathBuf { let dir = self.pkg_dir(unit); self.layout(unit.kind) .build_dir() .out_force_new_layout(&dir) } /// Directory where the fingerprint for the given unit should go. pub fn fingerprint_dir(&self, unit: &Unit) -> PathBuf { let dir = self.pkg_dir(unit); self.layout(unit.kind).build_dir().fingerprint(&dir) } /// The lock location for a given build unit. pub fn build_unit_lock(&self, unit: &Unit) -> PathBuf { let dir = self.pkg_dir(unit); self.layout(unit.kind) .build_dir() .build_unit(&dir) .join(".lock") } /// Directory where incremental output for the given unit should go. pub fn incremental_dir(&self, unit: &Unit) -> &Path { self.layout(unit.kind).build_dir().incremental() } /// Directory where timing output should go. pub fn timings_dir(&self) -> Option<&Path> { self.host.artifact_dir().map(|v| v.timings()) } /// Returns the path for a file in the fingerprint directory. /// /// The "prefix" should be something to distinguish the file from other /// files in the fingerprint directory. pub fn fingerprint_file_path(&self, unit: &Unit, prefix: &str) -> PathBuf { // Different targets need to be distinguished in the let kind = unit.target.kind().description(); let flavor = if unit.mode.is_any_test() { "test-" } else if unit.mode.is_doc() { "doc-" } else if unit.mode.is_run_custom_build() { "run-" } else { "" }; let name = format!("{}{}{}-{}", prefix, flavor, kind, unit.target.name()); self.fingerprint_dir(unit).join(name) } /// Path where compiler output is cached. pub fn message_cache_path(&self, unit: &Unit) -> PathBuf { self.fingerprint_file_path(unit, "output-") } /// Returns the directory where a compiled build script is stored. /// `/path/to/target/{debug,release}/build/PKG-HASH` pub fn build_script_dir(&self, unit: &Unit) -> PathBuf { assert!(unit.target.is_custom_build()); assert!(!unit.mode.is_run_custom_build()); assert!(self.metas.contains_key(unit)); let dir = self.pkg_dir(unit); self.layout(CompileKind::Host) .build_dir() .build_script(&dir) } /// Returns the directory for compiled artifacts files. /// `/path/to/target/{debug,release}/deps/artifact/KIND/PKG-HASH` fn artifact_dir(&self, unit: &Unit) -> PathBuf { assert!(self.metas.contains_key(unit)); assert!(unit.artifact.is_true()); let dir = self.pkg_dir(unit); let kind = match unit.target.kind() { TargetKind::Bin => "bin", TargetKind::Lib(lib_kinds) => match lib_kinds.as_slice() { &[CrateType::Cdylib] => "cdylib", &[CrateType::Staticlib] => "staticlib", invalid => unreachable!( "BUG: unexpected artifact library type(s): {:?} - these should have been split", invalid ), }, invalid => unreachable!( "BUG: {:?} are not supposed to be used as artifacts", invalid ), }; self.layout(unit.kind).build_dir().artifact(&dir, kind) } /// Returns the directory where information about running a build script /// is stored. /// `/path/to/target/{debug,release}/build/PKG-HASH` pub fn build_script_run_dir(&self, unit: &Unit) -> PathBuf { assert!(unit.target.is_custom_build()); assert!(unit.mode.is_run_custom_build()); let dir = self.pkg_dir(unit); self.layout(unit.kind) .build_dir() .build_script_execution(&dir) } /// Returns the "`OUT_DIR`" directory for running a build script. /// `/path/to/target/{debug,release}/build/PKG-HASH/out` pub fn build_script_out_dir(&self, unit: &Unit) -> PathBuf { self.build_script_run_dir(unit).join("out") } /// Returns the path to the executable binary for the given bin target. /// /// This should only to be used when a `Unit` is not available. pub fn bin_link_for_target( &self, target: &Target, kind: CompileKind, bcx: &BuildContext<'_, '_>, ) -> CargoResult> { assert!(target.is_bin()); let Some(dest) = self.layout(kind).artifact_dir().map(|v| v.dest()) else { return Ok(None); }; let info = bcx.target_data.info(kind); let (file_types, _) = info .rustc_outputs( CompileMode::Build, &TargetKind::Bin, bcx.target_data.short_name(&kind), bcx.gctx, ) .expect("target must support `bin`"); let file_type = file_types .iter() .find(|file_type| file_type.flavor == FileFlavor::Normal) .expect("target must support `bin`"); Ok(Some(dest.join(file_type.uplift_filename(target)))) } /// Returns the filenames that the given unit will generate. /// /// Note: It is not guaranteed that all of the files will be generated. pub(super) fn outputs( &self, unit: &Unit, bcx: &BuildContext<'a, 'gctx>, ) -> CargoResult>> { self.outputs[unit] .try_borrow_with(|| self.calc_outputs(unit, bcx)) .map(Arc::clone) } /// Returns the path where the output for the given unit and `FileType` /// should be uplifted to. /// /// Returns `None` if the unit shouldn't be uplifted (for example, a /// dependent rlib). fn uplift_to(&self, unit: &Unit, file_type: &FileType, from_path: &Path) -> Option { // Tests, check, doc, etc. should not be uplifted. if unit.mode != CompileMode::Build || file_type.flavor == FileFlavor::Rmeta { return None; } // Artifact dependencies are never uplifted. if unit.artifact.is_true() { return None; } // - Binaries: The user always wants to see these, even if they are // implicitly built (for example for integration tests). // - dylibs: This ensures that the dynamic linker pulls in all the // latest copies (even if the dylib was built from a previous cargo // build). There are complex reasons for this, see #8139, #6167, #6162. // - Things directly requested from the command-line (the "roots"). // This one is a little questionable for rlibs (see #6131), but is // historically how Cargo has operated. This is primarily useful to // give the user access to staticlibs and cdylibs. if !unit.target.is_bin() && !unit.target.is_custom_build() && file_type.crate_type != Some(CrateType::Dylib) && !self.roots.contains(unit) { return None; } let filename = file_type.uplift_filename(&unit.target); let uplift_path = if unit.target.is_example() { // Examples live in their own little world. self.layout(unit.kind) .artifact_dir()? .examples() .join(filename) } else if unit.target.is_custom_build() { self.build_script_dir(unit).join(filename) } else { self.layout(unit.kind).artifact_dir()?.dest().join(filename) }; if from_path == uplift_path { // This can happen with things like examples that reside in the // same directory, do not have a metadata hash (like on Windows), // and do not have hyphens. return None; } Some(uplift_path) } /// Calculates the filenames that the given unit will generate. /// Should use [`CompilationFiles::outputs`] instead /// as it caches the result of this function. fn calc_outputs( &self, unit: &Unit, bcx: &BuildContext<'a, 'gctx>, ) -> CargoResult>> { let ret = match unit.mode { _ if unit.skip_non_compile_time_dep => { // This skips compilations so no outputs vec![] } CompileMode::Doc => { let path = if bcx.build_config.intent.wants_doc_json_output() { self.output_dir(unit) .join(format!("{}.json", unit.target.crate_name())) } else { self.output_dir(unit) .join(unit.target.crate_name()) .join("index.html") }; let mut outputs = vec![OutputFile { path, hardlink: None, export_path: None, flavor: FileFlavor::Normal, }]; if bcx.gctx.cli_unstable().rustdoc_mergeable_info { // `-Zrustdoc-mergeable-info` always uses the new layout. outputs.push(OutputFile { path: self .out_dir_new_layout(unit) .join(unit.target.crate_name()) .with_extension("json"), hardlink: None, export_path: None, flavor: FileFlavor::DocParts, }) } outputs } CompileMode::RunCustomBuild => { // At this time, this code path does not handle build script // outputs. vec![] } CompileMode::Doctest => { // Doctests are built in a temporary directory and then // deleted. There is the `--persist-doctests` unstable flag, // but Cargo does not know about that. vec![] } CompileMode::Docscrape => { // The file name needs to be stable across Cargo sessions. // This originally used unit.buildkey(), but that isn't stable, // so we use metadata instead (prefixed with name for debugging). let file_name = format!( "{}-{}.examples", unit.pkg.name(), self.metadata(unit).unit_id() ); let path = self.deps_dir(unit).join(file_name); vec![OutputFile { path, hardlink: None, export_path: None, flavor: FileFlavor::Normal, }] } CompileMode::Test | CompileMode::Build | CompileMode::Check { .. } => { let mut outputs = self.calc_outputs_rustc(unit, bcx)?; if bcx.build_config.sbom && bcx.gctx.cli_unstable().sbom { let sbom_files: Vec<_> = outputs .iter() .filter(|o| matches!(o.flavor, FileFlavor::Normal | FileFlavor::Linkable)) .map(|output| OutputFile { path: Self::append_sbom_suffix(&output.path), hardlink: output.hardlink.as_ref().map(Self::append_sbom_suffix), export_path: output.export_path.as_ref().map(Self::append_sbom_suffix), flavor: FileFlavor::Sbom, }) .collect(); outputs.extend(sbom_files.into_iter()); } outputs } }; debug!("Target filenames: {:?}", ret); Ok(Arc::new(ret)) } /// Append the SBOM suffix to the file name. fn append_sbom_suffix(link: &PathBuf) -> PathBuf { const SBOM_FILE_EXTENSION: &str = ".cargo-sbom.json"; let mut link_buf = link.clone().into_os_string(); link_buf.push(SBOM_FILE_EXTENSION); PathBuf::from(link_buf) } /// Computes the actual, full pathnames for all the files generated by rustc. /// /// The `OutputFile` also contains the paths where those files should be /// "uplifted" to. fn calc_outputs_rustc( &self, unit: &Unit, bcx: &BuildContext<'a, 'gctx>, ) -> CargoResult> { let out_dir = self.output_dir(unit); let info = bcx.target_data.info(unit.kind); let triple = bcx.target_data.short_name(&unit.kind); let (file_types, unsupported) = info.rustc_outputs(unit.mode, unit.target.kind(), triple, bcx.gctx)?; if file_types.is_empty() { if !unsupported.is_empty() { let unsupported_strs: Vec<_> = unsupported.iter().map(|ct| ct.as_str()).collect(); anyhow::bail!( "cannot produce {} for `{}` as the target `{}` \ does not support these crate types", unsupported_strs.join(", "), unit.pkg, triple, ) } anyhow::bail!( "cannot compile `{}` as the target `{}` does not \ support any of the output crate types", unit.pkg, triple, ); } // Convert FileType to OutputFile. let mut outputs = Vec::new(); for file_type in file_types { let meta = self.metas[unit]; let meta_opt = meta.c_extra_filename().map(|h| h.to_string()); let path = out_dir.join(file_type.output_filename(&unit.target, meta_opt.as_deref())); // If, the `different_binary_name` feature is enabled, the name of the hardlink will // be the name of the binary provided by the user in `Cargo.toml`. let hardlink = self.uplift_to(unit, &file_type, &path); let export_path = if unit.target.is_custom_build() { None } else { self.export_dir.as_ref().and_then(|export_dir| { hardlink .as_ref() .map(|hardlink| export_dir.join(hardlink.file_name().unwrap())) }) }; outputs.push(OutputFile { path, hardlink, export_path, flavor: file_type.flavor, }); } Ok(outputs) } } /// Gets the metadata hash for the given [`Unit`]. /// /// When a metadata hash doesn't exist for the given unit, /// this calls itself recursively to compute metadata hashes of all its dependencies. /// See [`compute_metadata`] for how a single metadata hash is computed. fn metadata_of<'a>( unit: &Unit, build_runner: &BuildRunner<'_, '_>, metas: &'a mut HashMap, ) -> &'a Metadata { if !metas.contains_key(unit) { let meta = compute_metadata(unit, build_runner, metas); metas.insert(unit.clone(), meta); for dep in build_runner.unit_deps(unit) { metadata_of(&dep.unit, build_runner, metas); } } &metas[unit] } /// Computes the metadata hash for the given [`Unit`]. fn compute_metadata( unit: &Unit, build_runner: &BuildRunner<'_, '_>, metas: &mut HashMap, ) -> Metadata { let bcx = &build_runner.bcx; let deps_metadata = build_runner .unit_deps(unit) .iter() .map(|dep| *metadata_of(&dep.unit, build_runner, metas)) .collect::>(); let c_extra_filename = use_extra_filename(bcx, unit); let pkg_dir = use_pkg_dir(bcx, unit); let mut shared_hasher = StableHasher::new(); METADATA_VERSION.hash(&mut shared_hasher); let ws_root = if unit.is_std { // SourceId for stdlib crates is an absolute path inside the sysroot. // Pass the sysroot as workspace root so that we hash a relative path. // This avoids the metadata hash changing depending on where the user installed rustc. &bcx.target_data.get_info(unit.kind).unwrap().sysroot } else { bcx.ws.root() }; // Unique metadata per (name, source, version) triple. This'll allow us // to pull crates from anywhere without worrying about conflicts. unit.pkg .package_id() .stable_hash(ws_root) .hash(&mut shared_hasher); // Also mix in enabled features to our metadata. This'll ensure that // when changing feature sets each lib is separately cached. unit.features.hash(&mut shared_hasher); // Throw in the profile we're compiling with. This helps caching // `panic=abort` and `panic=unwind` artifacts, additionally with various // settings like debuginfo and whatnot. unit.profile.hash(&mut shared_hasher); unit.mode.hash(&mut shared_hasher); build_runner.lto[unit].hash(&mut shared_hasher); // Artifacts compiled for the host should have a different // metadata piece than those compiled for the target, so make sure // we throw in the unit's `kind` as well. Use `fingerprint_hash` // so that the StableHash doesn't change based on the pathnames // of the custom target JSON spec files. unit.kind.fingerprint_hash().hash(&mut shared_hasher); // Finally throw in the target name/kind. This ensures that concurrent // compiles of targets in the same crate don't collide. unit.target.name().hash(&mut shared_hasher); unit.target.kind().hash(&mut shared_hasher); hash_rustc_version(bcx, &mut shared_hasher, unit); if build_runner.bcx.ws.is_member(&unit.pkg) { // This is primarily here for clippy. This ensures that the clippy // artifacts are separate from the `check` ones. if let Some(path) = &build_runner.bcx.rustc().workspace_wrapper { path.hash(&mut shared_hasher); } } // Seed the contents of `__CARGO_DEFAULT_LIB_METADATA` to the hasher if present. // This should be the release channel, to get a different hash for each channel. if let Ok(ref channel) = build_runner .bcx .gctx .get_env("__CARGO_DEFAULT_LIB_METADATA") { channel.hash(&mut shared_hasher); } // std units need to be kept separate from user dependencies. std crates // are differentiated in the Unit with `is_std` (for things like // `-Zforce-unstable-if-unmarked`), so they are always built separately. // This isn't strictly necessary for build dependencies which probably // don't need unstable support. A future experiment might be to set // `is_std` to false for build dependencies so that they can be shared // with user dependencies. unit.is_std.hash(&mut shared_hasher); // While we don't hash RUSTFLAGS because it may contain absolute paths that // hurts reproducibility, we track whether a unit's RUSTFLAGS is from host // config, so that we can generate a different metadata hash for runtime // and compile-time units. // // HACK: This is a temporary hack for fixing rust-lang/cargo#14253 // Need to find a long-term solution to replace this fragile workaround. // See https://github.com/rust-lang/cargo/pull/14432#discussion_r1725065350 if unit.kind.is_host() && !bcx.gctx.target_applies_to_host().unwrap_or_default() { let host_info = bcx.target_data.info(CompileKind::Host); let target_configs_are_different = unit.rustflags != host_info.rustflags || unit.rustdocflags != host_info.rustdocflags || bcx .target_data .target_config(CompileKind::Host) .links_overrides != unit.links_overrides; target_configs_are_different.hash(&mut shared_hasher); } let mut c_metadata_hasher = shared_hasher.clone(); // Mix in the target-metadata of all the dependencies of this target. let mut dep_c_metadata_hashes = deps_metadata .iter() .map(|m| m.c_metadata) .collect::>(); dep_c_metadata_hashes.sort(); dep_c_metadata_hashes.hash(&mut c_metadata_hasher); let mut unit_id_hasher = shared_hasher.clone(); // Mix in the target-metadata of all the dependencies of this target. let mut dep_unit_id_hashes = deps_metadata.iter().map(|m| m.unit_id).collect::>(); dep_unit_id_hashes.sort(); dep_unit_id_hashes.hash(&mut unit_id_hasher); // Avoid trashing the caches on RUSTFLAGS changing via `unit_id` // // Limited to `unit_id` to help with reproducible build / PGO issues. let default = Vec::new(); let extra_args = build_runner.bcx.extra_args_for(unit).unwrap_or(&default); if !has_remap_path_prefix(&extra_args) { extra_args.hash(&mut unit_id_hasher); } if unit.mode.is_doc() || unit.mode.is_doc_scrape() { if !has_remap_path_prefix(&unit.rustdocflags) { unit.rustdocflags.hash(&mut unit_id_hasher); } } else { if !has_remap_path_prefix(&unit.rustflags) { unit.rustflags.hash(&mut unit_id_hasher); } } let c_metadata = UnitHash(Hasher::finish(&c_metadata_hasher)); let unit_id = UnitHash(Hasher::finish(&unit_id_hasher)); Metadata { unit_id, c_metadata, c_extra_filename, pkg_dir, } } /// HACK: Detect the *potential* presence of `--remap-path-prefix` /// /// As CLI parsing is contextual and dependent on the CLI definition to understand the context, we /// can't say for sure whether `--remap-path-prefix` is present, so we guess if anything looks like /// it. /// If we could, we'd strip it out for hashing. /// Instead, we use this to avoid hashing rustflags if it might be present to avoid the risk of taking /// a flag that is trying to make things reproducible and making things less reproducible by the /// `-Cextra-filename` showing up in the rlib, even with `split-debuginfo`. fn has_remap_path_prefix(args: &[String]) -> bool { args.iter() .any(|s| s.starts_with("--remap-path-prefix=") || s == "--remap-path-prefix") } /// Hash the version of rustc being used during the build process. fn hash_rustc_version(bcx: &BuildContext<'_, '_>, hasher: &mut StableHasher, unit: &Unit) { let vers = &bcx.rustc().version; if vers.pre.is_empty() || bcx.gctx.cli_unstable().separate_nightlies { // For stable, keep the artifacts separate. This helps if someone is // testing multiple versions, to avoid recompiles. Note though that for // cross-compiled builds the `host:` line of `verbose_version` is // omitted since rustc should produce the same output for each target // regardless of the host. for line in bcx.rustc().verbose_version.lines() { if unit.kind.is_host() || !line.starts_with("host: ") { line.hash(hasher); } } return; } // On "nightly"/"beta"/"dev"/etc, keep each "channel" separate. Don't hash // the date/git information, so that whenever someone updates "nightly", // they won't have a bunch of stale artifacts in the target directory. // // This assumes that the first segment is the important bit ("nightly", // "beta", "dev", etc.). Skip other parts like the `.3` in `-beta.3`. vers.pre.split('.').next().hash(hasher); // Keep "host" since some people switch hosts to implicitly change // targets, (like gnu vs musl or gnu vs msvc). In the future, we may want // to consider hashing `unit.kind.short_name()` instead. if unit.kind.is_host() { bcx.rustc().host.hash(hasher); } // None of the other lines are important. Currently they are: // binary: rustc <-- or "rustdoc" // commit-hash: 38114ff16e7856f98b2b4be7ab4cd29b38bed59a // commit-date: 2020-03-21 // host: x86_64-apple-darwin // release: 1.44.0-nightly // LLVM version: 9.0 // // The backend version ("LLVM version") might become more relevant in // the future when cranelift sees more use, and people want to switch // between different backends without recompiling. } /// Returns whether or not this unit should use a hash in the filename to make it unique. fn use_extra_filename(bcx: &BuildContext<'_, '_>, unit: &Unit) -> bool { if unit.mode.is_doc_test() || unit.mode.is_doc() { // Doc tests do not have metadata. return false; } if bcx.gctx.cli_unstable().build_dir_new_layout { if unit.mode.is_any_test() || unit.mode.is_check() { // These always use metadata. return true; } // No metadata in these cases: // // - dylib, cdylib, executable: `pkg_dir` avoids collisions for us and rustc isn't looking these // up by `-Cextra-filename` // // The __CARGO_DEFAULT_LIB_METADATA env var is used to override this to // force metadata in the hash. This is only used for building libstd. For // example, if libstd is placed in a common location, we don't want a file // named /usr/lib/libstd.so which could conflict with other rustc // installs. In addition it prevents accidentally loading a libstd of a // different compiler at runtime. // See https://github.com/rust-lang/cargo/issues/3005 if (unit.target.is_dylib() || unit.target.is_cdylib() || unit.target.is_executable()) && bcx.gctx.get_env("__CARGO_DEFAULT_LIB_METADATA").is_err() { return false; } } else { if unit.mode.is_any_test() || unit.mode.is_check() { // These always use metadata. return true; } // No metadata in these cases: // // - dylibs: // - if any dylib names are encoded in executables, so they can't be renamed. // - TODO: Maybe use `-install-name` on macOS or `-soname` on other UNIX systems // to specify the dylib name to be used by the linker instead of the filename. // - Windows MSVC executables: The path to the PDB is embedded in the // executable, and we don't want the PDB path to include the hash in it. // - wasm32-unknown-emscripten executables: When using emscripten, the path to the // .wasm file is embedded in the .js file, so we don't want the hash in there. // // This is only done for local packages, as we don't expect to export // dependencies. // // The __CARGO_DEFAULT_LIB_METADATA env var is used to override this to // force metadata in the hash. This is only used for building libstd. For // example, if libstd is placed in a common location, we don't want a file // named /usr/lib/libstd.so which could conflict with other rustc // installs. In addition it prevents accidentally loading a libstd of a // different compiler at runtime. // See https://github.com/rust-lang/cargo/issues/3005 let short_name = bcx.target_data.short_name(&unit.kind); if (unit.target.is_dylib() || unit.target.is_cdylib() || (unit.target.is_executable() && short_name == "wasm32-unknown-emscripten") || (unit.target.is_executable() && short_name.contains("msvc"))) && unit.pkg.package_id().source_id().is_path() && bcx.gctx.get_env("__CARGO_DEFAULT_LIB_METADATA").is_err() { return false; } } true } /// Returns whether or not this unit should use a hash in the pkg_dir to make it unique. fn use_pkg_dir(bcx: &BuildContext<'_, '_>, unit: &Unit) -> bool { if unit.mode.is_doc_test() || unit.mode.is_doc() { // Doc tests do not have metadata. return false; } if bcx.gctx.cli_unstable().build_dir_new_layout { // These always use metadata. return true; } if unit.mode.is_any_test() || unit.mode.is_check() { // These always use metadata. return true; } // No metadata in these cases: // // - dylibs: // - if any dylib names are encoded in executables, so they can't be renamed. // - TODO: Maybe use `-install-name` on macOS or `-soname` on other UNIX systems // to specify the dylib name to be used by the linker instead of the filename. // - Windows MSVC executables: The path to the PDB is embedded in the // executable, and we don't want the PDB path to include the hash in it. // - wasm32-unknown-emscripten executables: When using emscripten, the path to the // .wasm file is embedded in the .js file, so we don't want the hash in there. // // This is only done for local packages, as we don't expect to export // dependencies. // // The __CARGO_DEFAULT_LIB_METADATA env var is used to override this to // force metadata in the hash. This is only used for building libstd. For // example, if libstd is placed in a common location, we don't want a file // named /usr/lib/libstd.so which could conflict with other rustc // installs. In addition it prevents accidentally loading a libstd of a // different compiler at runtime. // See https://github.com/rust-lang/cargo/issues/3005 let short_name = bcx.target_data.short_name(&unit.kind); if (unit.target.is_dylib() || unit.target.is_cdylib() || (unit.target.is_executable() && short_name == "wasm32-unknown-emscripten") || (unit.target.is_executable() && short_name.contains("msvc"))) && unit.pkg.package_id().source_id().is_path() && bcx.gctx.get_env("__CARGO_DEFAULT_LIB_METADATA").is_err() { return false; } true } ================================================ FILE: src/cargo/core/compiler/build_runner/mod.rs ================================================ //! [`BuildRunner`] is the mutable state used during the build process. use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use crate::core::PackageId; use crate::core::compiler::compilation::{self, UnitOutput}; use crate::core::compiler::locking::LockManager; use crate::core::compiler::{self, Unit, UserIntent, artifact}; use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use annotate_snippets::{Level, Message}; use anyhow::{Context as _, bail}; use cargo_util::paths; use filetime::FileTime; use itertools::Itertools; use jobserver::Client; use super::RustdocFingerprint; use super::custom_build::{self, BuildDeps, BuildScriptOutputs, BuildScripts}; use super::fingerprint::{Checksum, Fingerprint}; use super::job_queue::JobQueue; use super::layout::Layout; use super::lto::Lto; use super::unit_graph::UnitDep; use super::{BuildContext, Compilation, CompileKind, CompileMode, Executor, FileFlavor}; mod compilation_files; use self::compilation_files::CompilationFiles; pub use self::compilation_files::{Metadata, OutputFile, UnitHash}; /// Collection of all the stuff that is needed to perform a build. /// /// Different from the [`BuildContext`], `Context` is a _mutable_ state used /// throughout the entire build process. Everything is coordinated through this. /// /// [`BuildContext`]: crate::core::compiler::BuildContext pub struct BuildRunner<'a, 'gctx> { /// Mostly static information about the build task. pub bcx: &'a BuildContext<'a, 'gctx>, /// A large collection of information about the result of the entire compilation. pub compilation: Compilation<'gctx>, /// Output from build scripts, updated after each build script runs. pub build_script_outputs: Arc>, /// Dependencies (like rerun-if-changed) declared by a build script. /// This is *only* populated from the output from previous runs. /// If the build script hasn't ever been run, then it must be run. pub build_explicit_deps: HashMap, /// Fingerprints used to detect if a unit is out-of-date. pub fingerprints: HashMap>, /// Cache of file mtimes to reduce filesystem hits. pub mtime_cache: HashMap, /// Cache of file checksums to reduce filesystem reads. pub checksum_cache: HashMap, /// A set used to track which units have been compiled. /// A unit may appear in the job graph multiple times as a dependency of /// multiple packages, but it only needs to run once. pub compiled: HashSet, /// Linking information for each `Unit`. /// See `build_map` for details. pub build_scripts: HashMap>, /// Job server client to manage concurrency with other processes. pub jobserver: Client, /// "Primary" packages are the ones the user selected on the command-line /// with `-p` flags. If no flags are specified, then it is the defaults /// based on the current directory and the default workspace members. primary_packages: HashSet, /// An abstraction of the files and directories that will be generated by /// the compilation. This is `None` until after `unit_dependencies` has /// been computed. files: Option>, /// A set of units which are compiling rlibs and are expected to produce /// metadata files in addition to the rlib itself. rmeta_required: HashSet, /// Map of the LTO-status of each unit. This indicates what sort of /// compilation is happening (only object, only bitcode, both, etc), and is /// precalculated early on. pub lto: HashMap, /// Map of Doc/Docscrape units to metadata for their -Cmetadata flag. /// See `Context::find_metadata_units` for more details. pub metadata_for_doc_units: HashMap, /// Set of metadata of Docscrape units that fail before completion, e.g. /// because the target has a type error. This is in an Arc> /// because it is continuously updated as the job progresses. pub failed_scrape_units: Arc>>, /// Manages locks for build units when fine grain locking is enabled. pub lock_manager: Arc, } impl<'a, 'gctx> BuildRunner<'a, 'gctx> { pub fn new(bcx: &'a BuildContext<'a, 'gctx>) -> CargoResult { // Load up the jobserver that we'll use to manage our parallelism. This // is the same as the GNU make implementation of a jobserver, and // intentionally so! It's hoped that we can interact with GNU make and // all share the same jobserver. // // Note that if we don't have a jobserver in our environment then we // create our own, and we create it with `n` tokens, but immediately // acquire one, because one token is ourself, a running process. let jobserver = match bcx.gctx.jobserver_from_env() { Some(c) => c.clone(), None => { let client = Client::new(bcx.jobs() as usize).context("failed to create jobserver")?; client.acquire_raw()?; client } }; Ok(Self { bcx, compilation: Compilation::new(bcx)?, build_script_outputs: Arc::new(Mutex::new(BuildScriptOutputs::default())), fingerprints: HashMap::new(), mtime_cache: HashMap::new(), checksum_cache: HashMap::new(), compiled: HashSet::new(), build_scripts: HashMap::new(), build_explicit_deps: HashMap::new(), jobserver, primary_packages: HashSet::new(), files: None, rmeta_required: HashSet::new(), lto: HashMap::new(), metadata_for_doc_units: HashMap::new(), failed_scrape_units: Arc::new(Mutex::new(HashSet::new())), lock_manager: Arc::new(LockManager::new()), }) } /// Dry-run the compilation without actually running it. /// /// This is expected to collect information like the location of output artifacts. /// Please keep in sync with non-compilation part in [`BuildRunner::compile`]. pub fn dry_run(mut self) -> CargoResult> { let _lock = self .bcx .gctx .acquire_package_cache_lock(CacheLockMode::Shared)?; self.lto = super::lto::generate(self.bcx)?; self.prepare_units()?; self.prepare()?; self.check_collisions()?; for unit in &self.bcx.roots { self.collect_tests_and_executables(unit)?; } Ok(self.compilation) } /// Starts compilation, waits for it to finish, and returns information /// about the result of compilation. /// /// See [`ops::cargo_compile`] for a higher-level view of the compile process. /// /// [`ops::cargo_compile`]: crate::ops::cargo_compile #[tracing::instrument(skip_all)] pub fn compile(mut self, exec: &Arc) -> CargoResult> { // A shared lock is held during the duration of the build since rustc // needs to read from the `src` cache, and we don't want other // commands modifying the `src` cache while it is running. let _lock = self .bcx .gctx .acquire_package_cache_lock(CacheLockMode::Shared)?; let mut queue = JobQueue::new(self.bcx); self.lto = super::lto::generate(self.bcx)?; self.prepare_units()?; self.prepare()?; custom_build::build_map(&mut self)?; self.check_collisions()?; self.compute_metadata_for_doc_units(); // We need to make sure that if there were any previous docs already compiled, // they were compiled with the same Rustc version that we're currently using. // See the function doc comment for more. if self.bcx.build_config.intent.is_doc() { RustdocFingerprint::check_rustdoc_fingerprint(&self)? } for unit in &self.bcx.roots { let force_rebuild = self.bcx.build_config.force_rebuild; super::compile(&mut self, &mut queue, unit, exec, force_rebuild)?; } // Now that we've got the full job queue and we've done all our // fingerprint analysis to determine what to run, bust all the memoized // fingerprint hashes to ensure that during the build they all get the // most up-to-date values. In theory we only need to bust hashes that // transitively depend on a dirty build script, but it shouldn't matter // that much for performance anyway. for fingerprint in self.fingerprints.values() { fingerprint.clear_memoized(); } // Now that we've figured out everything that we're going to do, do it! queue.execute(&mut self)?; // Add `OUT_DIR` to env vars if unit has a build script. let units_with_build_script = &self .bcx .roots .iter() .filter(|unit| self.build_scripts.contains_key(unit)) .dedup_by(|x, y| x.pkg.package_id() == y.pkg.package_id()) .collect::>(); for unit in units_with_build_script { for dep in &self.bcx.unit_graph[unit] { if dep.unit.mode.is_run_custom_build() { let out_dir = if self.bcx.gctx.cli_unstable().build_dir_new_layout { self.files().out_dir_new_layout(&dep.unit) } else { self.files().build_script_out_dir(&dep.unit) }; let script_meta = self.get_run_build_script_metadata(&dep.unit); self.compilation .extra_env .entry(script_meta) .or_insert_with(Vec::new) .push(("OUT_DIR".to_string(), out_dir.display().to_string())); } } } self.collect_doc_merge_info()?; // Collect the result of the build into `self.compilation`. for unit in &self.bcx.roots { self.collect_tests_and_executables(unit)?; // Collect information for `rustdoc --test`. if unit.mode.is_doc_test() { let mut unstable_opts = false; let mut args = compiler::extern_args(&self, unit, &mut unstable_opts)?; args.extend(compiler::lib_search_paths(&self, unit)?); args.extend(compiler::lto_args(&self, unit)); args.extend(compiler::features_args(unit)); args.extend(compiler::check_cfg_args(unit)); let script_metas = self.find_build_script_metadatas(unit); if let Some(meta_vec) = script_metas.clone() { for meta in meta_vec { if let Some(output) = self.build_script_outputs.lock().unwrap().get(meta) { for cfg in &output.cfgs { args.push("--cfg".into()); args.push(cfg.into()); } for check_cfg in &output.check_cfgs { args.push("--check-cfg".into()); args.push(check_cfg.into()); } for (lt, arg) in &output.linker_args { if lt.applies_to(&unit.target, unit.mode) { args.push("-C".into()); args.push(format!("link-arg={}", arg).into()); } } } } } args.extend(unit.rustdocflags.iter().map(Into::into)); use super::MessageFormat; let format = match self.bcx.build_config.message_format { MessageFormat::Short => "short", MessageFormat::Human => "human", MessageFormat::Json { .. } => "json", }; args.push("--error-format".into()); args.push(format.into()); self.compilation.to_doc_test.push(compilation::Doctest { unit: unit.clone(), args, unstable_opts, linker: self .compilation .target_linker(unit.kind) .map(|p| p.to_path_buf()), script_metas, env: artifact::get_env(&self, unit, self.unit_deps(unit))?, }); } super::output_depinfo(&mut self, unit)?; } for (script_meta, output) in self.build_script_outputs.lock().unwrap().iter() { self.compilation .extra_env .entry(*script_meta) .or_insert_with(Vec::new) .extend(output.env.iter().cloned()); for dir in output.library_paths.iter() { self.compilation .native_dirs .insert(dir.clone().into_path_buf()); } } Ok(self.compilation) } fn collect_tests_and_executables(&mut self, unit: &Unit) -> CargoResult<()> { for output in self.outputs(unit)?.iter() { if matches!( output.flavor, FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom ) { continue; } let bindst = output.bin_dst(); if unit.mode == CompileMode::Test { self.compilation .tests .push(self.unit_output(unit, &output.path)?); } else if unit.target.is_executable() { self.compilation .binaries .push(self.unit_output(unit, bindst)?); } else if unit.target.is_cdylib() && !self.compilation.cdylibs.iter().any(|uo| uo.unit == *unit) { self.compilation .cdylibs .push(self.unit_output(unit, bindst)?); } } Ok(()) } fn collect_doc_merge_info(&mut self) -> CargoResult<()> { if !self.bcx.gctx.cli_unstable().rustdoc_mergeable_info { return Ok(()); } if !self.bcx.build_config.intent.is_doc() { return Ok(()); } if self.bcx.build_config.intent.wants_doc_json_output() { // rustdoc JSON output doesn't support merge (yet?) return Ok(()); } let mut doc_parts_map: HashMap<_, Vec<_>> = HashMap::new(); let unit_iter = if self.bcx.build_config.intent.wants_deps_docs() { itertools::Either::Left(self.bcx.unit_graph.keys()) } else { itertools::Either::Right(self.bcx.roots.iter()) }; for unit in unit_iter { if !unit.mode.is_doc() { continue; } // Assumption: one `rustdoc` call generates only one cross-crate info JSON. let outputs = self.outputs(unit)?; let Some(doc_parts) = outputs .iter() .find(|o| matches!(o.flavor, FileFlavor::DocParts)) else { continue; }; doc_parts_map .entry(unit.kind) .or_default() .push(doc_parts.path.to_owned()); } self.compilation.rustdoc_fingerprints = Some( doc_parts_map .into_iter() .map(|(kind, doc_parts)| (kind, RustdocFingerprint::new(self, kind, doc_parts))) .collect(), ); Ok(()) } /// Returns the executable for the specified unit (if any). pub fn get_executable(&mut self, unit: &Unit) -> CargoResult> { let is_binary = unit.target.is_executable(); let is_test = unit.mode.is_any_test(); if !unit.mode.generates_executable() || !(is_binary || is_test) { return Ok(None); } Ok(self .outputs(unit)? .iter() .find(|o| o.flavor == FileFlavor::Normal) .map(|output| output.bin_dst().clone())) } #[tracing::instrument(skip_all)] pub fn prepare_units(&mut self) -> CargoResult<()> { let dest = self.bcx.profiles.get_dir_name(); // We try to only lock the artifact-dir if we need to. // For example, `cargo check` does not write any files to the artifact-dir so we don't need // to lock it. let must_take_artifact_dir_lock = match self.bcx.build_config.intent { UserIntent::Check { .. } => { // Generally cargo check does not need to take the artifact-dir lock but there is // one exception: If check has `--timings` we still need to lock artifact-dir since // we will output the report files. self.bcx.build_config.timing_report } UserIntent::Build | UserIntent::Test | UserIntent::Doc { .. } | UserIntent::Doctest | UserIntent::Bench => true, }; let host_layout = Layout::new(self.bcx.ws, None, &dest, must_take_artifact_dir_lock, false)?; let mut targets = HashMap::new(); for kind in self.bcx.all_kinds.iter() { if let CompileKind::Target(target) = *kind { let layout = Layout::new( self.bcx.ws, Some(target), &dest, must_take_artifact_dir_lock, false, )?; targets.insert(target, layout); } } self.primary_packages .extend(self.bcx.roots.iter().map(|u| u.pkg.package_id())); self.compilation .root_crate_names .extend(self.bcx.roots.iter().map(|u| u.target.crate_name())); self.record_units_requiring_metadata(); let files = CompilationFiles::new(self, host_layout, targets); self.files = Some(files); Ok(()) } /// Prepare this context, ensuring that all filesystem directories are in /// place. #[tracing::instrument(skip_all)] pub fn prepare(&mut self) -> CargoResult<()> { self.files .as_mut() .unwrap() .host .prepare() .context("couldn't prepare build directories")?; for target in self.files.as_mut().unwrap().target.values_mut() { target .prepare() .context("couldn't prepare build directories")?; } let files = self.files.as_ref().unwrap(); for &kind in self.bcx.all_kinds.iter() { let layout = files.layout(kind); if let Some(artifact_dir) = layout.artifact_dir() { self.compilation .root_output .insert(kind, artifact_dir.dest().to_path_buf()); } if self.bcx.gctx.cli_unstable().build_dir_new_layout { for (unit, _) in self.bcx.unit_graph.iter() { let dep_dir = self.files().deps_dir(unit); paths::create_dir_all(&dep_dir)?; self.compilation.deps_output.insert(kind, dep_dir); } } else { self.compilation .deps_output .insert(kind, layout.build_dir().legacy_deps().to_path_buf()); } } Ok(()) } pub fn files(&self) -> &CompilationFiles<'a, 'gctx> { self.files.as_ref().unwrap() } /// Returns the filenames that the given unit will generate. pub fn outputs(&self, unit: &Unit) -> CargoResult>> { self.files.as_ref().unwrap().outputs(unit, self.bcx) } /// Direct dependencies for the given unit. pub fn unit_deps(&self, unit: &Unit) -> &[UnitDep] { &self.bcx.unit_graph[unit] } /// Returns the `RunCustomBuild` Units associated with the given Unit. /// /// If the package does not have a build script, this returns None. pub fn find_build_script_units(&self, unit: &Unit) -> Option> { if unit.mode.is_run_custom_build() { return Some(vec![unit.clone()]); } let build_script_units: Vec = self.bcx.unit_graph[unit] .iter() .filter(|unit_dep| { unit_dep.unit.mode.is_run_custom_build() && unit_dep.unit.pkg.package_id() == unit.pkg.package_id() }) .map(|unit_dep| unit_dep.unit.clone()) .collect(); if build_script_units.is_empty() { None } else { Some(build_script_units) } } /// Returns the metadata hash for the `RunCustomBuild` Unit associated with /// the given unit. /// /// If the package does not have a build script, this returns None. pub fn find_build_script_metadatas(&self, unit: &Unit) -> Option> { self.find_build_script_units(unit).map(|units| { units .iter() .map(|u| self.get_run_build_script_metadata(u)) .collect() }) } /// Returns the metadata hash for a `RunCustomBuild` unit. pub fn get_run_build_script_metadata(&self, unit: &Unit) -> UnitHash { assert!(unit.mode.is_run_custom_build()); self.files().metadata(unit).unit_id() } /// Returns the list of SBOM output file paths for a given [`Unit`]. pub fn sbom_output_files(&self, unit: &Unit) -> CargoResult> { Ok(self .outputs(unit)? .iter() .filter(|o| o.flavor == FileFlavor::Sbom) .map(|o| o.path.clone()) .collect()) } pub fn is_primary_package(&self, unit: &Unit) -> bool { self.primary_packages.contains(&unit.pkg.package_id()) } /// Returns a [`UnitOutput`] which represents some information about the /// output of a unit. pub fn unit_output(&self, unit: &Unit, path: &Path) -> CargoResult { let script_metas = self.find_build_script_metadatas(unit); let env = artifact::get_env(&self, unit, self.unit_deps(unit))?; Ok(UnitOutput { unit: unit.clone(), path: path.to_path_buf(), script_metas, env, }) } /// Check if any output file name collision happens. /// See for more. #[tracing::instrument(skip_all)] fn check_collisions(&self) -> CargoResult<()> { let mut output_collisions = HashMap::new(); let describe_collision = |unit: &Unit, other_unit: &Unit| -> String { format!( "the {} target `{}` in package `{}` has the same output filename as the {} target `{}` in package `{}`", unit.target.kind().description(), unit.target.name(), unit.pkg.package_id(), other_unit.target.kind().description(), other_unit.target.name(), other_unit.pkg.package_id(), ) }; let suggestion = [ Level::NOTE.message("this may become a hard error in the future; see "), Level::HELP.message("consider changing their names to be unique or compiling them separately") ]; let rustdoc_suggestion = [ Level::NOTE.message("this is a known bug where multiple crates with the same name use the same path; see ") ]; let report_collision = |unit: &Unit, other_unit: &Unit, path: &PathBuf, messages: &[Message<'_>]| -> CargoResult<()> { if unit.target.name() == other_unit.target.name() { self.bcx.gctx.shell().print_report( &[Level::WARNING .secondary_title(format!("output filename collision at {}", path.display())) .elements( [Level::NOTE.message(describe_collision(unit, other_unit))] .into_iter() .chain(messages.iter().cloned()), )], false, ) } else { self.bcx.gctx.shell().print_report( &[Level::WARNING .secondary_title(format!("output filename collision at {}", path.display())) .elements([ Level::NOTE.message(describe_collision(unit, other_unit)), Level::NOTE.message("if this looks unexpected, it may be a bug in Cargo. Please file a bug \ report at https://github.com/rust-lang/cargo/issues/ with as much information as you \ can provide."), Level::NOTE.message(format!("cargo {} running on `{}` target `{}`", crate::version(), self.bcx.host_triple(), self.bcx.target_data.short_name(&unit.kind))), Level::NOTE.message(format!("first unit: {unit:?}")), Level::NOTE.message(format!("second unit: {other_unit:?}")), ])], false, ) } }; fn doc_collision_error(unit: &Unit, other_unit: &Unit) -> CargoResult<()> { bail!( "document output filename collision\n\ The {} `{}` in package `{}` has the same name as the {} `{}` in package `{}`.\n\ Only one may be documented at once since they output to the same path.\n\ Consider documenting only one, renaming one, \ or marking one with `doc = false` in Cargo.toml.", unit.target.kind().description(), unit.target.name(), unit.pkg, other_unit.target.kind().description(), other_unit.target.name(), other_unit.pkg, ); } let mut keys = self .bcx .unit_graph .keys() .filter(|unit| !unit.mode.is_run_custom_build()) .collect::>(); // Sort for consistent error messages. keys.sort_unstable(); // These are kept separate to retain compatibility with older // versions, which generated an error when there was a duplicate lib // or bin (but the old code did not check bin<->lib collisions). To // retain backwards compatibility, this only generates an error for // duplicate libs or duplicate bins (but not both). Ideally this // shouldn't be here, but since there isn't a complete workaround, // yet, this retains the old behavior. let mut doc_libs = HashMap::new(); let mut doc_bins = HashMap::new(); for unit in keys { if unit.mode.is_doc() && self.is_primary_package(unit) { // These situations have been an error since before 1.0, so it // is not a warning like the other situations. if unit.target.is_lib() { if let Some(prev) = doc_libs.insert((unit.target.crate_name(), unit.kind), unit) { doc_collision_error(unit, prev)?; } } else if let Some(prev) = doc_bins.insert((unit.target.crate_name(), unit.kind), unit) { doc_collision_error(unit, prev)?; } } for output in self.outputs(unit)?.iter() { if let Some(other_unit) = output_collisions.insert(output.path.clone(), unit) { if unit.mode.is_doc() { // See https://github.com/rust-lang/rust/issues/56169 // and https://github.com/rust-lang/rust/issues/61378 report_collision(unit, other_unit, &output.path, &rustdoc_suggestion)?; } else { report_collision(unit, other_unit, &output.path, &suggestion)?; } } if let Some(hardlink) = output.hardlink.as_ref() { if let Some(other_unit) = output_collisions.insert(hardlink.clone(), unit) { report_collision(unit, other_unit, hardlink, &suggestion)?; } } if let Some(ref export_path) = output.export_path { if let Some(other_unit) = output_collisions.insert(export_path.clone(), unit) { self.bcx.gctx.shell().print_report( &[Level::WARNING .secondary_title(format!( "`--artifact-dir` filename collision at {}", export_path.display() )) .elements( [Level::NOTE.message(describe_collision(unit, other_unit))] .into_iter() .chain(suggestion.iter().cloned()), )], false, )?; } } } } Ok(()) } /// Records the list of units which are required to emit metadata. /// /// Units which depend only on the metadata of others requires the others to /// actually produce metadata, so we'll record that here. fn record_units_requiring_metadata(&mut self) { for (key, deps) in self.bcx.unit_graph.iter() { for dep in deps { if self.only_requires_rmeta(key, &dep.unit) { self.rmeta_required.insert(dep.unit.clone()); } } } } /// Returns whether when `parent` depends on `dep` if it only requires the /// metadata file from `dep`. pub fn only_requires_rmeta(&self, parent: &Unit, dep: &Unit) -> bool { // We're only a candidate for requiring an `rmeta` file if we // ourselves are building an rlib, !parent.requires_upstream_objects() && parent.mode == CompileMode::Build // Our dependency must also be built as an rlib, otherwise the // object code must be useful in some fashion && !dep.requires_upstream_objects() && dep.mode == CompileMode::Build } /// Returns whether when `unit` is built whether it should emit metadata as /// well because some compilations rely on that. pub fn rmeta_required(&self, unit: &Unit) -> bool { self.rmeta_required.contains(unit) } /// Finds metadata for Doc/Docscrape units. /// /// rustdoc needs a -Cmetadata flag in order to recognize StableCrateIds that refer to /// items in the crate being documented. The -Cmetadata flag used by reverse-dependencies /// will be the metadata of the Cargo unit that generated the current library's rmeta file, /// which should be a Check unit. /// /// If the current crate has reverse-dependencies, such a Check unit should exist, and so /// we use that crate's metadata. If not, we use the crate's Doc unit so at least examples /// scraped from the current crate can be used when documenting the current crate. #[tracing::instrument(skip_all)] pub fn compute_metadata_for_doc_units(&mut self) { for unit in self.bcx.unit_graph.keys() { if !unit.mode.is_doc() && !unit.mode.is_doc_scrape() { continue; } let matching_units = self .bcx .unit_graph .keys() .filter(|other| { unit.pkg == other.pkg && unit.target == other.target && !other.mode.is_doc_scrape() }) .collect::>(); let metadata_unit = matching_units .iter() .find(|other| other.mode.is_check()) .or_else(|| matching_units.iter().find(|other| other.mode.is_doc())) .unwrap_or(&unit); self.metadata_for_doc_units .insert(unit.clone(), self.files().metadata(metadata_unit)); } } } ================================================ FILE: src/cargo/core/compiler/compilation.rs ================================================ //! Type definitions for the result of a compilation. use std::collections::{BTreeSet, HashMap}; use std::ffi::{OsStr, OsString}; use std::path::Path; use std::path::PathBuf; use cargo_platform::CfgExpr; use cargo_util::{ProcessBuilder, paths}; use crate::core::Package; use crate::core::compiler::BuildContext; use crate::core::compiler::CompileTarget; use crate::core::compiler::RustdocFingerprint; use crate::core::compiler::apply_env_config; use crate::core::compiler::{CompileKind, Unit, UnitHash}; use crate::util::{CargoResult, GlobalContext}; /// Represents the kind of process we are creating. #[derive(Debug)] enum ToolKind { /// See [`Compilation::rustc_process`]. Rustc, /// See [`Compilation::rustdoc_process`]. Rustdoc, /// See [`Compilation::host_process`]. HostProcess, /// See [`Compilation::target_process`]. TargetProcess, } impl ToolKind { fn is_rustc_tool(&self) -> bool { matches!(self, ToolKind::Rustc | ToolKind::Rustdoc) } } /// Structure with enough information to run `rustdoc --test`. pub struct Doctest { /// What's being doctested pub unit: Unit, /// Arguments needed to pass to rustdoc to run this test. pub args: Vec, /// Whether or not -Zunstable-options is needed. pub unstable_opts: bool, /// The -Clinker value to use. pub linker: Option, /// The script metadata, if this unit's package has a build script. /// /// This is used for indexing [`Compilation::extra_env`]. pub script_metas: Option>, /// Environment variables to set in the rustdoc process. pub env: HashMap, } /// Information about the output of a unit. pub struct UnitOutput { /// The unit that generated this output. pub unit: Unit, /// Path to the unit's primary output (an executable or cdylib). pub path: PathBuf, /// The script metadata, if this unit's package has a build script. /// /// This is used for indexing [`Compilation::extra_env`]. pub script_metas: Option>, /// Environment variables to set in the unit's process. pub env: HashMap, } /// A structure returning the result of a compilation. pub struct Compilation<'gctx> { /// An array of all tests created during this compilation. pub tests: Vec, /// An array of all binaries created. pub binaries: Vec, /// An array of all cdylibs created. pub cdylibs: Vec, /// The crate names of the root units specified on the command-line. pub root_crate_names: Vec, /// All directories for the output of native build commands. /// /// This is currently used to drive some entries which are added to the /// `LD_LIBRARY_PATH` as appropriate. /// /// The order should be deterministic. pub native_dirs: BTreeSet, /// Root output directory (for the local package's artifacts) pub root_output: HashMap, /// Output directory for rust dependencies. /// May be for the host or for a specific target. pub deps_output: HashMap, /// The path to libstd for each target sysroot_target_libdir: HashMap, /// Extra environment variables that were passed to compilations and should /// be passed to future invocations of programs. /// /// The key is the build script metadata for uniquely identifying the /// `RunCustomBuild` unit that generated these env vars. pub extra_env: HashMap>, /// Libraries to test with rustdoc. pub to_doc_test: Vec, /// Rustdoc fingerprint files to determine whether we need to run `rustdoc --merge=finalize`. /// /// See `-Zrustdoc-mergeable-info` for more. pub rustdoc_fingerprints: Option>, /// The target host triple. pub host: String, gctx: &'gctx GlobalContext, /// Rustc process to be used by default rustc_process: ProcessBuilder, /// Rustc process to be used for workspace crates instead of `rustc_process` rustc_workspace_wrapper_process: ProcessBuilder, /// Optional rustc process to be used for primary crates instead of either `rustc_process` or /// `rustc_workspace_wrapper_process` primary_rustc_process: Option, /// The runner to use for each host or target process. runners: HashMap)>>, /// The linker to use for each host or target. linkers: HashMap>, /// The total number of lint warnings emitted by the compilation. pub lint_warning_count: usize, } impl<'gctx> Compilation<'gctx> { pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult> { let rustc_process = bcx.rustc().process(); let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone(); let rustc_workspace_wrapper_process = bcx.rustc().workspace_process(); let host = bcx.host_triple().to_string(); // When `target-applies-to-host=false`, and without `--target`, // there will be only `CompileKind::Host` in requested_kinds. // Need to insert target config explicitly for target-applies-to-host=false // to find the correct configs. let insert_explicit_host_runner = !bcx.gctx.target_applies_to_host()? && bcx .build_config .requested_kinds .iter() .any(CompileKind::is_host); let mut runners = bcx .build_config .requested_kinds .iter() .chain(Some(&CompileKind::Host)) .map(|kind| Ok((*kind, target_runner(bcx, *kind)?))) .collect::>>()?; if insert_explicit_host_runner { let kind = explicit_host_kind(&host); runners.insert(kind, target_runner(bcx, kind)?); } let mut linkers = bcx .build_config .requested_kinds .iter() .chain(Some(&CompileKind::Host)) .map(|kind| Ok((*kind, target_linker(bcx, *kind)?))) .collect::>>()?; if insert_explicit_host_runner { let kind = explicit_host_kind(&host); linkers.insert(kind, target_linker(bcx, kind)?); } Ok(Compilation { native_dirs: BTreeSet::new(), root_output: HashMap::new(), deps_output: HashMap::new(), sysroot_target_libdir: get_sysroot_target_libdir(bcx)?, tests: Vec::new(), binaries: Vec::new(), cdylibs: Vec::new(), root_crate_names: Vec::new(), extra_env: HashMap::new(), to_doc_test: Vec::new(), rustdoc_fingerprints: None, gctx: bcx.gctx, host, rustc_process, rustc_workspace_wrapper_process, primary_rustc_process, runners, linkers, lint_warning_count: 0, }) } /// Returns a [`ProcessBuilder`] for running `rustc`. /// /// `is_primary` is true if this is a "primary package", which means it /// was selected by the user on the command-line (such as with a `-p` /// flag), see [`crate::core::compiler::BuildRunner::primary_packages`]. /// /// `is_workspace` is true if this is a workspace member. pub fn rustc_process( &self, unit: &Unit, is_primary: bool, is_workspace: bool, ) -> CargoResult { let mut rustc = if is_primary && self.primary_rustc_process.is_some() { self.primary_rustc_process.clone().unwrap() } else if is_workspace { self.rustc_workspace_wrapper_process.clone() } else { self.rustc_process.clone() }; if self.gctx.extra_verbose() { rustc.display_env_vars(); } let cmd = fill_rustc_tool_env(rustc, unit); self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc) } /// Returns a [`ProcessBuilder`] for running `rustdoc`. pub fn rustdoc_process( &self, unit: &Unit, script_metas: Option<&Vec>, ) -> CargoResult { let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?); if self.gctx.extra_verbose() { rustdoc.display_env_vars(); } let cmd = fill_rustc_tool_env(rustdoc, unit); let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?; cmd.retry_with_argfile(true); unit.target.edition().cmd_edition_arg(&mut cmd); for crate_type in unit.target.rustc_crate_types() { cmd.arg("--crate-type").arg(crate_type.as_str()); } Ok(cmd) } /// Returns a [`ProcessBuilder`] appropriate for running a process for the /// host platform. /// /// This is currently only used for running build scripts. If you use this /// for anything else, please be extra careful on how environment /// variables are set! pub fn host_process>( &self, cmd: T, pkg: &Package, ) -> CargoResult { // Only use host runner when -Zhost-config is enabled // to ensure `target..runner` does not wrap build scripts. let builder = if !self.gctx.target_applies_to_host()? && let Some((runner, args)) = self .runners .get(&CompileKind::Host) .and_then(|x| x.as_ref()) { let mut builder = ProcessBuilder::new(runner); builder.args(args); builder.arg(cmd); builder } else { ProcessBuilder::new(cmd) }; self.fill_env(builder, pkg, None, CompileKind::Host, ToolKind::HostProcess) } pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec)> { let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true); let kind = if !target_applies_to_host && kind.is_host() { // Use explicit host target triple when `target-applies-to-host=false` // This ensures `host.runner` won't be accidentally applied to `cargo run` / `cargo test`. explicit_host_kind(&self.host) } else { kind }; self.runners.get(&kind).and_then(|x| x.as_ref()) } /// Gets the `[host.linker]` for host build target (build scripts and proc macros). pub fn host_linker(&self) -> Option<&Path> { self.linkers .get(&CompileKind::Host) .and_then(|x| x.as_ref()) .map(|x| x.as_path()) } /// Gets the user-specified linker for a particular host or target. pub fn target_linker(&self, kind: CompileKind) -> Option<&Path> { let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true); let kind = if !target_applies_to_host && kind.is_host() { // Use explicit host target triple when `target-applies-to-host=false` // This ensures `host.linker` won't be accidentally applied to normal builds explicit_host_kind(&self.host) } else { kind }; self.linkers .get(&kind) .and_then(|x| x.as_ref()) .map(|x| x.as_path()) } /// Returns a [`ProcessBuilder`] appropriate for running a process for the /// target platform. This is typically used for `cargo run` and `cargo /// test`. /// /// `script_metas` is the metadata for the `RunCustomBuild` unit that this /// unit used for its build script. Use `None` if the package did not have /// a build script. pub fn target_process>( &self, cmd: T, kind: CompileKind, pkg: &Package, script_metas: Option<&Vec>, ) -> CargoResult { let builder = if let Some((runner, args)) = self.target_runner(kind) { let mut builder = ProcessBuilder::new(runner); builder.args(args); builder.arg(cmd); builder } else { ProcessBuilder::new(cmd) }; let tool_kind = ToolKind::TargetProcess; let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?; if let Some(client) = self.gctx.jobserver_from_env() { builder.inherit_jobserver(client); } Ok(builder) } /// Prepares a new process with an appropriate environment to run against /// the artifacts produced by the build process. /// /// The package argument is also used to configure environment variables as /// well as the working directory of the child process. fn fill_env( &self, mut cmd: ProcessBuilder, pkg: &Package, script_metas: Option<&Vec>, kind: CompileKind, tool_kind: ToolKind, ) -> CargoResult { let mut search_path = Vec::new(); if tool_kind.is_rustc_tool() { if matches!(tool_kind, ToolKind::Rustdoc) { // HACK: `rustdoc --test` not only compiles but executes doctests. // Ideally only execution phase should have search paths appended, // so the executions can find native libs just like other tests. // However, there is no way to separate these two phase, so this // hack is added for both phases. // TODO: handle doctest-xcompile search_path.extend(super::filter_dynamic_search_path( self.native_dirs.iter(), &self.root_output[&CompileKind::Host], )); } search_path.push(self.deps_output[&CompileKind::Host].clone()); } else { if let Some(path) = self.root_output.get(&kind) { search_path.extend(super::filter_dynamic_search_path( self.native_dirs.iter(), path, )); search_path.push(path.clone()); } search_path.push(self.deps_output[&kind].clone()); // For build-std, we don't want to accidentally pull in any shared // libs from the sysroot that ships with rustc. This may not be // required (at least I cannot craft a situation where it // matters), but is here to be safe. if self.gctx.cli_unstable().build_std.is_none() || // Proc macros dynamically link to std, so set it anyway. pkg.proc_macro() { search_path.push(self.sysroot_target_libdir[&kind].clone()); } } let dylib_path = paths::dylib_path(); let dylib_path_is_empty = dylib_path.is_empty(); if dylib_path.starts_with(&search_path) { search_path = dylib_path; } else { search_path.extend(dylib_path.into_iter()); } if cfg!(target_os = "macos") && dylib_path_is_empty { // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't // set or set to an empty string. Since Cargo is explicitly setting // the value, make sure the defaults still work. if let Some(home) = self.gctx.get_env_os("HOME") { search_path.push(PathBuf::from(home).join("lib")); } search_path.push(PathBuf::from("/usr/local/lib")); search_path.push(PathBuf::from("/usr/lib")); } let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?; cmd.env(paths::dylib_path_envvar(), &search_path); if let Some(meta_vec) = script_metas { for meta in meta_vec { if let Some(env) = self.extra_env.get(meta) { for (k, v) in env { cmd.env(k, v); } } } } let cargo_exe = self.gctx.cargo_exe()?; cmd.env(crate::CARGO_ENV, cargo_exe); // When adding new environment variables depending on // crate properties which might require rebuild upon change // consider adding the corresponding properties to the hash // in BuildContext::target_metadata() cmd.env("CARGO_MANIFEST_DIR", pkg.root()) .env("CARGO_MANIFEST_PATH", pkg.manifest_path()) .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string()) .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string()) .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string()) .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str()) .env("CARGO_PKG_VERSION", &pkg.version().to_string()) .env("CARGO_PKG_NAME", &*pkg.name()); for (key, value) in pkg.manifest().metadata().env_vars() { cmd.env(key, value.as_ref()); } cmd.cwd(pkg.root()); apply_env_config(self.gctx, &mut cmd)?; Ok(cmd) } } /// Prepares a `rustc_tool` process with additional environment variables /// that are only relevant in a context that has a unit fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder { if unit.target.is_executable() { let name = unit .target .binary_filename() .unwrap_or(unit.target.name().to_string()); cmd.env("CARGO_BIN_NAME", name); } cmd.env("CARGO_CRATE_NAME", unit.target.crate_name()); cmd } fn get_sysroot_target_libdir( bcx: &BuildContext<'_, '_>, ) -> CargoResult> { bcx.all_kinds .iter() .map(|&kind| { let Some(info) = bcx.target_data.get_info(kind) else { let target = match kind { CompileKind::Host => "host".to_owned(), CompileKind::Target(s) => s.short_name().to_owned(), }; let dependency = bcx .unit_graph .iter() .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id())) .unwrap(); anyhow::bail!( "could not find specification for target `{target}`.\n \ Dependency `{dependency}` requires to build for target `{target}`." ) }; Ok((kind, info.sysroot_target_libdir.clone())) }) .collect() } fn target_runner( bcx: &BuildContext<'_, '_>, kind: CompileKind, ) -> CargoResult)>> { if let Some(runner) = bcx.target_data.target_config(kind).runner.as_ref() { let path = runner.val.path.clone().resolve_program(bcx.gctx); return Ok(Some((path, runner.val.args.clone()))); } // try target.'cfg(...)'.runner let target_cfg = bcx.target_data.info(kind).cfg(); let mut cfgs = bcx .gctx .target_cfgs()? .iter() .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner))) .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg)); let matching_runner = cfgs.next(); if let Some((key, runner)) = cfgs.next() { anyhow::bail!( "several matching instances of `target.'cfg(..)'.runner` in configurations\n\ first match `{}` located in {}\n\ second match `{}` located in {}", matching_runner.unwrap().0, matching_runner.unwrap().1.definition, key, runner.definition ); } Ok(matching_runner.map(|(_k, runner)| { ( runner.val.path.clone().resolve_program(bcx.gctx), runner.val.args.clone(), ) })) } /// Gets the user-specified linker for a particular host or target from the configuration. fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult> { // Try host.linker and target.{}.linker. if let Some(path) = bcx .target_data .target_config(kind) .linker .as_ref() .map(|l| l.val.clone().resolve_program(bcx.gctx)) { return Ok(Some(path)); } // Try target.'cfg(...)'.linker. let target_cfg = bcx.target_data.info(kind).cfg(); let mut cfgs = bcx .gctx .target_cfgs()? .iter() .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker))) .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg)); let matching_linker = cfgs.next(); if let Some((key, linker)) = cfgs.next() { anyhow::bail!( "several matching instances of `target.'cfg(..)'.linker` in configurations\n\ first match `{}` located in {}\n\ second match `{}` located in {}", matching_linker.unwrap().0, matching_linker.unwrap().1.definition, key, linker.definition ); } Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx))) } fn explicit_host_kind(host: &str) -> CompileKind { let target = CompileTarget::new(host, false).expect("must be a host tuple"); CompileKind::Target(target) } ================================================ FILE: src/cargo/core/compiler/compile_kind.rs ================================================ //! Type definitions for cross-compilation. use crate::core::Target; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::{GlobalContext, StableHasher, try_canonicalize}; use anyhow::Context as _; use anyhow::bail; use cargo_util::ProcessBuilder; use serde::Serialize; use std::collections::BTreeSet; use std::fs; use std::hash::{Hash, Hasher}; use std::path::Path; /// Indicator for how a unit is being compiled. /// /// This is used primarily for organizing cross compilations vs host /// compilations, where cross compilations happen at the request of `--target` /// and host compilations happen for things like build scripts and procedural /// macros. #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)] pub enum CompileKind { /// Attached to a unit that is compiled for the "host" system or otherwise /// is compiled without a `--target` flag. This is used for procedural /// macros and build scripts, or if the `--target` flag isn't passed. Host, /// Attached to a unit to be compiled for a particular target. This is used /// for units when the `--target` flag is passed. Target(CompileTarget), } /// Fallback behavior in the /// [`CompileKind::from_requested_targets_with_fallback`] function when /// no targets are specified. pub enum CompileKindFallback { /// The build configuration is consulted to find the default target, such as /// `$CARGO_BUILD_TARGET` or reading `build.target`. BuildConfig, /// Only the host should be returned when targets aren't explicitly /// specified. This is used by `cargo metadata` for example where "only /// host" has a special meaning in terms of the returned metadata. JustHost, } impl CompileKind { pub fn is_host(&self) -> bool { matches!(self, CompileKind::Host) } pub fn for_target(self, target: &Target) -> CompileKind { // Once we start compiling for the `Host` kind we continue doing so, but // if we are a `Target` kind and then we start compiling for a target // that needs to be on the host we lift ourselves up to `Host`. match self { CompileKind::Host => CompileKind::Host, CompileKind::Target(_) if target.for_host() => CompileKind::Host, CompileKind::Target(n) => CompileKind::Target(n), } } /// Creates a new list of `CompileKind` based on the requested list of /// targets. /// /// If no targets are given then this returns a single-element vector with /// `CompileKind::Host`. pub fn from_requested_targets( gctx: &GlobalContext, targets: &[String], ) -> CargoResult> { CompileKind::from_requested_targets_with_fallback( gctx, targets, CompileKindFallback::BuildConfig, ) } /// Same as [`CompileKind::from_requested_targets`] except that if `targets` /// doesn't explicitly mention anything the behavior of what to return is /// controlled by the `fallback` argument. pub fn from_requested_targets_with_fallback( gctx: &GlobalContext, targets: &[String], fallback: CompileKindFallback, ) -> CargoResult> { let dedup = |targets: &[String]| { let deduplicated_targets = targets .iter() .map(|value| { // This neatly substitutes the manually-specified `host-tuple` target directive // with the compiling machine's target triple. if value.as_str() == "host-tuple" { let host_triple = env!("RUST_HOST_TARGET"); Ok(CompileKind::Target(CompileTarget::new( host_triple, gctx.cli_unstable().json_target_spec, )?)) } else { Ok(CompileKind::Target(CompileTarget::new( value.as_str(), gctx.cli_unstable().json_target_spec, )?)) } }) // First collect into a set to deduplicate any `--target` passed // more than once... .collect::>>()? // ... then generate a flat list for everything else to use. .into_iter() .collect(); Ok(deduplicated_targets) }; if !targets.is_empty() { return dedup(targets); } let kinds = match (fallback, &gctx.build_config()?.target) { (_, None) | (CompileKindFallback::JustHost, _) => Ok(vec![CompileKind::Host]), (CompileKindFallback::BuildConfig, Some(build_target_config)) => { dedup(&build_target_config.values(gctx.cwd())?) } }; kinds } /// Hash used for fingerprinting. /// /// Metadata hashing uses the normal Hash trait, which does not /// differentiate on `.json` file contents. The fingerprint hash does /// check the contents. pub fn fingerprint_hash(&self) -> u64 { match self { CompileKind::Host => 0, CompileKind::Target(target) => target.fingerprint_hash(), } } /// Adds the `--target` flag to the given [`ProcessBuilder`] if this is a /// non-host build. pub fn add_target_arg(&self, builder: &mut ProcessBuilder) { if let CompileKind::Target(target) = self { builder.arg("--target").arg(target.rustc_target()); if matches!(target, CompileTarget::Json { .. }) { builder.arg("-Zunstable-options"); } } } } impl serde::ser::Serialize for CompileKind { fn serialize(&self, s: S) -> Result where S: serde::ser::Serializer, { match self { CompileKind::Host => None::<&str>.serialize(s), CompileKind::Target(t) => Some(t.rustc_target()).serialize(s), } } } /// Abstraction for the representation of a compilation target that Cargo has. /// /// Compilation targets are one of two things right now: /// /// 1. A raw target string, like `x86_64-unknown-linux-gnu`. /// 2. The path to a JSON file, such as `/path/to/my-target.json`. /// /// Raw target strings are typically dictated by `rustc` itself and represent /// built-in targets. Custom JSON files are somewhat unstable, but supported /// here in Cargo. Note that for JSON target files this `CompileTarget` stores a /// full canonicalized path to the target. /// /// The main reason for this existence is to handle JSON target files where when /// we call rustc we pass full paths but when we use it for Cargo's purposes /// like naming directories or looking up configuration keys we only check the /// file stem of JSON target files. For built-in rustc targets this is just an /// uninterpreted string basically. #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord, Serialize)] pub enum CompileTarget { Tuple(InternedString), Json { short: InternedString, path: InternedString, }, } impl CompileTarget { pub fn new(name: &str, unstable_json: bool) -> CargoResult { let name = name.trim(); if name.is_empty() { bail!("target was empty"); } if !name.ends_with(".json") { return Ok(CompileTarget::Tuple(name.into())); } if !unstable_json { bail!("`.json` target specs require -Zjson-target-spec"); } // If `name` ends in `.json` then it's likely a custom target // specification. Canonicalize the path to ensure that different builds // with different paths always produce the same result. let p = try_canonicalize(Path::new(name)) .with_context(|| format!("target path `{name}` is not a valid file"))?; let path = p .to_str() .ok_or_else(|| anyhow::format_err!("target path `{name}` is not valid unicode"))? .into(); let short = p.file_stem().unwrap().to_str().unwrap().into(); Ok(CompileTarget::Json { short, path }) } /// Returns the full unqualified name of this target, suitable for passing /// to `rustc` directly. /// /// Typically this is pretty much the same as `short_name`, but for the case /// of JSON target files this will be a full canonicalized path name for the /// current filesystem. pub fn rustc_target(&self) -> InternedString { match self { CompileTarget::Tuple(name) => *name, CompileTarget::Json { path, .. } => *path, } } /// Returns a "short" version of the target name suitable for usage within /// Cargo for configuration and such. /// /// This is typically the same as `rustc_target`, or the full name, but for /// JSON target files this returns just the file stem (e.g. `foo` out of /// `foo.json`) instead of the full path. pub fn short_name(&self) -> &str { match self { CompileTarget::Tuple(name) => name, CompileTarget::Json { short, .. } => short, } } /// See [`CompileKind::fingerprint_hash`]. pub fn fingerprint_hash(&self) -> u64 { let mut hasher = StableHasher::new(); match self { CompileTarget::Tuple(name) => name.hash(&mut hasher), CompileTarget::Json { path, .. } => { // This may have some performance concerns, since it is called // fairly often. If that ever seems worth fixing, consider // embedding this in `CompileTarget`. match fs::read_to_string(path) { Ok(contents) => contents.hash(&mut hasher), Err(_) => path.hash(&mut hasher), } } } Hasher::finish(&hasher) } } ================================================ FILE: src/cargo/core/compiler/crate_type.rs ================================================ use std::fmt; /// Types of the output artifact that the compiler emits. /// Usually distributable or linkable either statically or dynamically. /// /// See . #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum CrateType { Bin, Lib, Rlib, Dylib, Cdylib, Staticlib, ProcMacro, Other(String), } impl CrateType { pub fn as_str(&self) -> &str { match self { CrateType::Bin => "bin", CrateType::Lib => "lib", CrateType::Rlib => "rlib", CrateType::Dylib => "dylib", CrateType::Cdylib => "cdylib", CrateType::Staticlib => "staticlib", CrateType::ProcMacro => "proc-macro", CrateType::Other(s) => s, } } pub fn can_lto(&self) -> bool { match self { CrateType::Bin | CrateType::Staticlib | CrateType::Cdylib => true, CrateType::Lib | CrateType::Rlib | CrateType::Dylib | CrateType::ProcMacro | CrateType::Other(..) => false, } } pub fn is_linkable(&self) -> bool { match self { CrateType::Lib | CrateType::Rlib | CrateType::Dylib | CrateType::ProcMacro => true, CrateType::Bin | CrateType::Cdylib | CrateType::Staticlib | CrateType::Other(..) => { false } } } pub fn is_dynamic(&self) -> bool { match self { CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro => true, CrateType::Lib | CrateType::Rlib | CrateType::Bin | CrateType::Staticlib | CrateType::Other(..) => false, } } /// Returns whether production of this crate type requires the object files /// from dependencies to be available. /// /// See also [`TargetKind::requires_upstream_objects`]. /// /// [`TargetKind::requires_upstream_objects`]: crate::core::manifest::TargetKind::requires_upstream_objects pub fn requires_upstream_objects(&self) -> bool { // "lib" == "rlib" and is a compilation that doesn't actually // require upstream object files to exist, only upstream metadata // files. As a result, it doesn't require upstream artifacts !matches!(self, CrateType::Lib | CrateType::Rlib) // Everything else, however, is some form of "linkable output" or // something that requires upstream object files. } /// Returns whether production of this crate type could benefit from splitting metadata /// into a .rmeta file. /// /// See also [`TargetKind::benefits_from_no_embed_metadata`]. /// /// [`TargetKind::benefits_from_no_embed_metadata`]: crate::core::manifest::TargetKind::benefits_from_no_embed_metadata pub fn benefits_from_no_embed_metadata(&self) -> bool { match self { // rlib/libs generate .rmeta files for pipelined compilation. // If we also include metadata inside of them, we waste disk space, since the metadata // will be located both in the lib/rlib and the .rmeta file. CrateType::Lib | CrateType::Rlib | // Dylibs do not have to contain metadata when they are used as a runtime dependency. // If we split the metadata into a separate .rmeta file, the dylib file (that // can be shipped as a runtime dependency) can be smaller. CrateType::Dylib => true, // Proc macros contain metadata that specifies what macro functions are available in // it, but the metadata is typically very small. The metadata of proc macros is also // self-contained (unlike rlibs/dylibs), so let's not unnecessarily split it into // multiple files. CrateType::ProcMacro | // cdylib and staticlib produce artifacts that are used through the C ABI and do not // contain Rust-specific metadata. CrateType::Cdylib | CrateType::Staticlib | // Binaries also do not contain metadata CrateType::Bin | CrateType::Other(_) => false } } } impl fmt::Display for CrateType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_str().fmt(f) } } impl<'a> From<&'a String> for CrateType { fn from(s: &'a String) -> Self { match s.as_str() { "bin" => CrateType::Bin, "lib" => CrateType::Lib, "rlib" => CrateType::Rlib, "dylib" => CrateType::Dylib, "cdylib" => CrateType::Cdylib, "staticlib" => CrateType::Staticlib, "procmacro" => CrateType::ProcMacro, _ => CrateType::Other(s.clone()), } } } impl fmt::Debug for CrateType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.to_string().fmt(f) } } impl serde::Serialize for CrateType { fn serialize(&self, s: S) -> Result where S: serde::ser::Serializer, { self.to_string().serialize(s) } } ================================================ FILE: src/cargo/core/compiler/custom_build.rs ================================================ //! How to execute a build script and parse its output. //! //! ## Preparing a build script run //! //! A [build script] is an optional Rust script Cargo will run before building //! your package. As of this writing, two kinds of special [`Unit`]s will be //! constructed when there is a build script in a package. //! //! * Build script compilation --- This unit is generally the same as units //! that would compile other Cargo targets. It will recursively creates units //! of its dependencies. One biggest difference is that the [`Unit`] of //! compiling a build script is flagged as [`TargetKind::CustomBuild`]. //! * Build script execution --- During the construction of the [`UnitGraph`], //! Cargo inserts a [`Unit`] with [`CompileMode::RunCustomBuild`]. This unit //! depends on the unit of compiling the associated build script, to ensure //! the executable is available before running. The [`Work`] of running the //! build script is prepared in the function [`prepare`]. //! //! ## Running a build script //! //! When running a build script, Cargo is aware of the progress and the result //! of a build script. Standard output is the chosen interprocess communication //! between Cargo and build script processes. A set of strings is defined for //! that purpose. These strings, a.k.a. instructions, are interpreted by //! [`BuildOutput::parse`] and stored in [`BuildRunner::build_script_outputs`]. //! The entire execution work is constructed by [`build_work`]. //! //! [build script]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html //! [`TargetKind::CustomBuild`]: crate::core::manifest::TargetKind::CustomBuild //! [`UnitGraph`]: super::unit_graph::UnitGraph //! [`CompileMode::RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild //! [instructions]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script use super::{BuildRunner, Job, Unit, Work, fingerprint, get_dynamic_search_path}; use crate::core::compiler::CompileMode; use crate::core::compiler::artifact; use crate::core::compiler::build_runner::UnitHash; use crate::core::compiler::job_queue::JobState; use crate::core::{PackageId, Target, profiles::ProfileRoot}; use crate::util::errors::CargoResult; use crate::util::internal; use crate::util::machine_message::{self, Message}; use anyhow::{Context as _, bail}; use cargo_platform::Cfg; use cargo_util::paths; use cargo_util_schemas::manifest::RustVersion; use std::collections::hash_map::{Entry, HashMap}; use std::collections::{BTreeSet, HashSet}; use std::path::{Path, PathBuf}; use std::str; use std::sync::{Arc, Mutex}; /// A build script instruction that tells Cargo to display an error after the /// build script has finished running. Read [the doc] for more. /// /// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-error const CARGO_ERROR_SYNTAX: &str = "cargo::error="; /// Deprecated: A build script instruction that tells Cargo to display a warning after the /// build script has finished running. Read [the doc] for more. /// /// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-warning const OLD_CARGO_WARNING_SYNTAX: &str = "cargo:warning="; /// A build script instruction that tells Cargo to display a warning after the /// build script has finished running. Read [the doc] for more. /// /// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-warning const NEW_CARGO_WARNING_SYNTAX: &str = "cargo::warning="; #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Severity { Error, Warning, } pub type LogMessage = (Severity, String); /// Represents a path added to the library search path. /// /// We need to keep track of requests to add search paths within the cargo build directory /// separately from paths outside of Cargo. The reason is that we want to give precedence to linking /// against libraries within the Cargo build directory even if a similar library exists in the /// system (e.g. crate A adds `/usr/lib` to the search path and then a later build of crate B adds /// `target/debug/...` to satisfy its request to link against the library B that it built, but B is /// also found in `/usr/lib`). /// /// There's some nuance here because we want to preserve relative order of paths of the same type. /// For example, if the build process would in declaration order emit the following linker line: /// ```bash /// -L/usr/lib -Ltarget/debug/build/crate1/libs -L/lib -Ltarget/debug/build/crate2/libs) /// ``` /// /// we want the linker to actually receive: /// ```bash /// -Ltarget/debug/build/crate1/libs -Ltarget/debug/build/crate2/libs) -L/usr/lib -L/lib /// ``` /// /// so that the library search paths within the crate artifacts directory come first but retain /// relative ordering while the system library paths come after while still retaining relative /// ordering among them; ordering is the order they are emitted within the build process, /// not lexicographic order. /// /// WARNING: Even though this type implements PartialOrd + Ord, this is a lexicographic ordering. /// The linker line will require an explicit sorting algorithm. PartialOrd + Ord is derived because /// BuildOutput requires it but that ordering is different from the one for the linker search path, /// at least today. It may be worth reconsidering & perhaps it's ok if BuildOutput doesn't have /// a lexicographic ordering for the library_paths? I'm not sure the consequence of that. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum LibraryPath { /// The path is pointing within the output folder of the crate and takes priority over /// external paths when passed to the linker. CargoArtifact(PathBuf), /// The path is pointing outside of the crate's build location. The linker will always /// receive such paths after `CargoArtifact`. External(PathBuf), } impl LibraryPath { fn new(p: PathBuf, script_out_dir: &Path) -> Self { let search_path = get_dynamic_search_path(&p); if search_path.starts_with(script_out_dir) { Self::CargoArtifact(p) } else { Self::External(p) } } pub fn into_path_buf(self) -> PathBuf { match self { LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p, } } } impl AsRef for LibraryPath { fn as_ref(&self) -> &PathBuf { match self { LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p, } } } /// Contains the parsed output of a custom build script. #[derive(Clone, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct BuildOutput { /// Paths to pass to rustc with the `-L` flag. pub library_paths: Vec, /// Names and link kinds of libraries, suitable for the `-l` flag. pub library_links: Vec, /// Linker arguments suitable to be passed to `-C link-arg=` pub linker_args: Vec<(LinkArgTarget, String)>, /// Various `--cfg` flags to pass to the compiler. pub cfgs: Vec, /// Various `--check-cfg` flags to pass to the compiler. pub check_cfgs: Vec, /// Additional environment variables to run the compiler with. pub env: Vec<(String, String)>, /// Metadata to pass to the immediate dependencies. pub metadata: Vec<(String, String)>, /// Paths to trigger a rerun of this build script. /// May be absolute or relative paths (relative to package root). pub rerun_if_changed: Vec, /// Environment variables which, when changed, will cause a rebuild. pub rerun_if_env_changed: Vec, /// Errors and warnings generated by this build. /// /// These are only displayed if this is a "local" package, `-vv` is used, or /// there is a build error for any target in this package. Note that any log /// message of severity `Error` will by itself cause a build error, and will /// cause all log messages to be displayed. pub log_messages: Vec, } /// Map of packages to build script output. /// /// This initially starts out as empty. Overridden build scripts get /// inserted during `build_map`. The rest of the entries are added /// immediately after each build script runs. /// /// The [`UnitHash`] is the unique metadata hash for the `RunCustomBuild` Unit of /// the package. It needs a unique key, since the build script can be run /// multiple times with different profiles or features. We can't embed a /// `Unit` because this structure needs to be shareable between threads. #[derive(Default)] pub struct BuildScriptOutputs { outputs: HashMap, } /// Linking information for a `Unit`. /// /// See [`build_map`] for more details. #[derive(Default)] pub struct BuildScripts { /// List of build script outputs this Unit needs to include for linking. Each /// element is an index into `BuildScriptOutputs`. /// /// Cargo will use this `to_link` vector to add `-L` flags to compiles as we /// propagate them upwards towards the final build. Note, however, that we /// need to preserve the ordering of `to_link` to be topologically sorted. /// This will ensure that build scripts which print their paths properly will /// correctly pick up the files they generated (if there are duplicates /// elsewhere). /// /// To preserve this ordering, the (id, metadata) is stored in two places, once /// in the `Vec` and once in `seen_to_link` for a fast lookup. We maintain /// this as we're building interactively below to ensure that the memory /// usage here doesn't blow up too much. /// /// For more information, see #2354. pub to_link: Vec<(PackageId, UnitHash)>, /// This is only used while constructing `to_link` to avoid duplicates. seen_to_link: HashSet<(PackageId, UnitHash)>, /// Host-only dependencies that have build scripts. Each element is an /// index into `BuildScriptOutputs`. /// /// This is the set of transitive dependencies that are host-only /// (proc-macro, plugin, build-dependency) that contain a build script. /// Any `BuildOutput::library_paths` path relative to `target` will be /// added to `LD_LIBRARY_PATH` so that the compiler can find any dynamic /// libraries a build script may have generated. pub plugins: BTreeSet<(PackageId, UnitHash)>, } /// Dependency information as declared by a build script that might trigger /// a recompile of itself. #[derive(Debug)] pub struct BuildDeps { /// Absolute path to the file in the target directory that stores the /// output of the build script. pub build_script_output: PathBuf, /// Files that trigger a rebuild if they change. pub rerun_if_changed: Vec, /// Environment variables that trigger a rebuild if they change. pub rerun_if_env_changed: Vec, } /// Represents one of the instructions from `cargo::rustc-link-arg-*` build /// script instruction family. /// /// In other words, indicates targets that custom linker arguments applies to. /// /// See the [build script documentation][1] for more. /// /// [1]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargorustc-link-argflag #[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum LinkArgTarget { /// Represents `cargo::rustc-link-arg=FLAG`. All, /// Represents `cargo::rustc-cdylib-link-arg=FLAG`. Cdylib, /// Represents `cargo::rustc-link-arg-bins=FLAG`. Bin, /// Represents `cargo::rustc-link-arg-bin=BIN=FLAG`. SingleBin(String), /// Represents `cargo::rustc-link-arg-tests=FLAG`. Test, /// Represents `cargo::rustc-link-arg-benches=FLAG`. Bench, /// Represents `cargo::rustc-link-arg-examples=FLAG`. Example, } impl LinkArgTarget { /// Checks if this link type applies to a given [`Target`]. pub fn applies_to(&self, target: &Target, mode: CompileMode) -> bool { let is_test = mode.is_any_test(); match self { LinkArgTarget::All => true, LinkArgTarget::Cdylib => !is_test && target.is_cdylib(), LinkArgTarget::Bin => target.is_bin(), LinkArgTarget::SingleBin(name) => target.is_bin() && target.name() == name, LinkArgTarget::Test => target.is_test(), LinkArgTarget::Bench => target.is_bench(), LinkArgTarget::Example => target.is_exe_example(), } } } /// Prepares a `Work` that executes the target as a custom build script. #[tracing::instrument(skip_all)] pub fn prepare(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult { let metadata = build_runner.get_run_build_script_metadata(unit); if build_runner .build_script_outputs .lock() .unwrap() .contains_key(metadata) { // The output is already set, thus the build script is overridden. fingerprint::prepare_target(build_runner, unit, false) } else { build_work(build_runner, unit) } } /// Emits the output of a build script as a [`machine_message::BuildScript`] /// JSON string to standard output. fn emit_build_output( state: &JobState<'_, '_>, output: &BuildOutput, out_dir: &Path, package_id: PackageId, ) -> CargoResult<()> { let library_paths = output .library_paths .iter() .map(|l| l.as_ref().display().to_string()) .collect::>(); let msg = machine_message::BuildScript { package_id: package_id.to_spec(), linked_libs: &output.library_links, linked_paths: &library_paths, cfgs: &output.cfgs, env: &output.env, out_dir, } .to_json_string(); state.stdout(msg)?; Ok(()) } /// Constructs the unit of work of running a build script. /// /// The construction includes: /// /// * Set environment variables for the build script run. /// * Create the output dir (`OUT_DIR`) for the build script output. /// * Determine if the build script needs a re-run. /// * Run the build script and store its output. fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult { assert!(unit.mode.is_run_custom_build()); let bcx = &build_runner.bcx; let dependencies = build_runner.unit_deps(unit); let build_script_unit = dependencies .iter() .find(|d| !d.unit.mode.is_run_custom_build() && d.unit.target.is_custom_build()) .map(|d| &d.unit) .expect("running a script not depending on an actual script"); let script_dir = build_runner.files().build_script_dir(build_script_unit); let script_out_dir = if bcx.gctx.cli_unstable().build_dir_new_layout { build_runner.files().out_dir_new_layout(unit) } else { build_runner.files().build_script_out_dir(unit) }; if let Some(deps) = unit.pkg.manifest().metabuild() { prepare_metabuild(build_runner, build_script_unit, deps)?; } // Building the command to execute let to_exec = script_dir.join(unit.target.name()); // Start preparing the process to execute, starting out with some // environment variables. Note that the profile-related environment // variables are not set with this the build script's profile but rather the // package's library profile. // NOTE: if you add any profile flags, be sure to update // `Profiles::get_profile_run_custom_build` so that those flags get // carried over. let to_exec = to_exec.into_os_string(); let mut cmd = build_runner.compilation.host_process(to_exec, &unit.pkg)?; let debug = unit.profile.debuginfo.is_turned_on(); cmd.env("OUT_DIR", &script_out_dir) .env("CARGO_MANIFEST_DIR", unit.pkg.root()) .env("CARGO_MANIFEST_PATH", unit.pkg.manifest_path()) .env("NUM_JOBS", &bcx.jobs().to_string()) .env("TARGET", bcx.target_data.short_name(&unit.kind)) .env("DEBUG", debug.to_string()) .env("OPT_LEVEL", &unit.profile.opt_level) .env( "PROFILE", match unit.profile.root { ProfileRoot::Release => "release", ProfileRoot::Debug => "debug", }, ) .env("HOST", &bcx.host_triple()) .env("RUSTC", &bcx.rustc().path) .env("RUSTDOC", &*bcx.gctx.rustdoc()?) .inherit_jobserver(&build_runner.jobserver); // Find all artifact dependencies and make their file and containing directory discoverable using environment variables. for (var, value) in artifact::get_env(build_runner, unit, dependencies)? { cmd.env(&var, value); } if let Some(linker) = &build_runner.compilation.target_linker(unit.kind) { cmd.env("RUSTC_LINKER", linker); } if let Some(links) = unit.pkg.manifest().links() { cmd.env("CARGO_MANIFEST_LINKS", links); } if let Some(trim_paths) = unit.profile.trim_paths.as_ref() { cmd.env("CARGO_TRIM_PATHS", trim_paths.to_string()); } // Be sure to pass along all enabled features for this package, this is the // last piece of statically known information that we have. for feat in &unit.features { cmd.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), "1"); } let mut cfg_map = HashMap::new(); cfg_map.insert( "feature", unit.features.iter().map(|s| s.as_str()).collect::>(), ); // Manually inject debug_assertions based on the profile setting. // The cfg query from rustc doesn't include profile settings and would always be true, // so we override it with the actual profile setting. if unit.profile.debug_assertions { cfg_map.insert("debug_assertions", Vec::new()); } for cfg in bcx.target_data.cfg(unit.kind) { match *cfg { Cfg::Name(ref n) => { // Skip debug_assertions from rustc query; we use the profile setting instead if n.as_str() == "debug_assertions" { continue; } cfg_map.insert(n.as_str(), Vec::new()); } Cfg::KeyPair(ref k, ref v) => { let values = cfg_map.entry(k.as_str()).or_default(); values.push(v.as_str()); } } } for (k, v) in cfg_map { // FIXME: We should handle raw-idents somehow instead of pretending they // don't exist here let k = format!("CARGO_CFG_{}", super::envify(k)); cmd.env(&k, v.join(",")); } // Also inform the build script of the rustc compiler context. if let Some(wrapper) = bcx.rustc().wrapper.as_ref() { cmd.env("RUSTC_WRAPPER", wrapper); } else { cmd.env_remove("RUSTC_WRAPPER"); } cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); if build_runner.bcx.ws.is_member(&unit.pkg) { if let Some(wrapper) = bcx.rustc().workspace_wrapper.as_ref() { cmd.env("RUSTC_WORKSPACE_WRAPPER", wrapper); } } cmd.env("CARGO_ENCODED_RUSTFLAGS", unit.rustflags.join("\x1f")); cmd.env_remove("RUSTFLAGS"); if build_runner.bcx.ws.gctx().extra_verbose() { cmd.display_env_vars(); } let any_build_script_metadata = bcx.gctx.cli_unstable().any_build_script_metadata; // Gather the set of native dependencies that this package has along with // some other variables to close over. // // This information will be used at build-time later on to figure out which // sorts of variables need to be discovered at that time. let lib_deps = dependencies .iter() .filter_map(|dep| { if dep.unit.mode.is_run_custom_build() { let dep_metadata = build_runner.get_run_build_script_metadata(&dep.unit); let dep_name = dep.dep_name.unwrap_or(dep.unit.pkg.name()); Some(( dep_name, dep.unit .pkg .manifest() .links() .map(|links| links.to_string()), dep.unit.pkg.package_id(), dep_metadata, )) } else { None } }) .collect::>(); let library_name = unit.pkg.library().map(|t| t.crate_name()); let pkg_descr = unit.pkg.to_string(); let build_script_outputs = Arc::clone(&build_runner.build_script_outputs); let id = unit.pkg.package_id(); let run_files = BuildScriptRunFiles::for_unit(build_runner, unit); let host_target_root = build_runner.files().host_dest().map(|v| v.to_path_buf()); let all = ( id, library_name.clone(), pkg_descr.clone(), Arc::clone(&build_script_outputs), run_files.stdout.clone(), script_out_dir.clone(), ); let build_scripts = build_runner.build_scripts.get(unit).cloned(); let json_messages = bcx.build_config.emit_json(); let extra_verbose = bcx.gctx.extra_verbose(); let (prev_output, prev_script_out_dir) = prev_build_output(build_runner, unit); let metadata_hash = build_runner.get_run_build_script_metadata(unit); paths::create_dir_all(&script_dir)?; paths::create_dir_all(&script_out_dir)?; paths::create_dir_all(&run_files.root)?; let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed; let targets: Vec = unit.pkg.targets().to_vec(); let msrv = unit.pkg.rust_version().cloned(); // Need a separate copy for the fresh closure. let targets_fresh = targets.clone(); let msrv_fresh = msrv.clone(); let env_profile_name = unit.profile.name.to_uppercase(); let built_with_debuginfo = build_runner .bcx .unit_graph .get(unit) .and_then(|deps| deps.iter().find(|dep| dep.unit.target == unit.target)) .map(|dep| dep.unit.profile.debuginfo.is_turned_on()) .unwrap_or(false); // Prepare the unit of "dirty work" which will actually run the custom build // command. // // Note that this has to do some extra work just before running the command // to determine extra environment variables and such. let dirty = Work::new(move |state| { // Make sure that OUT_DIR exists. // // If we have an old build directory, then just move it into place, // otherwise create it! paths::create_dir_all(&script_out_dir) .context("failed to create script output directory for build command")?; // For all our native lib dependencies, pick up their metadata to pass // along to this custom build command. We're also careful to augment our // dynamic library search path in case the build script depended on any // native dynamic libraries. { let build_script_outputs = build_script_outputs.lock().unwrap(); for (name, links, dep_id, dep_metadata) in lib_deps { let script_output = build_script_outputs.get(dep_metadata).ok_or_else(|| { internal(format!( "failed to locate build state for env vars: {}/{}", dep_id, dep_metadata )) })?; let data = &script_output.metadata; for (key, value) in data.iter() { if let Some(ref links) = links { cmd.env( &format!("DEP_{}_{}", super::envify(&links), super::envify(key)), value, ); } if any_build_script_metadata { cmd.env( &format!("CARGO_DEP_{}_{}", super::envify(&name), super::envify(key)), value, ); } } } if let Some(build_scripts) = build_scripts && let Some(ref host_target_root) = host_target_root { super::add_plugin_deps( &mut cmd, &build_script_outputs, &build_scripts, host_target_root, )?; } } // And now finally, run the build command itself! state.running(&cmd); let timestamp = paths::set_invocation_time(&run_files.root)?; let prefix = format!("[{} {}] ", id.name(), id.version()); let mut log_messages_in_case_of_panic = Vec::new(); let span = tracing::debug_span!("build_script", process = cmd.to_string()); let output = span.in_scope(|| { cmd.exec_with_streaming( &mut |stdout| { if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) { log_messages_in_case_of_panic.push((Severity::Error, error.to_owned())); } if let Some(warning) = stdout .strip_prefix(OLD_CARGO_WARNING_SYNTAX) .or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX)) { log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned())); } if extra_verbose { state.stdout(format!("{}{}", prefix, stdout))?; } Ok(()) }, &mut |stderr| { if extra_verbose { state.stderr(format!("{}{}", prefix, stderr))?; } Ok(()) }, true, ) .with_context(|| { let mut build_error_context = format!("failed to run custom build command for `{}`", pkg_descr); // If we're opting into backtraces, mention that build dependencies' backtraces can // be improved by requesting debuginfo to be built, if we're not building with // debuginfo already. #[expect(clippy::disallowed_methods, reason = "consistency with rustc")] if let Ok(show_backtraces) = std::env::var("RUST_BACKTRACE") { if !built_with_debuginfo && show_backtraces != "0" { build_error_context.push_str(&format!( "\n\ note: To improve backtraces for build dependencies, set the \ CARGO_PROFILE_{env_profile_name}_BUILD_OVERRIDE_DEBUG=true environment \ variable to enable debug information generation.", )); } } build_error_context }) }); // If the build failed if let Err(error) = output { insert_log_messages_in_build_outputs( build_script_outputs, id, metadata_hash, log_messages_in_case_of_panic, ); return Err(error); } // ... or it logged any errors else if log_messages_in_case_of_panic .iter() .any(|(severity, _)| *severity == Severity::Error) { insert_log_messages_in_build_outputs( build_script_outputs, id, metadata_hash, log_messages_in_case_of_panic, ); anyhow::bail!("build script logged errors"); } let output = output.unwrap(); // After the build command has finished running, we need to be sure to // remember all of its output so we can later discover precisely what it // was, even if we don't run the build command again (due to freshness). // // This is also the location where we provide feedback into the build // state informing what variables were discovered via our script as // well. paths::write(&run_files.stdout, &output.stdout)?; // This mtime shift allows Cargo to detect if a source file was // modified in the middle of the build. paths::set_file_time_no_err(run_files.stdout, timestamp); paths::write(&run_files.stderr, &output.stderr)?; paths::write(&run_files.root_output, paths::path2bytes(&script_out_dir)?)?; let parsed_output = BuildOutput::parse( &output.stdout, library_name, &pkg_descr, &script_out_dir, &script_out_dir, nightly_features_allowed, &targets, &msrv, )?; if json_messages { emit_build_output(state, &parsed_output, script_out_dir.as_path(), id)?; } build_script_outputs .lock() .unwrap() .insert(id, metadata_hash, parsed_output); Ok(()) }); // Now that we've prepared our work-to-do, we need to prepare the fresh work // itself to run when we actually end up just discarding what we calculated // above. let fresh = Work::new(move |state| { let (id, library_name, pkg_descr, build_script_outputs, output_file, script_out_dir) = all; let output = match prev_output { Some(output) => output, None => BuildOutput::parse_file( &output_file, library_name, &pkg_descr, &prev_script_out_dir, &script_out_dir, nightly_features_allowed, &targets_fresh, &msrv_fresh, )?, }; if json_messages { emit_build_output(state, &output, script_out_dir.as_path(), id)?; } build_script_outputs .lock() .unwrap() .insert(id, metadata_hash, output); Ok(()) }); let mut job = fingerprint::prepare_target(build_runner, unit, false)?; if job.freshness().is_dirty() { job.before(dirty); } else { job.before(fresh); } Ok(job) } /// When a build script run fails, store only log messages, and nuke other /// outputs, as they are likely broken. fn insert_log_messages_in_build_outputs( build_script_outputs: Arc>, id: PackageId, metadata_hash: UnitHash, log_messages: Vec, ) { let build_output_with_only_log_messages = BuildOutput { log_messages, ..BuildOutput::default() }; build_script_outputs.lock().unwrap().insert( id, metadata_hash, build_output_with_only_log_messages, ); } impl BuildOutput { /// Like [`BuildOutput::parse`] but from a file path. pub fn parse_file( path: &Path, library_name: Option, pkg_descr: &str, script_out_dir_when_generated: &Path, script_out_dir: &Path, nightly_features_allowed: bool, targets: &[Target], msrv: &Option, ) -> CargoResult { let contents = paths::read_bytes(path)?; BuildOutput::parse( &contents, library_name, pkg_descr, script_out_dir_when_generated, script_out_dir, nightly_features_allowed, targets, msrv, ) } /// Parses the output instructions of a build script. /// /// * `pkg_descr` --- for error messages /// * `library_name` --- for determining if `RUSTC_BOOTSTRAP` should be allowed pub fn parse( input: &[u8], // Takes String instead of InternedString so passing `unit.pkg.name()` will give a compile error. library_name: Option, pkg_descr: &str, script_out_dir_when_generated: &Path, script_out_dir: &Path, nightly_features_allowed: bool, targets: &[Target], msrv: &Option, ) -> CargoResult { let mut library_paths = Vec::new(); let mut library_links = Vec::new(); let mut linker_args = Vec::new(); let mut cfgs = Vec::new(); let mut check_cfgs = Vec::new(); let mut env = Vec::new(); let mut metadata = Vec::new(); let mut rerun_if_changed = Vec::new(); let mut rerun_if_env_changed = Vec::new(); let mut log_messages = Vec::new(); let whence = format!("build script of `{}`", pkg_descr); // Old syntax: // cargo:rustc-flags=VALUE // cargo:KEY=VALUE (for other unreserved keys) // New syntax: // cargo::rustc-flags=VALUE // cargo::metadata=KEY=VALUE (for other unreserved keys) // Due to backwards compatibility, no new keys can be added to this old format. const RESERVED_PREFIXES: &[&str] = &[ "rustc-flags=", "rustc-link-lib=", "rustc-link-search=", "rustc-link-arg-cdylib=", "rustc-cdylib-link-arg=", "rustc-link-arg-bins=", "rustc-link-arg-bin=", "rustc-link-arg-tests=", "rustc-link-arg-benches=", "rustc-link-arg-examples=", "rustc-link-arg=", "rustc-cfg=", "rustc-check-cfg=", "rustc-env=", "warning=", "rerun-if-changed=", "rerun-if-env-changed=", ]; const DOCS_LINK_SUGGESTION: &str = "See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \ for more information about build script outputs."; fn has_reserved_prefix(flag: &str) -> bool { RESERVED_PREFIXES .iter() .any(|reserved_prefix| flag.starts_with(reserved_prefix)) } fn check_minimum_supported_rust_version_for_new_syntax( pkg_descr: &str, msrv: &Option, flag: &str, ) -> CargoResult<()> { if let Some(msrv) = msrv { let new_syntax_added_in = RustVersion::new(1, 77, 0); if !new_syntax_added_in.is_compatible_with(&msrv.to_partial()) { let old_syntax_suggestion = if has_reserved_prefix(flag) { format!( "Switch to the old `cargo:{flag}` syntax (note the single colon).\n" ) } else if flag.starts_with("metadata=") { let old_format_flag = flag.strip_prefix("metadata=").unwrap(); format!( "Switch to the old `cargo:{old_format_flag}` syntax instead of `cargo::{flag}` (note the single colon).\n" ) } else { String::new() }; bail!( "the `cargo::` syntax for build script output instructions was added in \ Rust 1.77.0, but the minimum supported Rust version of `{pkg_descr}` is {msrv}.\n\ {old_syntax_suggestion}\ {DOCS_LINK_SUGGESTION}" ); } } Ok(()) } fn parse_directive<'a>( whence: &str, line: &str, data: &'a str, old_syntax: bool, ) -> CargoResult<(&'a str, &'a str)> { let mut iter = data.splitn(2, "="); let key = iter.next(); let value = iter.next(); match (key, value) { (Some(a), Some(b)) => Ok((a, b.trim_end())), _ => bail!( "invalid output in {whence}: `{line}`\n\ Expected a line with `{syntax}KEY=VALUE` with an `=` character, \ but none was found.\n\ {DOCS_LINK_SUGGESTION}", syntax = if old_syntax { "cargo:" } else { "cargo::" }, ), } } fn parse_metadata<'a>( whence: &str, line: &str, data: &'a str, old_syntax: bool, ) -> CargoResult<(&'a str, &'a str)> { let mut iter = data.splitn(2, "="); let key = iter.next(); let value = iter.next(); match (key, value) { (Some(a), Some(b)) => Ok((a, b.trim_end())), _ => bail!( "invalid output in {whence}: `{line}`\n\ Expected a line with `{syntax}KEY=VALUE` with an `=` character, \ but none was found.\n\ {DOCS_LINK_SUGGESTION}", syntax = if old_syntax { "cargo:" } else { "cargo::metadata=" }, ), } } for line in input.split(|b| *b == b'\n') { let line = match str::from_utf8(line) { Ok(line) => line.trim(), Err(..) => continue, }; let mut old_syntax = false; let (key, value) = if let Some(data) = line.strip_prefix("cargo::") { check_minimum_supported_rust_version_for_new_syntax(pkg_descr, msrv, data)?; // For instance, `cargo::rustc-flags=foo` or `cargo::metadata=foo=bar`. parse_directive(whence.as_str(), line, data, old_syntax)? } else if let Some(data) = line.strip_prefix("cargo:") { old_syntax = true; // For instance, `cargo:rustc-flags=foo`. if has_reserved_prefix(data) { parse_directive(whence.as_str(), line, data, old_syntax)? } else { // For instance, `cargo:foo=bar`. ("metadata", data) } } else { // Skip this line since it doesn't start with "cargo:" or "cargo::". continue; }; // This will rewrite paths if the target directory has been moved. let value = value.replace( script_out_dir_when_generated.to_str().unwrap(), script_out_dir.to_str().unwrap(), ); let syntax_prefix = if old_syntax { "cargo:" } else { "cargo::" }; macro_rules! check_and_add_target { ($target_kind: expr, $is_target_kind: expr, $link_type: expr) => { if !targets.iter().any(|target| $is_target_kind(target)) { bail!( "invalid instruction `{}{}` from {}\n\ The package {} does not have a {} target.", syntax_prefix, key, whence, pkg_descr, $target_kind ); } linker_args.push(($link_type, value)); }; } // Keep in sync with TargetConfig::parse_links_overrides. match key { "rustc-flags" => { let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?; library_links.extend(links.into_iter()); library_paths.extend( paths .into_iter() .map(|p| LibraryPath::new(p, script_out_dir)), ); } "rustc-link-lib" => library_links.push(value.to_string()), "rustc-link-search" => { library_paths.push(LibraryPath::new(PathBuf::from(value), script_out_dir)) } "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => { if !targets.iter().any(|target| target.is_cdylib()) { log_messages.push(( Severity::Warning, format!( "{}{} was specified in the build script of {}, \ but that package does not contain a cdylib target\n\ \n\ Allowing this was an unintended change in the 1.50 \ release, and may become an error in the future. \ For more information, see \ .", syntax_prefix, key, pkg_descr ), )); } linker_args.push((LinkArgTarget::Cdylib, value)) } "rustc-link-arg-bins" => { check_and_add_target!("bin", Target::is_bin, LinkArgTarget::Bin); } "rustc-link-arg-bin" => { let (bin_name, arg) = value.split_once('=').ok_or_else(|| { anyhow::format_err!( "invalid instruction `{}{}={}` from {}\n\ The instruction should have the form {}{}=BIN=ARG", syntax_prefix, key, value, whence, syntax_prefix, key ) })?; if !targets .iter() .any(|target| target.is_bin() && target.name() == bin_name) { bail!( "invalid instruction `{}{}` from {}\n\ The package {} does not have a bin target with the name `{}`.", syntax_prefix, key, whence, pkg_descr, bin_name ); } linker_args.push(( LinkArgTarget::SingleBin(bin_name.to_owned()), arg.to_string(), )); } "rustc-link-arg-tests" => { check_and_add_target!("test", Target::is_test, LinkArgTarget::Test); } "rustc-link-arg-benches" => { check_and_add_target!("benchmark", Target::is_bench, LinkArgTarget::Bench); } "rustc-link-arg-examples" => { check_and_add_target!("example", Target::is_example, LinkArgTarget::Example); } "rustc-link-arg" => { linker_args.push((LinkArgTarget::All, value)); } "rustc-cfg" => cfgs.push(value.to_string()), "rustc-check-cfg" => check_cfgs.push(value.to_string()), "rustc-env" => { let (key, val) = BuildOutput::parse_rustc_env(&value, &whence)?; // Build scripts aren't allowed to set RUSTC_BOOTSTRAP. // See https://github.com/rust-lang/cargo/issues/7088. if key == "RUSTC_BOOTSTRAP" { // If RUSTC_BOOTSTRAP is already set, the user of Cargo knows about // bootstrap and still wants to override the channel. Give them a way to do // so, but still emit a warning that the current crate shouldn't be trying // to set RUSTC_BOOTSTRAP. // If this is a nightly build, setting RUSTC_BOOTSTRAP wouldn't affect the // behavior, so still only give a warning. // NOTE: cargo only allows nightly features on RUSTC_BOOTSTRAP=1, but we // want setting any value of RUSTC_BOOTSTRAP to downgrade this to a warning // (so that `RUSTC_BOOTSTRAP=library_name` will work) let rustc_bootstrap_allows = |name: Option<&str>| { let name = match name { // as of 2021, no binaries on crates.io use RUSTC_BOOTSTRAP, so // fine-grained opt-outs aren't needed. end-users can always use // RUSTC_BOOTSTRAP=1 from the top-level if it's really a problem. None => return false, Some(n) => n, }; #[expect( clippy::disallowed_methods, reason = "consistency with rustc, not specified behavior" )] std::env::var("RUSTC_BOOTSTRAP") .map_or(false, |var| var.split(',').any(|s| s == name)) }; if nightly_features_allowed || rustc_bootstrap_allows(library_name.as_deref()) { log_messages.push((Severity::Warning, format!("cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\ note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.", val, whence ))); } else { // Setting RUSTC_BOOTSTRAP would change the behavior of the crate. // Abort with an error. bail!( "cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\ note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.\n\ help: If you're sure you want to do this in your project, set the environment variable `RUSTC_BOOTSTRAP={}` before running cargo instead.", val, whence, library_name.as_deref().unwrap_or("1"), ); } } else { env.push((key, val)); } } "error" => log_messages.push((Severity::Error, value.to_string())), "warning" => log_messages.push((Severity::Warning, value.to_string())), "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)), "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()), "metadata" => { let (key, value) = parse_metadata(whence.as_str(), line, &value, old_syntax)?; metadata.push((key.to_owned(), value.to_owned())); } _ => bail!( "invalid output in {whence}: `{line}`\n\ Unknown key: `{key}`.\n\ {DOCS_LINK_SUGGESTION}", ), } } Ok(BuildOutput { library_paths, library_links, linker_args, cfgs, check_cfgs, env, metadata, rerun_if_changed, rerun_if_env_changed, log_messages, }) } /// Parses [`cargo::rustc-flags`] instruction. /// /// [`cargo::rustc-flags`]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargorustc-flagsflags pub fn parse_rustc_flags( value: &str, whence: &str, ) -> CargoResult<(Vec, Vec)> { let value = value.trim(); let mut flags_iter = value .split(|c: char| c.is_whitespace()) .filter(|w| w.chars().any(|c| !c.is_whitespace())); let (mut library_paths, mut library_links) = (Vec::new(), Vec::new()); while let Some(flag) = flags_iter.next() { if flag.starts_with("-l") || flag.starts_with("-L") { // Check if this flag has no space before the value as is // common with tools like pkg-config // e.g. -L/some/dir/local/lib or -licui18n let (flag, mut value) = flag.split_at(2); if value.is_empty() { value = match flags_iter.next() { Some(v) => v, None => bail! { "flag in rustc-flags has no value in {}: {}", whence, value }, } } match flag { "-l" => library_links.push(value.to_string()), "-L" => library_paths.push(PathBuf::from(value)), // This was already checked above _ => unreachable!(), }; } else { bail!( "only `-l` and `-L` flags are allowed in {}: `{}`", whence, value ) } } Ok((library_paths, library_links)) } /// Parses [`cargo::rustc-env`] instruction. /// /// [`cargo::rustc-env`]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#rustc-env pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> { match value.split_once('=') { Some((n, v)) => Ok((n.to_owned(), v.to_owned())), _ => bail!("Variable rustc-env has no value in {whence}: {value}"), } } } /// Prepares the Rust script for the unstable feature [metabuild]. /// /// [metabuild]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#metabuild fn prepare_metabuild( build_runner: &BuildRunner<'_, '_>, unit: &Unit, deps: &[String], ) -> CargoResult<()> { let mut output = Vec::new(); let available_deps = build_runner.unit_deps(unit); // Filter out optional dependencies, and look up the actual lib name. let meta_deps: Vec<_> = deps .iter() .filter_map(|name| { available_deps .iter() .find(|d| d.unit.pkg.name().as_str() == name.as_str()) .map(|d| d.unit.target.crate_name()) }) .collect(); output.push("fn main() {\n".to_string()); for dep in &meta_deps { output.push(format!(" {}::metabuild();\n", dep)); } output.push("}\n".to_string()); let output = output.join(""); let path = unit .pkg .manifest() .metabuild_path(build_runner.bcx.ws.build_dir()); paths::create_dir_all(path.parent().unwrap())?; paths::write_if_changed(path, &output)?; Ok(()) } impl BuildDeps { /// Creates a build script dependency information from a previous /// build script output path and the content. pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps { BuildDeps { build_script_output: output_file.to_path_buf(), rerun_if_changed: output .map(|p| &p.rerun_if_changed) .cloned() .unwrap_or_default(), rerun_if_env_changed: output .map(|p| &p.rerun_if_env_changed) .cloned() .unwrap_or_default(), } } } /// Computes several maps in [`BuildRunner`]. /// /// - [`build_scripts`]: A map that tracks which build scripts each package /// depends on. /// - [`build_explicit_deps`]: Dependency statements emitted by build scripts /// from a previous run. /// - [`build_script_outputs`]: Pre-populates this with any overridden build /// scripts. /// /// The important one here is [`build_scripts`], which for each `(package, /// metadata)` stores a [`BuildScripts`] object which contains a list of /// dependencies with build scripts that the unit should consider when linking. /// For example this lists all dependencies' `-L` flags which need to be /// propagated transitively. /// /// The given set of units to this function is the initial set of /// targets/profiles which are being built. /// /// [`build_scripts`]: BuildRunner::build_scripts /// [`build_explicit_deps`]: BuildRunner::build_explicit_deps /// [`build_script_outputs`]: BuildRunner::build_script_outputs pub fn build_map(build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> { let mut ret = HashMap::new(); for unit in &build_runner.bcx.roots { build(&mut ret, build_runner, unit)?; } build_runner .build_scripts .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v)))); return Ok(()); // Recursive function to build up the map we're constructing. This function // memoizes all of its return values as it goes along. fn build<'a>( out: &'a mut HashMap, build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult<&'a BuildScripts> { // Do a quick pre-flight check to see if we've already calculated the // set of dependencies. if out.contains_key(unit) { return Ok(&out[unit]); } // If there is a build script override, pre-fill the build output. if unit.mode.is_run_custom_build() { if let Some(links) = unit.pkg.manifest().links() { if let Some(output) = unit.links_overrides.get(links) { let metadata = build_runner.get_run_build_script_metadata(unit); build_runner.build_script_outputs.lock().unwrap().insert( unit.pkg.package_id(), metadata, output.clone(), ); } } } let mut ret = BuildScripts::default(); // If a package has a build script, add itself as something to inspect for linking. if !unit.target.is_custom_build() && unit.pkg.has_custom_build() { let script_metas = build_runner .find_build_script_metadatas(unit) .expect("has_custom_build should have RunCustomBuild"); for script_meta in script_metas { add_to_link(&mut ret, unit.pkg.package_id(), script_meta); } } if unit.mode.is_run_custom_build() { parse_previous_explicit_deps(build_runner, unit); } // We want to invoke the compiler deterministically to be cache-friendly // to rustc invocation caching schemes, so be sure to generate the same // set of build script dependency orderings via sorting the targets that // come out of the `Context`. let mut dependencies: Vec = build_runner .unit_deps(unit) .iter() .map(|d| d.unit.clone()) .collect(); dependencies.sort_by_key(|u| u.pkg.package_id()); for dep_unit in dependencies.iter() { let dep_scripts = build(out, build_runner, dep_unit)?; if dep_unit.target.for_host() { ret.plugins.extend(dep_scripts.to_link.iter().cloned()); } else if dep_unit.target.is_linkable() { for &(pkg, metadata) in dep_scripts.to_link.iter() { add_to_link(&mut ret, pkg, metadata); } } } match out.entry(unit.clone()) { Entry::Vacant(entry) => Ok(entry.insert(ret)), Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"), } } // When adding an entry to 'to_link' we only actually push it on if the // script hasn't seen it yet (e.g., we don't push on duplicates). fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: UnitHash) { if scripts.seen_to_link.insert((pkg, metadata)) { scripts.to_link.push((pkg, metadata)); } } /// Load any dependency declarations from a previous build script run. fn parse_previous_explicit_deps(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) { let run_files = BuildScriptRunFiles::for_unit(build_runner, unit); let (prev_output, _) = prev_build_output(build_runner, unit); let deps = BuildDeps::new(&run_files.stdout, prev_output.as_ref()); build_runner.build_explicit_deps.insert(unit.clone(), deps); } } /// Returns the previous parsed `BuildOutput`, if any, from a previous /// execution. /// /// Also returns the directory containing the output, typically used later in /// processing. fn prev_build_output( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, ) -> (Option, PathBuf) { let script_out_dir = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout { build_runner.files().out_dir_new_layout(unit) } else { build_runner.files().build_script_out_dir(unit) }; let run_files = BuildScriptRunFiles::for_unit(build_runner, unit); let prev_script_out_dir = paths::read_bytes(&run_files.root_output) .and_then(|bytes| paths::bytes2path(&bytes)) .unwrap_or_else(|_| script_out_dir.clone()); ( BuildOutput::parse_file( &run_files.stdout, unit.pkg.library().map(|t| t.crate_name()), &unit.pkg.to_string(), &prev_script_out_dir, &script_out_dir, build_runner.bcx.gctx.nightly_features_allowed, unit.pkg.targets(), &unit.pkg.rust_version().cloned(), ) .ok(), prev_script_out_dir, ) } impl BuildScriptOutputs { /// Inserts a new entry into the map. fn insert(&mut self, pkg_id: PackageId, metadata: UnitHash, parsed_output: BuildOutput) { match self.outputs.entry(metadata) { Entry::Vacant(entry) => { entry.insert(parsed_output); } Entry::Occupied(entry) => panic!( "build script output collision for {}/{}\n\ old={:?}\nnew={:?}", pkg_id, metadata, entry.get(), parsed_output ), } } /// Returns `true` if the given key already exists. fn contains_key(&self, metadata: UnitHash) -> bool { self.outputs.contains_key(&metadata) } /// Gets the build output for the given key. pub fn get(&self, meta: UnitHash) -> Option<&BuildOutput> { self.outputs.get(&meta) } /// Returns an iterator over all entries. pub fn iter(&self) -> impl Iterator { self.outputs.iter() } } /// Files with information about a running build script. struct BuildScriptRunFiles { /// The directory containing files related to running a build script. root: PathBuf, /// The stdout produced by the build script stdout: PathBuf, /// The stderr produced by the build script stderr: PathBuf, /// A file that contains the path to the `out` dir of the build script. /// This is used for detect if the directory was moved since the previous run. root_output: PathBuf, } impl BuildScriptRunFiles { pub fn for_unit(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self { let root = build_runner.files().build_script_run_dir(unit); let stdout = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout { root.join("stdout") } else { root.join("output") }; let stderr = root.join("stderr"); let root_output = root.join("root-output"); Self { root, stdout, stderr, root_output, } } } ================================================ FILE: src/cargo/core/compiler/fingerprint/dep_info.rs ================================================ //! Types and functions managing dep-info files. //! For more, see [the documentation] in the `fingerprint` module. //! //! [the documentation]: crate::core::compiler::fingerprint#dep-info-files use std::collections::HashMap; use std::ffi::OsString; use std::fmt; use std::io; use std::io::Read; use std::path::Path; use std::path::PathBuf; use std::str; use std::str::FromStr; use std::sync::Arc; use anyhow::bail; use cargo_util::ProcessBuilder; use cargo_util::Sha256; use cargo_util::paths; use serde::Serialize; use crate::CARGO_ENV; use crate::CargoResult; use crate::core::manifest::ManifestMetadata; /// The current format version of [`EncodedDepInfo`]. const CURRENT_ENCODED_DEP_INFO_VERSION: u8 = 1; /// The representation of the `.d` dep-info file generated by rustc #[derive(Default)] pub struct RustcDepInfo { /// The list of files that the main target in the dep-info file depends on. /// /// The optional checksums are parsed from the special `# checksum:...` comments. pub files: HashMap>, /// The list of environment variables we found that the rustc compilation /// depends on. /// /// The first element of the pair is the name of the env var and the second /// item is the value. `Some` means that the env var was set, and `None` /// means that the env var wasn't actually set and the compilation depends /// on it not being set. /// /// These are from the special `# env-var:...` comments. pub env: Vec<(String, Option)>, } /// Tells the associated path in [`EncodedDepInfo::files`] is relative to package root, /// target root, or absolute. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] pub enum DepInfoPathType { /// src/, e.g. src/lib.rs PackageRootRelative, /// {build-dir}/debug/deps/lib... /// or an absolute path /.../sysroot/... BuildRootRelative, } /// Same as [`RustcDepInfo`] except avoids absolute paths as much as possible to /// allow moving around the target directory. /// /// This is also stored in an optimized format to make parsing it fast because /// Cargo will read it for crates on all future compilations. /// /// Currently the format looks like: /// /// ```text /// +--------+---------+------------+------------+---------------+---------------+ /// | marker | version | # of files | file paths | # of env vars | env var pairs | /// +--------+---------+------------+------------+---------------+---------------+ /// ``` /// /// Each field represents /// /// * _Marker_ --- A magic marker to ensure that older Cargoes, which only /// recognize format v0 (prior to checksum support in [`f4ca7390`]), do not /// proceed with parsing newer formats. Since [`EncodedDepInfo`] is merely /// an optimization, and to avoid adding complexity, Cargo recognizes only /// one version of [`CURRENT_ENCODED_DEP_INFO_VERSION`]. /// The current layout looks like this /// ```text /// +----------------------------+ /// | [0x01 0x00 0x00 0x00 0xff] | /// +----------------------------+ /// ``` /// These bytes will be interpreted as "one file tracked and an invalid /// [`DepInfoPathType`] variant with 255" by older Cargoes, causing them to /// stop parsing. This could prevent problematic parsing as noted in /// rust-lang/cargo#14712. /// * _Version_ --- The current format version. /// * _Number of files/envs_ --- A `u32` representing the number of things. /// * _File paths_ --- Zero or more paths of files the dep-info file depends on. /// Each path is encoded as the following: /// /// ```text /// +-----------+-------------+------------+---------------+-----------+-------+ /// | path type | len of path | path bytes | cksum exists? | file size | cksum | /// +-----------+-------------+------------+---------------+-----------+-------+ /// ``` /// * _Env var pairs_ --- Zero or more env vars the dep-info file depends on. /// Each env key-value pair is encoded as the following: /// ```text /// +------------+-----------+---------------+--------------+-------------+ /// | len of key | key bytes | value exists? | len of value | value bytes | /// +------------+-----------+---------------+--------------+-------------+ /// ``` /// /// [`f4ca7390`]: https://github.com/rust-lang/cargo/commit/f4ca739073185ea5e1148ff100bb4a06d3bf721d #[derive(Default, Debug, PartialEq, Eq)] pub struct EncodedDepInfo { pub files: Vec<(DepInfoPathType, PathBuf, Option<(u64, String)>)>, pub env: Vec<(String, Option)>, } impl EncodedDepInfo { pub fn parse(mut bytes: &[u8]) -> Option { let bytes = &mut bytes; read_magic_marker(bytes)?; let version = read_u8(bytes)?; if version != CURRENT_ENCODED_DEP_INFO_VERSION { return None; } let nfiles = read_usize(bytes)?; let mut files = Vec::with_capacity(nfiles); for _ in 0..nfiles { let ty = match read_u8(bytes)? { 0 => DepInfoPathType::PackageRootRelative, 1 => DepInfoPathType::BuildRootRelative, _ => return None, }; let path_bytes = read_bytes(bytes)?; let path = paths::bytes2path(path_bytes).ok()?; let has_checksum = read_bool(bytes)?; let checksum_info = has_checksum .then(|| { let file_len = read_u64(bytes); let checksum_string = read_bytes(bytes) .map(Vec::from) .and_then(|v| String::from_utf8(v).ok()); file_len.zip(checksum_string) }) .flatten(); files.push((ty, path, checksum_info)); } let nenv = read_usize(bytes)?; let mut env = Vec::with_capacity(nenv); for _ in 0..nenv { let key = str::from_utf8(read_bytes(bytes)?).ok()?.to_string(); let val = match read_u8(bytes)? { 0 => None, 1 => Some(str::from_utf8(read_bytes(bytes)?).ok()?.to_string()), _ => return None, }; env.push((key, val)); } return Some(EncodedDepInfo { files, env }); /// See [`EncodedDepInfo`] for why a magic marker exists. fn read_magic_marker(bytes: &mut &[u8]) -> Option<()> { let _size = read_usize(bytes)?; let path_type = read_u8(bytes)?; if path_type != u8::MAX { // Old depinfo. Give up parsing it. None } else { Some(()) } } fn read_usize(bytes: &mut &[u8]) -> Option { let ret = bytes.get(..4)?; *bytes = &bytes[4..]; Some(u32::from_le_bytes(ret.try_into().unwrap()) as usize) } fn read_u64(bytes: &mut &[u8]) -> Option { let ret = bytes.get(..8)?; *bytes = &bytes[8..]; Some(u64::from_le_bytes(ret.try_into().unwrap())) } fn read_bool(bytes: &mut &[u8]) -> Option { read_u8(bytes).map(|b| b != 0) } fn read_u8(bytes: &mut &[u8]) -> Option { let ret = *bytes.get(0)?; *bytes = &bytes[1..]; Some(ret) } fn read_bytes<'a>(bytes: &mut &'a [u8]) -> Option<&'a [u8]> { let n = read_usize(bytes)? as usize; let ret = bytes.get(..n)?; *bytes = &bytes[n..]; Some(ret) } } pub fn serialize(&self) -> CargoResult> { let mut ret = Vec::new(); let dst = &mut ret; write_magic_marker(dst); dst.push(CURRENT_ENCODED_DEP_INFO_VERSION); write_usize(dst, self.files.len()); for (ty, file, checksum_info) in self.files.iter() { match ty { DepInfoPathType::PackageRootRelative => dst.push(0), DepInfoPathType::BuildRootRelative => dst.push(1), } write_bytes(dst, paths::path2bytes(file)?); write_bool(dst, checksum_info.is_some()); if let Some((len, checksum)) = checksum_info { write_u64(dst, *len); write_bytes(dst, checksum); } } write_usize(dst, self.env.len()); for (key, val) in self.env.iter() { write_bytes(dst, key); match val { None => dst.push(0), Some(val) => { dst.push(1); write_bytes(dst, val); } } } return Ok(ret); /// See [`EncodedDepInfo`] for why a magic marker exists. /// /// There is an assumption that there is always at least a file. fn write_magic_marker(dst: &mut Vec) { write_usize(dst, 1); dst.push(u8::MAX); } fn write_bytes(dst: &mut Vec, val: impl AsRef<[u8]>) { let val = val.as_ref(); write_usize(dst, val.len()); dst.extend_from_slice(val); } fn write_usize(dst: &mut Vec, val: usize) { dst.extend(&u32::to_le_bytes(val as u32)); } fn write_u64(dst: &mut Vec, val: u64) { dst.extend(&u64::to_le_bytes(val)); } fn write_bool(dst: &mut Vec, val: bool) { dst.push(u8::from(val)); } } } /// Parses the dep-info file coming out of rustc into a Cargo-specific format. /// /// This function will parse `rustc_dep_info` as a makefile-style dep info to /// learn about the all files which a crate depends on. This is then /// re-serialized into the `cargo_dep_info` path in a Cargo-specific format. /// /// The `pkg_root` argument here is the absolute path to the directory /// containing `Cargo.toml` for this crate that was compiled. The paths listed /// in the rustc dep-info file may or may not be absolute but we'll want to /// consider all of them relative to the `root` specified. /// /// The `rustc_cwd` argument is the absolute path to the cwd of the compiler /// when it was invoked. /// /// If the `allow_package` argument is true, then package-relative paths are /// included. If it is false, then package-relative paths are skipped and /// ignored (typically used for registry or git dependencies where we assume /// the source never changes, and we don't want the cost of running `stat` on /// all those files). See the module-level docs for the note about /// `-Zbinary-dep-depinfo` for more details on why this is done. /// /// The serialized Cargo format will contain a list of files, all of which are /// relative if they're under `root`. or absolute if they're elsewhere. /// /// The `env_config` argument is a set of environment variables that are /// defined in `[env]` table of the `config.toml`. pub fn translate_dep_info( rustc_dep_info: &Path, cargo_dep_info: &Path, rustc_cwd: &Path, pkg_root: &Path, build_root: &Path, rustc_cmd: &ProcessBuilder, allow_package: bool, env_config: &Arc>, ) -> CargoResult<()> { let depinfo = parse_rustc_dep_info(rustc_dep_info)?; let build_root = crate::util::try_canonicalize(build_root)?; let pkg_root = crate::util::try_canonicalize(pkg_root)?; let mut on_disk_info = EncodedDepInfo::default(); on_disk_info.env = depinfo.env; // This is a bit of a tricky statement, but here we're *removing* the // dependency on environment variables that were defined specifically for // the command itself. Environment variables returned by `get_envs` includes // environment variables like: // // * `OUT_DIR` if applicable // * env vars added by a build script, if any // // The general idea here is that the dep info file tells us what, when // changed, should cause us to rebuild the crate. These environment // variables are synthesized by Cargo and/or the build script, and the // intention is that their values are tracked elsewhere for whether the // crate needs to be rebuilt. // // For example a build script says when it needs to be rerun and otherwise // it's assumed to produce the same output, so we're guaranteed that env // vars defined by the build script will always be the same unless the build // script itself reruns, in which case the crate will rerun anyway. // // For things like `OUT_DIR` it's a bit sketchy for now. Most of the time // that's used for code generation but this is technically buggy where if // you write a binary that does `println!("{}", env!("OUT_DIR"))` we won't // recompile that if you move the target directory. Hopefully that's not too // bad of an issue for now... // // This also includes `CARGO` since if the code is explicitly wanting to // know that path, it should be rebuilt if it changes. The CARGO path is // not tracked elsewhere in the fingerprint. // // For cargo#13280, We trace env vars that are defined in the `[env]` config table. on_disk_info.env.retain(|(key, _)| { ManifestMetadata::should_track(key) || env_config.contains_key(key) || !rustc_cmd.get_envs().contains_key(key) || key == CARGO_ENV }); let serialize_path = |file| { // The path may be absolute or relative, canonical or not. Make sure // it is canonicalized so we are comparing the same kinds of paths. let abs_file = rustc_cwd.join(file); // If canonicalization fails, just use the abs path. There is currently // a bug where --remap-path-prefix is affecting .d files, causing them // to point to non-existent paths. let canon_file = crate::util::try_canonicalize(&abs_file).unwrap_or_else(|_| abs_file.clone()); let (ty, path) = if let Ok(stripped) = canon_file.strip_prefix(&build_root) { (DepInfoPathType::BuildRootRelative, stripped) } else if let Ok(stripped) = canon_file.strip_prefix(&pkg_root) { if !allow_package { return None; } (DepInfoPathType::PackageRootRelative, stripped) } else { // It's definitely not target root relative, but this is an absolute path (since it was // joined to rustc_cwd) and as such re-joining it later to the target root will have no // effect. (DepInfoPathType::BuildRootRelative, &*abs_file) }; Some((ty, path.to_owned())) }; for (file, checksum_info) in depinfo.files { let Some((path_type, path)) = serialize_path(file) else { continue; }; on_disk_info.files.push(( path_type, path, checksum_info.map(|(len, checksum)| (len, checksum.to_string())), )); } paths::write(cargo_dep_info, on_disk_info.serialize()?)?; Ok(()) } /// Parse the `.d` dep-info file generated by rustc. pub fn parse_rustc_dep_info(rustc_dep_info: &Path) -> CargoResult { let contents = paths::read(rustc_dep_info)?; let mut ret = RustcDepInfo::default(); let mut found_deps = false; for line in contents.lines() { if let Some(rest) = line.strip_prefix("# env-dep:") { let mut parts = rest.splitn(2, '='); let Some(env_var) = parts.next() else { continue; }; let env_val = match parts.next() { Some(s) => Some(unescape_env(s)?), None => None, }; ret.env.push((unescape_env(env_var)?, env_val)); } else if let Some(pos) = line.find(": ") { if found_deps { continue; } found_deps = true; let mut deps = line[pos + 2..].split_whitespace(); while let Some(s) = deps.next() { let mut file = s.to_string(); while file.ends_with('\\') { file.pop(); file.push(' '); file.push_str(deps.next().ok_or_else(|| { crate::util::internal("malformed dep-info format, trailing \\") })?); } ret.files.entry(file.into()).or_default(); } } else if let Some(rest) = line.strip_prefix("# checksum:") { let mut parts = rest.splitn(3, ' '); let Some(checksum) = parts.next().map(Checksum::from_str).transpose()? else { continue; }; let Some(Ok(file_len)) = parts .next() .and_then(|s| s.strip_prefix("file_len:").map(|s| s.parse::())) else { continue; }; let Some(path) = parts.next().map(PathBuf::from) else { continue; }; ret.files.insert(path, Some((file_len, checksum))); } } return Ok(ret); // rustc tries to fit env var names and values all on a single line, which // means it needs to escape `\r` and `\n`. The escape syntax used is "\n" // which means that `\` also needs to be escaped. fn unescape_env(s: &str) -> CargoResult { let mut ret = String::with_capacity(s.len()); let mut chars = s.chars(); while let Some(c) = chars.next() { if c != '\\' { ret.push(c); continue; } match chars.next() { Some('\\') => ret.push('\\'), Some('n') => ret.push('\n'), Some('r') => ret.push('\r'), Some(c) => bail!("unknown escape character `{}`", c), None => bail!("unterminated escape character"), } } Ok(ret) } } /// Parses Cargo's internal [`EncodedDepInfo`] structure that was previously /// serialized to disk. /// /// Note that this is not rustc's `*.d` files. /// /// Also note that rustc's `*.d` files are translated to Cargo-specific /// `EncodedDepInfo` files after compilations have finished in /// [`translate_dep_info`]. /// /// Returns `None` if the file is corrupt or couldn't be read from disk. This /// indicates that the crate should likely be rebuilt. pub fn parse_dep_info( pkg_root: &Path, build_root: &Path, dep_info: &Path, ) -> CargoResult> { let Ok(data) = paths::read_bytes(dep_info) else { return Ok(None); }; let Some(info) = EncodedDepInfo::parse(&data) else { tracing::warn!("failed to parse cargo's dep-info at {:?}", dep_info); return Ok(None); }; let mut ret = RustcDepInfo::default(); ret.env = info.env; ret.files .extend(info.files.into_iter().map(|(ty, path, checksum_info)| { ( make_absolute_path(ty, pkg_root, build_root, path), checksum_info.and_then(|(file_len, checksum)| { Checksum::from_str(&checksum).ok().map(|c| (file_len, c)) }), ) })); Ok(Some(ret)) } fn make_absolute_path( ty: DepInfoPathType, pkg_root: &Path, build_root: &Path, path: PathBuf, ) -> PathBuf { let relative_to = match ty { DepInfoPathType::PackageRootRelative => pkg_root, // N.B. path might be absolute here in which case the join below will have no effect DepInfoPathType::BuildRootRelative => build_root, }; if path.as_os_str().is_empty() { // Joining with an empty path causes Rust to add a trailing path separator. On Windows, this // would add an invalid trailing backslash to the .d file. return relative_to.to_path_buf(); } relative_to.join(path) } /// Some algorithms are here to ensure compatibility with possible rustc outputs. /// The presence of an algorithm here is not a suggestion that it's fit for use. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ChecksumAlgo { Sha256, Blake3, } impl ChecksumAlgo { fn hash_len(&self) -> usize { match self { ChecksumAlgo::Sha256 | ChecksumAlgo::Blake3 => 32, } } } impl FromStr for ChecksumAlgo { type Err = InvalidChecksum; fn from_str(s: &str) -> Result { match s { "sha256" => Ok(Self::Sha256), "blake3" => Ok(Self::Blake3), _ => Err(InvalidChecksum::InvalidChecksumAlgo), } } } impl fmt::Display for ChecksumAlgo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { ChecksumAlgo::Sha256 => "sha256", ChecksumAlgo::Blake3 => "blake3", }) } } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Checksum { algo: ChecksumAlgo, /// If the algorithm uses fewer than 32 bytes, then the remaining bytes will be zero. value: [u8; 32], } impl Checksum { pub fn new(algo: ChecksumAlgo, value: [u8; 32]) -> Self { Self { algo, value } } pub fn compute(algo: ChecksumAlgo, contents: impl Read) -> Result { // Buffer size is the recommended amount to fully leverage SIMD instructions on AVX-512 as per // blake3 documentation. let mut buf = vec![0; 16 * 1024]; let mut ret = Self { algo, value: [0; 32], }; let len = algo.hash_len(); let value = &mut ret.value[..len]; fn digest( mut hasher: T, mut update: impl FnMut(&mut T, &[u8]), finish: impl FnOnce(T, &mut [u8]), mut contents: impl Read, buf: &mut [u8], value: &mut [u8], ) -> Result<(), io::Error> { loop { let bytes_read = contents.read(buf)?; if bytes_read == 0 { break; } update(&mut hasher, &buf[0..bytes_read]); } finish(hasher, value); Ok(()) } match algo { ChecksumAlgo::Sha256 => { digest( Sha256::new(), |h, b| { h.update(b); }, |mut h, out| out.copy_from_slice(&h.finish()), contents, &mut buf, value, )?; } ChecksumAlgo::Blake3 => { digest( blake3::Hasher::new(), |h, b| { h.update(b); }, |h, out| out.copy_from_slice(h.finalize().as_bytes()), contents, &mut buf, value, )?; } } Ok(ret) } pub fn algo(&self) -> ChecksumAlgo { self.algo } pub fn value(&self) -> &[u8; 32] { &self.value } } impl FromStr for Checksum { type Err = InvalidChecksum; fn from_str(s: &str) -> Result { let mut parts = s.split('='); let Some(algo) = parts.next().map(ChecksumAlgo::from_str).transpose()? else { return Err(InvalidChecksum::InvalidFormat); }; let Some(checksum) = parts.next() else { return Err(InvalidChecksum::InvalidFormat); }; let mut value = [0; 32]; if hex::decode_to_slice(checksum, &mut value[0..algo.hash_len()]).is_err() { return Err(InvalidChecksum::InvalidChecksum(algo)); } Ok(Self { algo, value }) } } impl fmt::Display for Checksum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut checksum = [0; 64]; let hash_len = self.algo.hash_len(); hex::encode_to_slice(&self.value[0..hash_len], &mut checksum[0..(hash_len * 2)]) .map_err(|_| fmt::Error)?; write!( f, "{}={}", self.algo, str::from_utf8(&checksum[0..(hash_len * 2)]).unwrap_or_default() ) } } impl Serialize for Checksum { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) } } impl<'de> serde::Deserialize<'de> for Checksum { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; s.parse().map_err(serde::de::Error::custom) } } #[derive(Debug, thiserror::Error)] pub enum InvalidChecksum { #[error("algorithm portion incorrect, expected `sha256`, or `blake3`")] InvalidChecksumAlgo, #[error("expected {} hexadecimal digits in checksum portion", .0.hash_len() * 2)] InvalidChecksum(ChecksumAlgo), #[error("expected a string with format \"algorithm=hex_checksum\"")] InvalidFormat, } #[cfg(test)] mod encoded_dep_info { use super::*; #[track_caller] fn gen_test(checksum: bool) { let checksum = checksum.then_some((768, "c01efc669f09508b55eced32d3c88702578a7c3e".into())); let lib_rs = ( DepInfoPathType::BuildRootRelative, PathBuf::from("src/lib.rs"), checksum.clone(), ); let depinfo = EncodedDepInfo { files: vec![lib_rs.clone()], env: Vec::new(), }; let data = depinfo.serialize().unwrap(); assert_eq!(EncodedDepInfo::parse(&data).unwrap(), depinfo); let mod_rs = ( DepInfoPathType::BuildRootRelative, PathBuf::from("src/mod.rs"), checksum.clone(), ); let depinfo = EncodedDepInfo { files: vec![lib_rs.clone(), mod_rs.clone()], env: Vec::new(), }; let data = depinfo.serialize().unwrap(); assert_eq!(EncodedDepInfo::parse(&data).unwrap(), depinfo); let depinfo = EncodedDepInfo { files: vec![lib_rs, mod_rs], env: vec![ ("Gimli".into(), Some("Legolas".into())), ("Beren".into(), Some("Lúthien".into())), ], }; let data = depinfo.serialize().unwrap(); assert_eq!(EncodedDepInfo::parse(&data).unwrap(), depinfo); } #[test] fn round_trip() { gen_test(false); } #[test] fn round_trip_with_checksums() { gen_test(true); } #[test] fn path_type_is_u8_max() { #[rustfmt::skip] let data = [ 0x01, 0x00, 0x00, 0x00, 0xff, // magic marker CURRENT_ENCODED_DEP_INFO_VERSION, // version 0x01, 0x00, 0x00, 0x00, // # of files 0x00, // path type 0x04, 0x00, 0x00, 0x00, // len of path 0x72, 0x75, 0x73, 0x74, // path bytes ("rust") 0x00, // cksum exists? 0x00, 0x00, 0x00, 0x00, // # of env vars ]; // The current cargo doesn't recognize the magic marker. assert_eq!( EncodedDepInfo::parse(&data).unwrap(), EncodedDepInfo { files: vec![(DepInfoPathType::PackageRootRelative, "rust".into(), None)], env: Vec::new(), } ); } #[test] fn parse_v0_fingerprint_dep_info() { #[rustfmt::skip] let data = [ 0x01, 0x00, 0x00, 0x00, // # of files 0x00, // path type 0x04, 0x00, 0x00, 0x00, // len of path 0x72, 0x75, 0x73, 0x74, // path bytes: "rust" 0x00, 0x00, 0x00, 0x00, // # of env vars ]; // Cargo can't recognize v0 after `-Zchecksum-freshness` added. assert!(EncodedDepInfo::parse(&data).is_none()); } } ================================================ FILE: src/cargo/core/compiler/fingerprint/dirty_reason.rs ================================================ use std::collections::HashMap; use std::fmt; use std::fmt::Debug; use serde::Serialize; use super::*; use crate::core::Shell; use crate::core::compiler::UnitIndex; /// Tells a better story of why a build is considered "dirty" that leads /// to a recompile. Usually constructed via [`Fingerprint::compare`]. /// /// [`Fingerprint::compare`]: super::Fingerprint::compare #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "dirty_reason", rename_all = "kebab-case")] pub enum DirtyReason { RustcChanged, FeaturesChanged { old: String, new: String, }, DeclaredFeaturesChanged { old: String, new: String, }, TargetConfigurationChanged, PathToSourceChanged, ProfileConfigurationChanged, RustflagsChanged { old: Vec, new: Vec, }, ConfigSettingsChanged, CompileKindChanged, LocalLengthsChanged, PrecalculatedComponentsChanged { old: String, new: String, }, ChecksumUseChanged { old: bool, }, DepInfoOutputChanged { old: PathBuf, new: PathBuf, }, RerunIfChangedOutputFileChanged { old: PathBuf, new: PathBuf, }, RerunIfChangedOutputPathsChanged { old: Vec, new: Vec, }, EnvVarsChanged { old: String, new: String, }, EnvVarChanged { name: String, old_value: Option, new_value: Option, }, LocalFingerprintTypeChanged { old: String, new: String, }, NumberOfDependenciesChanged { old: usize, new: usize, }, UnitDependencyNameChanged { old: InternedString, new: InternedString, }, UnitDependencyInfoChanged { unit: UnitIndex, }, FsStatusOutdated(FsStatus), NothingObvious, Forced, /// First time to build something. FreshBuild, } trait ShellExt { fn dirty_because(&mut self, unit: &Unit, s: impl fmt::Display) -> CargoResult<()>; } impl ShellExt for Shell { fn dirty_because(&mut self, unit: &Unit, s: impl fmt::Display) -> CargoResult<()> { self.status("Dirty", format_args!("{}: {s}", &unit.pkg)) } } struct FileTimeDiff { old_time: FileTime, new_time: FileTime, } impl fmt::Display for FileTimeDiff { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s_diff = self.new_time.seconds() - self.old_time.seconds(); if s_diff >= 1 { write!(f, "{:#}", jiff::SignedDuration::from_secs(s_diff)) } else { // format nanoseconds as it is, jiff would display ms, us and ns let ns_diff = self.new_time.nanoseconds() - self.old_time.nanoseconds(); write!(f, "{ns_diff}ns") } } } #[derive(Copy, Clone)] struct After { old_time: FileTime, new_time: FileTime, what: &'static str, } impl fmt::Display for After { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { old_time, new_time, what, } = *self; let diff = FileTimeDiff { old_time, new_time }; write!(f, "{new_time}, {diff} after {what} at {old_time}") } } impl DirtyReason { /// Whether a build is dirty because it is a fresh build being kicked off. pub fn is_fresh_build(&self) -> bool { matches!(self, DirtyReason::FreshBuild) } fn after(old_time: FileTime, new_time: FileTime, what: &'static str) -> After { After { old_time, new_time, what, } } pub fn present_to( &self, s: &mut Shell, unit: &Unit, root: &Path, index_to_unit: &HashMap, ) -> CargoResult<()> { match self { DirtyReason::RustcChanged => s.dirty_because(unit, "the toolchain changed"), DirtyReason::FeaturesChanged { .. } => { s.dirty_because(unit, "the list of features changed") } DirtyReason::DeclaredFeaturesChanged { .. } => { s.dirty_because(unit, "the list of declared features changed") } DirtyReason::TargetConfigurationChanged => { s.dirty_because(unit, "the target configuration changed") } DirtyReason::PathToSourceChanged => { s.dirty_because(unit, "the path to the source changed") } DirtyReason::ProfileConfigurationChanged => { s.dirty_because(unit, "the profile configuration changed") } DirtyReason::RustflagsChanged { .. } => s.dirty_because(unit, "the rustflags changed"), DirtyReason::ConfigSettingsChanged => { s.dirty_because(unit, "the config settings changed") } DirtyReason::CompileKindChanged => { s.dirty_because(unit, "the rustc compile kind changed") } DirtyReason::LocalLengthsChanged => { s.dirty_because(unit, "the local lengths changed")?; s.note( "this could happen because of added/removed `cargo::rerun-if` instructions in the build script", )?; Ok(()) } DirtyReason::PrecalculatedComponentsChanged { .. } => { s.dirty_because(unit, "the precalculated components changed") } DirtyReason::ChecksumUseChanged { old } => { if *old { s.dirty_because( unit, "the prior compilation used checksum freshness and this one does not", ) } else { s.dirty_because(unit, "checksum freshness requested, prior compilation did not use checksum freshness") } } DirtyReason::DepInfoOutputChanged { .. } => { s.dirty_because(unit, "the dependency info output changed") } DirtyReason::RerunIfChangedOutputFileChanged { .. } => { s.dirty_because(unit, "rerun-if-changed output file path changed") } DirtyReason::RerunIfChangedOutputPathsChanged { .. } => { s.dirty_because(unit, "the rerun-if-changed instructions changed") } DirtyReason::EnvVarsChanged { .. } => { s.dirty_because(unit, "the environment variables changed") } DirtyReason::EnvVarChanged { name, .. } => { s.dirty_because(unit, format_args!("the env variable {name} changed")) } DirtyReason::LocalFingerprintTypeChanged { .. } => { s.dirty_because(unit, "the local fingerprint type changed") } DirtyReason::NumberOfDependenciesChanged { old, new } => s.dirty_because( unit, format_args!("number of dependencies changed ({old} => {new})",), ), DirtyReason::UnitDependencyNameChanged { old, new } => s.dirty_because( unit, format_args!("name of dependency changed ({old} => {new})"), ), DirtyReason::UnitDependencyInfoChanged { unit: dep_unit } => { let dep_name = index_to_unit.get(dep_unit).map(|u| u.pkg.name()).unwrap(); s.dirty_because( unit, format_args!("info of dependency `{dep_name}` changed"), ) } DirtyReason::FsStatusOutdated(status) => match status { FsStatus::Stale => s.dirty_because(unit, "stale, unknown reason"), FsStatus::StaleItem(item) => match item { StaleItem::MissingFile { path } => { let file = path.strip_prefix(root).unwrap_or(&path); s.dirty_because( unit, format_args!("the file `{}` is missing", file.display()), ) } StaleItem::UnableToReadFile { path } => { let file = path.strip_prefix(root).unwrap_or(&path); s.dirty_because( unit, format_args!("the file `{}` could not be read", file.display()), ) } StaleItem::FailedToReadMetadata { path } => { let file = path.strip_prefix(root).unwrap_or(&path); s.dirty_because( unit, format_args!("couldn't read metadata for file `{}`", file.display()), ) } StaleItem::ChangedFile { stale, stale_mtime, reference_mtime, .. } => { let file = stale.strip_prefix(root).unwrap_or(&stale); let after = Self::after(*reference_mtime, *stale_mtime, "last build"); s.dirty_because( unit, format_args!("the file `{}` has changed ({after})", file.display()), ) } StaleItem::ChangedChecksum { source, stored_checksum, new_checksum, } => { let file = source.strip_prefix(root).unwrap_or(&source); s.dirty_because( unit, format_args!( "the file `{}` has changed (checksum didn't match, {stored_checksum} != {new_checksum})", file.display(), ), ) } StaleItem::FileSizeChanged { path, old_size, new_size, } => { let file = path.strip_prefix(root).unwrap_or(&path); s.dirty_because( unit, format_args!( "file size changed ({old_size} != {new_size}) for `{}`", file.display() ), ) } StaleItem::MissingChecksum { path } => { let file = path.strip_prefix(root).unwrap_or(&path); s.dirty_because( unit, format_args!("the checksum for file `{}` is missing", file.display()), ) } StaleItem::ChangedEnv { var, .. } => s.dirty_because( unit, format_args!("the environment variable {var} changed"), ), }, FsStatus::StaleDependency { unit: dep_unit, dep_mtime, max_mtime, } => { let dep_name = index_to_unit.get(dep_unit).map(|u| u.pkg.name()).unwrap(); let after = Self::after(*max_mtime, *dep_mtime, "last build"); s.dirty_because( unit, format_args!("the dependency `{dep_name}` was rebuilt ({after})"), ) } FsStatus::StaleDepFingerprint { unit: dep_unit } => { let dep_name = index_to_unit.get(dep_unit).map(|u| u.pkg.name()).unwrap(); s.dirty_because( unit, format_args!("the dependency `{dep_name}` was rebuilt"), ) } FsStatus::UpToDate { .. } => { unreachable!() } }, DirtyReason::NothingObvious => { // See comment in fingerprint compare method. s.dirty_because(unit, "the fingerprint comparison turned up nothing obvious") } DirtyReason::Forced => s.dirty_because(unit, "forced"), DirtyReason::FreshBuild => s.dirty_because(unit, "fresh build"), } } } // These test the actual JSON structure that will be logged. // In the future we might decouple this from the actual log message schema. #[cfg(test)] mod json_schema { use super::*; use snapbox::IntoData; use snapbox::assert_data_eq; use snapbox::str; fn to_json(value: &T) -> String { serde_json::to_string_pretty(value).unwrap() } #[test] fn rustc_changed() { let reason = DirtyReason::RustcChanged; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "rustc-changed" } "#]] .is_json() ); } #[test] fn fresh_build() { let reason = DirtyReason::FreshBuild; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fresh-build" } "#]] .is_json() ); } #[test] fn forced() { let reason = DirtyReason::Forced; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "forced" } "#]] .is_json() ); } #[test] fn nothing_obvious() { let reason = DirtyReason::NothingObvious; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "nothing-obvious" } "#]] .is_json() ); } #[test] fn features_changed() { let reason = DirtyReason::FeaturesChanged { old: "f1".to_string(), new: "f1,f2".to_string(), }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "features-changed", "new": "f1,f2", "old": "f1" } "#]] .is_json() ); } #[test] fn rustflags_changed() { let reason = DirtyReason::RustflagsChanged { old: vec!["-C".into(), "opt-level=2".into()], new: vec!["--cfg".into(), "tokio_unstable".into()], }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "rustflags-changed", "old": [ "-C", "opt-level=2" ], "new": [ "--cfg", "tokio_unstable" ] } "#]] ); } #[test] fn env_var_changed_both_some() { let reason = DirtyReason::EnvVarChanged { name: "VAR".into(), old_value: Some("old".into()), new_value: Some("new".into()), }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "env-var-changed", "name": "VAR", "new_value": "new", "old_value": "old" } "#]] .is_json() ); } #[test] fn env_var_changed_old_none() { let reason = DirtyReason::EnvVarChanged { name: "VAR".into(), old_value: None, new_value: Some("new".into()), }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "env-var-changed", "name": "VAR", "new_value": "new", "old_value": null } "#]] .is_json() ); } #[test] fn dep_info_output_changed() { let reason = DirtyReason::DepInfoOutputChanged { old: "target/debug/old.d".into(), new: "target/debug/new.d".into(), }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "dep-info-output-changed", "old": "target/debug/old.d", "new": "target/debug/new.d" } "#]] .is_json() ); } #[test] fn number_of_dependencies_changed() { let reason = DirtyReason::NumberOfDependenciesChanged { old: 5, new: 7 }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "number-of-dependencies-changed", "old": 5, "new": 7 } "#]] .is_json() ); } #[test] fn unit_dependency_name_changed() { let reason = DirtyReason::UnitDependencyNameChanged { old: "old_dep".into(), new: "new_dep".into(), }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "unit-dependency-name-changed", "new": "new_dep", "old": "old_dep" } "#]] .is_json() ); } #[test] fn unit_dependency_info_changed() { let reason = DirtyReason::UnitDependencyInfoChanged { unit: UnitIndex(15), }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "unit-dependency-info-changed", "unit": 15 } "#]] .is_json() ); } #[test] fn fs_status_stale() { let reason = DirtyReason::FsStatusOutdated(FsStatus::Stale); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale" } "#]] .is_json() ); } #[test] fn fs_status_missing_file() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::MissingFile { path: "src/lib.rs".into(), })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "path": "src/lib.rs", "stale_item": "missing-file" } "#]] .is_json() ); } #[test] fn fs_status_changed_file() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::ChangedFile { reference: "target/debug/deps/libfoo-abc123.rmeta".into(), reference_mtime: FileTime::from_unix_time(1730567890, 123000000), stale: "src/lib.rs".into(), stale_mtime: FileTime::from_unix_time(1730567891, 456000000), })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "reference": "target/debug/deps/libfoo-abc123.rmeta", "reference_mtime": 1730567890123.0, "stale": "src/lib.rs", "stale_item": "changed-file", "stale_mtime": 1730567891456.0 } "#]] .is_json() ); } #[test] fn fs_status_changed_checksum() { use super::dep_info::ChecksumAlgo; let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::ChangedChecksum { source: "src/main.rs".into(), stored_checksum: Checksum::new(ChecksumAlgo::Sha256, [0xaa; 32]), new_checksum: Checksum::new(ChecksumAlgo::Sha256, [0xbb; 32]), })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "new_checksum": "sha256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "source": "src/main.rs", "stale_item": "changed-checksum", "stored_checksum": "sha256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } "#]] .is_json() ); } #[test] fn fs_status_stale_dependency() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleDependency { unit: UnitIndex(42), dep_mtime: FileTime::from_unix_time(1730567892, 789000000), max_mtime: FileTime::from_unix_time(1730567890, 123000000), }); assert_data_eq!( to_json(&reason), str![[r#" { "dep_mtime": 1730567892789.0, "dirty_reason": "fs-status-outdated", "fs_status": "stale-dependency", "max_mtime": 1730567890123.0, "unit": 42 } "#]] .is_json() ); } #[test] fn fs_status_stale_dep_fingerprint() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleDepFingerprint { unit: UnitIndex(42), }); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-dep-fingerprint", "unit": 42 } "#]] .is_json() ); } #[test] fn fs_status_unable_to_read_file() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::UnableToReadFile { path: "src/lib.rs".into(), })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "stale_item": "unable-to-read-file", "path": "src/lib.rs" } "#]] ); } #[test] fn fs_status_failed_to_read_metadata() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::FailedToReadMetadata { path: "src/lib.rs".into(), })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "stale_item": "failed-to-read-metadata", "path": "src/lib.rs" } "#]] ); } #[test] fn fs_status_file_size_changed() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::FileSizeChanged { path: "src/lib.rs".into(), old_size: 1024, new_size: 2048, })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "stale_item": "file-size-changed", "path": "src/lib.rs", "old_size": 1024, "new_size": 2048 } "#]] ); } #[test] fn fs_status_missing_checksum() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::MissingChecksum { path: "src/lib.rs".into(), })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "stale_item": "missing-checksum", "path": "src/lib.rs" } "#]] ); } #[test] fn fs_status_changed_env() { let reason = DirtyReason::FsStatusOutdated(FsStatus::StaleItem(StaleItem::ChangedEnv { var: "VAR".into(), previous: Some("old".into()), current: Some("new".into()), })); assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "fs-status-outdated", "fs_status": "stale-item", "stale_item": "changed-env", "var": "VAR", "previous": "old", "current": "new" } "#]] ); } #[test] fn checksum_use_changed() { let reason = DirtyReason::ChecksumUseChanged { old: false }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "checksum-use-changed", "old": false } "#]] .is_json() ); } #[test] fn rerun_if_changed_output_paths_changed() { let reason = DirtyReason::RerunIfChangedOutputPathsChanged { old: vec!["file1.txt".into(), "file2.txt".into()], new: vec!["file1.txt".into(), "file2.txt".into(), "file3.txt".into()], }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "rerun-if-changed-output-paths-changed", "old": [ "file1.txt", "file2.txt" ], "new": [ "file1.txt", "file2.txt", "file3.txt" ] } "#]] .is_json() ); } #[test] fn local_fingerprint_type_changed() { let reason = DirtyReason::LocalFingerprintTypeChanged { old: "precalculated".to_owned(), new: "rerun-if-changed".to_owned(), }; assert_data_eq!( to_json(&reason), str![[r#" { "dirty_reason": "local-fingerprint-type-changed", "new": "rerun-if-changed", "old": "precalculated" } "#]] .is_json() ); } } ================================================ FILE: src/cargo/core/compiler/fingerprint/mod.rs ================================================ //! Tracks changes to determine if something needs to be recompiled. //! //! This module implements change-tracking so that Cargo can know whether or //! not something needs to be recompiled. A Cargo [`Unit`] can be either "dirty" //! (needs to be recompiled) or "fresh" (it does not need to be recompiled). //! //! ## Mechanisms affecting freshness //! //! There are several mechanisms that influence a Unit's freshness: //! //! - The [`Fingerprint`] is a hash, saved to the filesystem in the //! `.fingerprint` directory, that tracks information about the Unit. If the //! fingerprint is missing (such as the first time the unit is being //! compiled), then the unit is dirty. If any of the fingerprint fields //! change (like the name of the source file), then the Unit is considered //! dirty. //! //! The `Fingerprint` also tracks the fingerprints of all its dependencies, //! so a change in a dependency will propagate the "dirty" status up. //! //! - Filesystem mtime tracking is also used to check if a unit is dirty. //! See the section below on "Mtime comparison" for more details. There //! are essentially two parts to mtime tracking: //! //! 1. The mtime of a Unit's output files is compared to the mtime of all //! its dependencies' output file mtimes (see //! [`check_filesystem`]). If any output is missing, or is //! older than a dependency's output, then the unit is dirty. //! 2. The mtime of a Unit's source files is compared to the mtime of its //! dep-info file in the fingerprint directory (see [`find_stale_file`]). //! The dep-info file is used as an anchor to know when the last build of //! the unit was done. See the "dep-info files" section below for more //! details. If any input files are missing, or are newer than the //! dep-info, then the unit is dirty. //! //! - Alternatively if you're using the unstable feature `checksum-freshness` //! mtimes are ignored entirely in favor of comparing first the file size, and //! then the checksum with a known prior value emitted by rustc. Only nightly //! rustc will emit the needed metadata at the time of writing. This is dependent //! on the unstable feature `-Z checksum-hash-algorithm`. //! //! Note: Fingerprinting is not a perfect solution. Filesystem mtime tracking //! is notoriously imprecise and problematic. Only a small part of the //! environment is captured. This is a balance of performance, simplicity, and //! completeness. Sandboxing, hashing file contents, tracking every file //! access, environment variable, and network operation would ensure more //! reliable and reproducible builds at the cost of being complex, slow, and //! platform-dependent. //! //! ## Fingerprints and [`UnitHash`]s //! //! [`Metadata`] tracks several [`UnitHash`]s, including //! [`Metadata::unit_id`], [`Metadata::c_metadata`], and [`Metadata::c_extra_filename`]. //! See its documentation for more details. //! //! NOTE: Not all output files are isolated via filename hashes (like dylibs). //! The fingerprint directory uses a hash, but sometimes units share the same //! fingerprint directory (when they don't have Metadata) so care should be //! taken to handle this! //! //! Fingerprints and [`UnitHash`]s are similar, and track some of the same things. //! [`UnitHash`]s contains information that is required to keep Units separate. //! The Fingerprint includes additional information that should cause a //! recompile, but it is desired to reuse the same filenames. A comparison //! of what is tracked: //! //! Value | Fingerprint | `Metadata::unit_id` [^8] | `Metadata::c_metadata` //! -------------------------------------------|-------------|--------------------------|----------------------- //! rustc | ✓ | ✓ | ✓ //! [`Profile`] | ✓ | ✓ | ✓ //! `cargo rustc` extra args | ✓ | ✓[^7] | //! [`CompileMode`] | ✓ | ✓ | ✓ //! Target Name | ✓ | ✓ | ✓ //! `TargetKind` (bin/lib/etc.) | ✓ | ✓ | ✓ //! Enabled Features | ✓ | ✓ | ✓ //! Declared Features | ✓ | | //! Immediate dependency’s hashes | ✓[^1] | ✓ | ✓ //! [`CompileKind`] (host/target) | ✓ | ✓ | ✓ //! `__CARGO_DEFAULT_LIB_METADATA`[^4] | | ✓ | ✓ //! `package_id` | | ✓ | ✓ //! Target src path relative to ws | ✓ | | //! Target flags (test/bench/for_host/edition) | ✓ | | //! -C incremental=… flag | ✓ | | //! mtime of sources | ✓[^3] | | //! RUSTFLAGS/RUSTDOCFLAGS | ✓ | ✓[^7] | //! [`Lto`] flags | ✓ | ✓ | ✓ //! config settings[^5] | ✓ | | //! `is_std` | | ✓ | ✓ //! `[lints]` table[^6] | ✓ | | //! `[lints.rust.unexpected_cfgs.check-cfg]` | ✓ | | //! //! [^1]: Bin dependencies are not included. //! //! [^3]: See below for details on mtime tracking. //! //! [^4]: `__CARGO_DEFAULT_LIB_METADATA` is set by rustbuild to embed the //! release channel (bootstrap/stable/beta/nightly) in libstd. //! //! [^5]: Config settings that are not otherwise captured anywhere else. //! Currently, this is only `doc.extern-map`. //! //! [^6]: Via [`Manifest::lint_rustflags`][crate::core::Manifest::lint_rustflags] //! //! [^7]: extra-flags and RUSTFLAGS are conditionally excluded when `--remap-path-prefix` is //! present to avoid breaking build reproducibility while we wait for trim-paths //! //! [^8]: including `-Cextra-filename` //! //! When deciding what should go in the Metadata vs the Fingerprint, consider //! that some files (like dylibs) do not have a hash in their filename. Thus, //! if a value changes, only the fingerprint will detect the change (consider, //! for example, swapping between different features). Fields that are only in //! Metadata generally aren't relevant to the fingerprint because they //! fundamentally change the output (like target vs host changes the directory //! where it is emitted). //! //! ## Fingerprint files //! //! Fingerprint information is stored in the //! `target/{debug,release}/.fingerprint/` directory. Each Unit is stored in a //! separate directory. Each Unit directory contains: //! //! - A file with a 16 hex-digit hash. This is the Fingerprint hash, used for //! quick loading and comparison. //! - A `.json` file that contains details about the Fingerprint. This is only //! used to log details about *why* a fingerprint is considered dirty. //! `CARGO_LOG=cargo::core::compiler::fingerprint=trace cargo build` can be //! used to display this log information. //! - A "dep-info" file which is a translation of rustc's `*.d` dep-info files //! to a Cargo-specific format that tweaks file names and is optimized for //! reading quickly. //! - An `invoked.timestamp` file whose filesystem mtime is updated every time //! the Unit is built. This is used for capturing the time when the build //! starts, to detect if files are changed in the middle of the build. See //! below for more details. //! //! Note that some units are a little different. A Unit for *running* a build //! script or for `rustdoc` does not have a dep-info file (it's not //! applicable). Build script `invoked.timestamp` files are in the build //! output directory. //! //! ## Fingerprint calculation //! //! After the list of Units has been calculated, the Units are added to the //! [`JobQueue`]. As each one is added, the fingerprint is calculated, and the //! dirty/fresh status is recorded. A closure is used to update the fingerprint //! on-disk when the Unit successfully finishes. The closure will recompute the //! Fingerprint based on the updated information. If the Unit fails to compile, //! the fingerprint is not updated. //! //! Fingerprints are cached in the [`BuildRunner`]. This makes computing //! Fingerprints faster, but also is necessary for properly updating //! dependency information. Since a Fingerprint includes the Fingerprints of //! all dependencies, when it is updated, by using `Arc` clones, it //! automatically picks up the updates to its dependencies. //! //! ### dep-info files //! //! Cargo has several kinds of "dep info" files: //! //! * dep-info files generated by `rustc`. //! * Fingerprint dep-info files translated from the first one. //! * dep-info for external build system integration. //! * Unstable `-Zbinary-dep-depinfo`. //! //! #### `rustc` dep-info files //! //! Cargo passes the `--emit=dep-info` flag to `rustc` so that `rustc` will //! generate a "dep info" file (with the `.d` extension). This is a //! Makefile-like syntax that includes all of the source files used to build //! the crate. This file is used by Cargo to know which files to check to see //! if the crate will need to be rebuilt. Example: //! //! ```makefile //! /path/to/target/debug/deps/cargo-b6219d178925203d: src/bin/main.rs src/bin/cargo/cli.rs # … etc. //! ``` //! //! #### Fingerprint dep-info files //! //! After `rustc` exits successfully, Cargo will read the first kind of dep //! info file and translate it into a binary format that is stored in the //! fingerprint directory ([`translate_dep_info`]). //! //! These are used to quickly scan for any changed files. The mtime of the //! fingerprint dep-info file itself is used as the reference for comparing the //! source files to determine if any of the source files have been modified //! (see [below](#mtime-comparison) for more detail). //! //! Note that Cargo parses the special `# env-var:...` comments in dep-info //! files to learn about environment variables that the rustc compile depends on. //! Cargo then later uses this to trigger a recompile if a referenced env var //! changes (even if the source didn't change). //! This also includes env vars generated from Cargo metadata like `CARGO_PKG_DESCRIPTION`. //! (See [`crate::core::manifest::ManifestMetadata`] //! //! #### dep-info files for build system integration. //! //! There is also a third dep-info file. Cargo will extend the file created by //! rustc with some additional information and saves this into the output //! directory. This is intended for build system integration. See the //! [`output_depinfo`] function for more detail. //! //! #### -Zbinary-dep-depinfo //! //! `rustc` has an experimental flag `-Zbinary-dep-depinfo`. This causes //! `rustc` to include binary files (like rlibs) in the dep-info file. This is //! primarily to support rustc development, so that Cargo can check the //! implicit dependency to the standard library (which lives in the sysroot). //! We want Cargo to recompile whenever the standard library rlib/dylibs //! change, and this is a generic mechanism to make that work. //! //! ### Mtime comparison //! //! The use of modification timestamps is the most common way a unit will be //! determined to be dirty or fresh between builds. There are many subtle //! issues and edge cases with mtime comparisons. This gives a high-level //! overview, but you'll need to read the code for the gritty details. Mtime //! handling is different for different unit kinds. The different styles are //! driven by the [`Fingerprint::local`] field, which is set based on the unit //! kind. //! //! The status of whether or not the mtime is "stale" or "up-to-date" is //! stored in [`Fingerprint::fs_status`]. //! //! All units will compare the mtime of its newest output file with the mtimes //! of the outputs of all its dependencies. If any output file is missing, //! then the unit is stale. If any dependency is newer, the unit is stale. //! //! #### Normal package mtime handling //! //! [`LocalFingerprint::CheckDepInfo`] is used for checking the mtime of //! packages. It compares the mtime of the input files (the source files) to //! the mtime of the dep-info file (which is written last after a build is //! finished). If the dep-info is missing, the unit is stale (it has never //! been built). The list of input files comes from the dep-info file. See the //! section above for details on dep-info files. //! //! Also note that although registry and git packages use [`CheckDepInfo`], none //! of their source files are included in the dep-info (see //! [`translate_dep_info`]), so for those kinds no mtime checking is done //! (unless `-Zbinary-dep-depinfo` is used). Repository and git packages are //! static, so there is no need to check anything. //! //! When a build is complete, the mtime of the dep-info file in the //! fingerprint directory is modified to rewind it to the time when the build //! started. This is done by creating an `invoked.timestamp` file when the //! build starts to capture the start time. The mtime is rewound to the start //! to handle the case where the user modifies a source file while a build is //! running. Cargo can't know whether or not the file was included in the //! build, so it takes a conservative approach of assuming the file was *not* //! included, and it should be rebuilt during the next build. //! //! #### Rustdoc mtime handling //! //! Rustdoc does not emit a dep-info file, so Cargo currently has a relatively //! simple system for detecting rebuilds. [`LocalFingerprint::Precalculated`] is //! used for rustdoc units. For registry packages, this is the package //! version. For git packages, it is the git hash. For path packages, it is //! a string of the mtime of the newest file in the package. //! //! There are some known bugs with how this works, so it should be improved at //! some point. //! //! #### Build script mtime handling //! //! Build script mtime handling runs in different modes. There is the "old //! style" where the build script does not emit any `rerun-if` directives. In //! this mode, Cargo will use [`LocalFingerprint::Precalculated`]. See the //! "rustdoc" section above how it works. //! //! In the new-style, each `rerun-if` directive is translated to the //! corresponding [`LocalFingerprint`] variant. The [`RerunIfChanged`] variant //! compares the mtime of the given filenames against the mtime of the //! "output" file. //! //! Similar to normal units, the build script "output" file mtime is rewound //! to the time just before the build script is executed to handle mid-build //! modifications. //! //! ## Considerations for inclusion in a fingerprint //! //! Over time we've realized a few items which historically were included in //! fingerprint hashings should not actually be included. Examples are: //! //! * Modification time values. We strive to never include a modification time //! inside a `Fingerprint` to get hashed into an actual value. While //! theoretically fine to do, in practice this causes issues with common //! applications like Docker. Docker, after a layer is built, will zero out //! the nanosecond part of all filesystem modification times. This means that //! the actual modification time is different for all build artifacts, which //! if we tracked the actual values of modification times would cause //! unnecessary recompiles. To fix this we instead only track paths which are //! relevant. These paths are checked dynamically to see if they're up to //! date, and the modification time doesn't make its way into the fingerprint //! hash. //! //! * Absolute path names. We strive to maintain a property where if you rename //! a project directory Cargo will continue to preserve all build artifacts //! and reuse the cache. This means that we can't ever hash an absolute path //! name. Instead we always hash relative path names and the "root" is passed //! in at runtime dynamically. Some of this is best effort, but the general //! idea is that we assume all accesses within a crate stay within that //! crate. //! //! These are pretty tricky to test for unfortunately, but we should have a good //! test suite nowadays and lord knows Cargo gets enough testing in the wild! //! //! ## Build scripts //! //! The *running* of a build script ([`CompileMode::RunCustomBuild`]) is treated //! significantly different than all other Unit kinds. It has its own function //! for calculating the Fingerprint ([`calculate_run_custom_build`]) and has some //! unique considerations. It does not track the same information as a normal //! Unit. The information tracked depends on the `rerun-if-changed` and //! `rerun-if-env-changed` statements produced by the build script. If the //! script does not emit either of these statements, the Fingerprint runs in //! "old style" mode where an mtime change of *any* file in the package will //! cause the build script to be re-run. Otherwise, the fingerprint *only* //! tracks the individual "rerun-if" items listed by the build script. //! //! The "rerun-if" statements from a *previous* build are stored in the build //! output directory in a file called `output`. Cargo parses this file when //! the Unit for that build script is prepared for the [`JobQueue`]. The //! Fingerprint code can then use that information to compute the Fingerprint //! and compare against the old fingerprint hash. //! //! Care must be taken with build script Fingerprints because the //! [`Fingerprint::local`] value may be changed after the build script runs //! (such as if the build script adds or removes "rerun-if" items). //! //! Another complication is if a build script is overridden. In that case, the //! fingerprint is the hash of the output of the override. //! //! ## Special considerations //! //! Registry dependencies do not track the mtime of files. This is because //! registry dependencies are not expected to change (if a new version is //! used, the Package ID will change, causing a rebuild). Cargo currently //! partially works with Docker caching. When a Docker image is built, it has //! normal mtime information. However, when a step is cached, the nanosecond //! portions of all files is zeroed out. Currently this works, but care must //! be taken for situations like these. //! //! HFS on macOS only supports 1 second timestamps. This causes a significant //! number of problems, particularly with Cargo's testsuite which does rapid //! builds in succession. Other filesystems have various degrees of //! resolution. //! //! Various weird filesystems (such as network filesystems) also can cause //! complications. Network filesystems may track the time on the server //! (except when the time is set manually such as with //! `filetime::set_file_times`). Not all filesystems support modifying the //! mtime. //! //! See the [`A-rebuild-detection`] label on the issue tracker for more. //! //! [`check_filesystem`]: Fingerprint::check_filesystem //! [`Metadata`]: crate::core::compiler::Metadata //! [`Metadata::unit_id`]: crate::core::compiler::Metadata::unit_id //! [`Metadata::c_metadata`]: crate::core::compiler::Metadata::c_metadata //! [`Metadata::c_extra_filename`]: crate::core::compiler::Metadata::c_extra_filename //! [`UnitHash`]: crate::core::compiler::UnitHash //! [`Profile`]: crate::core::profiles::Profile //! [`CompileMode`]: crate::core::compiler::CompileMode //! [`Lto`]: crate::core::compiler::Lto //! [`CompileKind`]: crate::core::compiler::CompileKind //! [`JobQueue`]: super::job_queue::JobQueue //! [`output_depinfo`]: super::output_depinfo() //! [`CheckDepInfo`]: LocalFingerprint::CheckDepInfo //! [`RerunIfChanged`]: LocalFingerprint::RerunIfChanged //! [`CompileMode::RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild //! [`A-rebuild-detection`]: https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AA-rebuild-detection mod dep_info; mod dirty_reason; mod rustdoc; use std::collections::hash_map::{Entry, HashMap}; use std::env; use std::ffi::OsString; use std::fs; use std::fs::File; use std::hash::{self, Hash, Hasher}; use std::io::{self}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::SystemTime; use anyhow::Context as _; use anyhow::format_err; use cargo_util::paths; use filetime::FileTime; use serde::de; use serde::ser; use serde::{Deserialize, Serialize}; use tracing::{debug, info}; use crate::core::Package; use crate::core::compiler::unit_graph::UnitDep; use crate::util; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::log_message::LogMessage; use crate::util::{StableHasher, internal, path_args}; use crate::{CARGO_ENV, GlobalContext}; use super::BuildContext; use super::BuildRunner; use super::FileFlavor; use super::Job; use super::Unit; use super::UnitIndex; use super::Work; use super::custom_build::BuildDeps; pub use self::dep_info::Checksum; pub use self::dep_info::parse_dep_info; pub use self::dep_info::parse_rustc_dep_info; pub use self::dep_info::translate_dep_info; pub use self::dirty_reason::DirtyReason; pub use self::rustdoc::RustdocFingerprint; /// Result of comparing fingerprints between the current and previous builds. enum FingerprintComparison { /// The unit does not need rebuilding. Fresh, /// The unit needs rebuilding. Dirty { /// The reason why the unit is dirty. reason: DirtyReason, }, } /// Determines if a [`Unit`] is up-to-date, and if not prepares necessary work to /// update the persisted fingerprint. /// /// This function will inspect `Unit`, calculate a fingerprint for it, and then /// return an appropriate [`Job`] to run. The returned `Job` will be a noop if /// `unit` is considered "fresh", or if it was previously built and cached. /// Otherwise the `Job` returned will write out the true fingerprint to the /// filesystem, to be executed after the unit's work has completed. /// /// The `force` flag is a way to force the `Job` to be "dirty", or always /// update the fingerprint. **Beware using this flag** because it does not /// transitively propagate throughout the dependency graph, it only forces this /// one unit which is very unlikely to be what you want unless you're /// exclusively talking about top-level units. #[tracing::instrument( skip(build_runner, unit), fields(package_id = %unit.pkg.package_id(), target = unit.target.name()) )] pub fn prepare_target( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, force: bool, ) -> CargoResult { let bcx = build_runner.bcx; let loc = build_runner.files().fingerprint_file_path(unit, ""); debug!("fingerprint at: {}", loc.display()); // Figure out if this unit is up to date. After calculating the fingerprint // compare it to an old version, if any, and attempt to print diagnostic // information about failed comparisons to aid in debugging. let fingerprint = calculate(build_runner, unit)?; let mtime_on_use = build_runner.bcx.gctx.cli_unstable().mtime_on_use; let dirty_reason = match compare_old_fingerprint(unit, &loc, &*fingerprint, mtime_on_use, force) { FingerprintComparison::Fresh => None, FingerprintComparison::Dirty { reason } => Some(reason), }; if let Some(logger) = bcx.logger { let index = bcx.unit_to_index[unit]; let mut cause = None; let status = match dirty_reason.as_ref() { Some(reason) if reason.is_fresh_build() => util::log_message::FingerprintStatus::New, Some(reason) => { cause = Some(reason.clone()); util::log_message::FingerprintStatus::Dirty } None => util::log_message::FingerprintStatus::Fresh, }; logger.log(LogMessage::UnitFingerprint { index, status, cause, }); } let Some(dirty_reason) = dirty_reason else { return Ok(Job::new_fresh()); }; // We're going to rebuild, so ensure the source of the crate passes all // verification checks before we build it. // // The `Source::verify` method is intended to allow sources to execute // pre-build checks to ensure that the relevant source code is all // up-to-date and as expected. This is currently used primarily for // directory sources which will use this hook to perform an integrity check // on all files in the source to ensure they haven't changed. If they have // changed then an error is issued. let source_id = unit.pkg.package_id().source_id(); let sources = bcx.packages.sources(); let source = sources .get(source_id) .ok_or_else(|| internal("missing package source"))?; source.verify(unit.pkg.package_id())?; // Clear out the old fingerprint file if it exists. This protects when // compilation is interrupted leaving a corrupt file. For example, a // project with a lib.rs and integration test (two units): // // 1. Build the library and integration test. // 2. Make a change to lib.rs (NOT the integration test). // 3. Build the integration test, hit Ctrl-C while linking. With gcc, this // will leave behind an incomplete executable (zero size, or partially // written). NOTE: The library builds successfully, it is the linking // of the integration test that we are interrupting. // 4. Build the integration test again. // // Without the following line, then step 3 will leave a valid fingerprint // on the disk. Then step 4 will think the integration test is "fresh" // because: // // - There is a valid fingerprint hash on disk (written in step 1). // - The mtime of the output file (the corrupt integration executable // written in step 3) is newer than all of its dependencies. // - The mtime of the integration test fingerprint dep-info file (written // in step 1) is newer than the integration test's source files, because // we haven't modified any of its source files. // // But the executable is corrupt and needs to be rebuilt. Clearing the // fingerprint at step 3 ensures that Cargo never mistakes a partially // written output as up-to-date. if loc.exists() { // Truncate instead of delete so that compare_old_fingerprint will // still log the reason for the fingerprint failure instead of just // reporting "failed to read fingerprint" during the next build if // this build fails. paths::write(&loc, b"")?; } let write_fingerprint = if unit.mode.is_run_custom_build() { // For build scripts the `local` field of the fingerprint may change // while we're executing it. For example it could be in the legacy // "consider everything a dependency mode" and then we switch to "deps // are explicitly specified" mode. // // To handle this movement we need to regenerate the `local` field of a // build script's fingerprint after it's executed. We do this by // using the `build_script_local_fingerprints` function which returns a // thunk we can invoke on a foreign thread to calculate this. let build_script_outputs = Arc::clone(&build_runner.build_script_outputs); let metadata = build_runner.get_run_build_script_metadata(unit); let (gen_local, _overridden) = build_script_local_fingerprints(build_runner, unit)?; let output_path = build_runner.build_explicit_deps[unit] .build_script_output .clone(); Work::new(move |_| { let outputs = build_script_outputs.lock().unwrap(); let output = outputs .get(metadata) .expect("output must exist after running"); let deps = BuildDeps::new(&output_path, Some(output)); // FIXME: it's basically buggy that we pass `None` to `call_box` // here. See documentation on `build_script_local_fingerprints` // below for more information. Despite this just try to proceed and // hobble along if it happens to return `Some`. if let Some(new_local) = (gen_local)(&deps, None)? { *fingerprint.local.lock().unwrap() = new_local; } write_fingerprint(&loc, &fingerprint) }) } else { Work::new(move |_| write_fingerprint(&loc, &fingerprint)) }; Ok(Job::new_dirty(write_fingerprint, dirty_reason)) } /// Dependency edge information for fingerprints. This is generated for each /// dependency and is stored in a [`Fingerprint`]. #[derive(Clone)] struct DepFingerprint { /// The hash of the package id that this dependency points to pkg_id: u64, /// The crate name we're using for this dependency, which if we change we'll /// need to recompile! name: InternedString, /// Whether or not this dependency is flagged as a public dependency or not. public: bool, /// Whether or not this dependency is an rmeta dependency or a "full" /// dependency. In the case of an rmeta dependency our dependency edge only /// actually requires the rmeta from what we depend on, so when checking /// mtime information all files other than the rmeta can be ignored. only_requires_rmeta: bool, /// The dependency's fingerprint we recursively point to, containing all the /// other hash information we'd otherwise need. fingerprint: Arc, } /// A fingerprint can be considered to be a "short string" representing the /// state of a world for a package. /// /// If a fingerprint ever changes, then the package itself needs to be /// recompiled. Inputs to the fingerprint include source code modifications, /// compiler flags, compiler version, etc. This structure is not simply a /// `String` due to the fact that some fingerprints cannot be calculated lazily. /// /// Path sources, for example, use the mtime of the corresponding dep-info file /// as a fingerprint (all source files must be modified *before* this mtime). /// This dep-info file is not generated, however, until after the crate is /// compiled. As a result, this structure can be thought of as a fingerprint /// to-be. The actual value can be calculated via [`hash_u64()`], but the operation /// may fail as some files may not have been generated. /// /// Note that dependencies are taken into account for fingerprints because rustc /// requires that whenever an upstream crate is recompiled that all downstream /// dependents are also recompiled. This is typically tracked through /// [`DependencyQueue`], but it also needs to be retained here because Cargo can /// be interrupted while executing, losing the state of the [`DependencyQueue`] /// graph. /// /// [`hash_u64()`]: crate::core::compiler::fingerprint::Fingerprint::hash_u64 /// [`DependencyQueue`]: crate::util::DependencyQueue #[derive(Serialize, Deserialize)] pub struct Fingerprint { /// Hash of the version of `rustc` used. rustc: u64, /// Sorted list of cfg features enabled. features: String, /// Sorted list of all the declared cfg features. declared_features: String, /// Hash of the `Target` struct, including the target name, /// package-relative source path, edition, etc. target: u64, /// Hash of the [`Profile`], [`CompileMode`], and any extra flags passed via /// `cargo rustc` or `cargo rustdoc`. /// /// [`Profile`]: crate::core::profiles::Profile /// [`CompileMode`]: crate::core::compiler::CompileMode profile: u64, /// Hash of the path to the base source file. This is relative to the /// workspace root for path members, or absolute for other sources. path: u64, /// Fingerprints of dependencies. deps: Vec, /// Information about the inputs that affect this Unit (such as source /// file mtimes or build script environment variables). local: Mutex>, /// Cached hash of the [`Fingerprint`] struct. Used to improve performance /// for hashing. #[serde(skip)] memoized_hash: Mutex>, /// RUSTFLAGS/RUSTDOCFLAGS environment variable value (or config value). rustflags: Vec, /// Hash of various config settings that change how things are compiled. config: u64, /// The rustc target. This is only relevant for `.json` files, otherwise /// the metadata hash segregates the units. compile_kind: u64, /// Unit index for this fingerprint, used for tracing cascading rebuilds. /// Not persisted to disk as indices can change between builds. #[serde(skip)] index: UnitIndex, /// Description of whether the filesystem status for this unit is up to date /// or should be considered stale. #[serde(skip)] fs_status: FsStatus, /// Files, relative to `target_root`, that are produced by the step that /// this `Fingerprint` represents. This is used to detect when the whole /// fingerprint is out of date if this is missing, or if previous /// fingerprints output files are regenerated and look newer than this one. #[serde(skip)] outputs: Vec, } /// Indication of the status on the filesystem for a particular unit. #[derive(Clone, Default, Debug, Serialize, Deserialize)] #[serde(tag = "fs_status", rename_all = "kebab-case")] pub enum FsStatus { /// This unit is to be considered stale, even if hash information all /// matches. #[default] Stale, /// File system inputs have changed (or are missing), or there were /// changes to the environment variables that affect this unit. See /// the variants of [`StaleItem`] for more information. StaleItem(StaleItem), /// A dependency was stale. StaleDependency { unit: UnitIndex, #[serde(with = "serde_file_time")] dep_mtime: FileTime, #[serde(with = "serde_file_time")] max_mtime: FileTime, }, /// A dependency's fingerprint was stale. StaleDepFingerprint { unit: UnitIndex }, /// This unit is up-to-date. All outputs and their corresponding mtime are /// listed in the payload here for other dependencies to compare against. #[serde(skip)] UpToDate { mtimes: HashMap }, } impl FsStatus { fn up_to_date(&self) -> bool { match self { FsStatus::UpToDate { .. } => true, FsStatus::Stale | FsStatus::StaleItem(_) | FsStatus::StaleDependency { .. } | FsStatus::StaleDepFingerprint { .. } => false, } } } mod serde_file_time { use filetime::FileTime; use serde::Deserialize; use serde::Serialize; /// Serialize FileTime as milliseconds with nano. pub(super) fn serialize(ft: &FileTime, s: S) -> Result where S: serde::Serializer, { let secs_as_millis = ft.unix_seconds() as f64 * 1000.0; let nanos_as_millis = ft.nanoseconds() as f64 / 1_000_000.0; (secs_as_millis + nanos_as_millis).serialize(s) } /// Deserialize FileTime from milliseconds with nano. pub(super) fn deserialize<'de, D>(d: D) -> Result where D: serde::Deserializer<'de>, { let millis = f64::deserialize(d)?; let secs = (millis / 1000.0) as i64; let nanos = ((millis % 1000.0) * 1_000_000.0) as u32; Ok(FileTime::from_unix_time(secs, nanos)) } } impl Serialize for DepFingerprint { fn serialize(&self, ser: S) -> Result where S: ser::Serializer, { ( &self.pkg_id, &self.name, &self.public, &self.fingerprint.hash_u64(), ) .serialize(ser) } } impl<'de> Deserialize<'de> for DepFingerprint { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { let (pkg_id, name, public, hash) = <(u64, String, bool, u64)>::deserialize(d)?; Ok(DepFingerprint { pkg_id, name: name.into(), public, fingerprint: Arc::new(Fingerprint { memoized_hash: Mutex::new(Some(hash)), ..Fingerprint::new() }), // This field is never read since it's only used in // `check_filesystem` which isn't used by fingerprints loaded from // disk. only_requires_rmeta: false, }) } } /// A `LocalFingerprint` represents something that we use to detect direct /// changes to a `Fingerprint`. /// /// This is where we track file information, env vars, etc. This /// `LocalFingerprint` struct is hashed and if the hash changes will force a /// recompile of any fingerprint it's included into. Note that the "local" /// terminology comes from the fact that it only has to do with one crate, and /// `Fingerprint` tracks the transitive propagation of fingerprint changes. /// /// Note that because this is hashed its contents are carefully managed. Like /// mentioned in the above module docs, we don't want to hash absolute paths or /// mtime information. /// /// Also note that a `LocalFingerprint` is used in `check_filesystem` to detect /// when the filesystem contains stale information (based on mtime currently). /// The paths here don't change much between compilations but they're used as /// inputs when we probe the filesystem looking at information. #[derive(Debug, Serialize, Deserialize, Hash)] enum LocalFingerprint { /// This is a precalculated fingerprint which has an opaque string we just /// hash as usual. This variant is primarily used for rustdoc where we /// don't have a dep-info file to compare against. /// /// This is also used for build scripts with no `rerun-if-*` statements, but /// that's overall a mistake and causes bugs in Cargo. We shouldn't use this /// for build scripts. Precalculated(String), /// This is used for crate compilations. The `dep_info` file is a relative /// path anchored at `target_root(...)` to the dep-info file that Cargo /// generates (which is a custom serialization after parsing rustc's own /// `dep-info` output). /// /// The `dep_info` file, when present, also lists a number of other files /// for us to look at. If any of those files are newer than this file then /// we need to recompile. /// /// If the `checksum` bool is true then the `dep_info` file is expected to /// contain file checksums instead of file mtimes. CheckDepInfo { dep_info: PathBuf, checksum: bool }, /// This represents a nonempty set of `rerun-if-changed` annotations printed /// out by a build script. The `output` file is a relative file anchored at /// `target_root(...)` which is the actual output of the build script. That /// output has already been parsed and the paths printed out via /// `rerun-if-changed` are listed in `paths`. The `paths` field is relative /// to `pkg.root()` /// /// This is considered up-to-date if all of the `paths` are older than /// `output`, otherwise we need to recompile. RerunIfChanged { output: PathBuf, paths: Vec, }, /// This represents a single `rerun-if-env-changed` annotation printed by a /// build script. The exact env var and value are hashed here. There's no /// filesystem dependence here, and if the values are changed the hash will /// change forcing a recompile. RerunIfEnvChanged { var: String, val: Option }, } /// See [`FsStatus::StaleItem`]. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "stale_item", rename_all = "kebab-case")] pub enum StaleItem { MissingFile { path: PathBuf, }, UnableToReadFile { path: PathBuf, }, FailedToReadMetadata { path: PathBuf, }, FileSizeChanged { path: PathBuf, old_size: u64, new_size: u64, }, ChangedFile { reference: PathBuf, #[serde(with = "serde_file_time")] reference_mtime: FileTime, stale: PathBuf, #[serde(with = "serde_file_time")] stale_mtime: FileTime, }, ChangedChecksum { source: PathBuf, stored_checksum: Checksum, new_checksum: Checksum, }, MissingChecksum { path: PathBuf, }, ChangedEnv { var: String, previous: Option, current: Option, }, } impl LocalFingerprint { /// Read the environment variable of the given env `key`, and creates a new /// [`LocalFingerprint::RerunIfEnvChanged`] for it. The `env_config` is used firstly /// to check if the env var is set in the config system as some envs need to be overridden. /// If not, it will fallback to `std::env::var`. /// // TODO: `std::env::var` is allowed at this moment. Should figure out // if it makes sense if permitting to read env from the env snapshot. #[allow(clippy::disallowed_methods)] fn from_env>( key: K, env_config: &Arc>, ) -> LocalFingerprint { let key = key.as_ref(); let var = key.to_owned(); let val = if let Some(val) = env_config.get(key) { val.to_str().map(ToOwned::to_owned) } else { env::var(key).ok() }; LocalFingerprint::RerunIfEnvChanged { var, val } } /// Checks dynamically at runtime if this `LocalFingerprint` has a stale /// item inside of it. /// /// The main purpose of this function is to handle two different ways /// fingerprints can be invalidated: /// /// * One is a dependency listed in rustc's dep-info files is invalid. Note /// that these could either be env vars or files. We check both here. /// /// * Another is the `rerun-if-changed` directive from build scripts. This /// is where we'll find whether files have actually changed fn find_stale_item( &self, mtime_cache: &mut HashMap, checksum_cache: &mut HashMap, pkg: &Package, build_root: &Path, cargo_exe: &Path, gctx: &GlobalContext, ) -> CargoResult> { let pkg_root = pkg.root(); match self { // We need to parse `dep_info`, learn about the crate's dependencies. // // For each env var we see if our current process's env var still // matches, and for each file we see if any of them are newer than // the `dep_info` file itself whose mtime represents the start of // rustc. LocalFingerprint::CheckDepInfo { dep_info, checksum } => { let dep_info = build_root.join(dep_info); let Some(info) = parse_dep_info(pkg_root, build_root, &dep_info)? else { return Ok(Some(StaleItem::MissingFile { path: dep_info })); }; for (key, previous) in info.env.iter() { if let Some(value) = pkg.manifest().metadata().env_var(key.as_str()) { if Some(value.as_ref()) == previous.as_deref() { continue; } } let current = if key == CARGO_ENV { Some(cargo_exe.to_str().ok_or_else(|| { format_err!( "cargo exe path {} must be valid UTF-8", cargo_exe.display() ) })?) } else { if let Some(value) = gctx.env_config()?.get(key) { value.to_str() } else { gctx.get_env(key).ok() } }; if current == previous.as_deref() { continue; } return Ok(Some(StaleItem::ChangedEnv { var: key.clone(), previous: previous.clone(), current: current.map(Into::into), })); } if *checksum { Ok(find_stale_file( mtime_cache, checksum_cache, &dep_info, info.files.iter().map(|(file, checksum)| (file, *checksum)), *checksum, )) } else { Ok(find_stale_file( mtime_cache, checksum_cache, &dep_info, info.files.into_keys().map(|p| (p, None)), *checksum, )) } } // We need to verify that no paths listed in `paths` are newer than // the `output` path itself, or the last time the build script ran. LocalFingerprint::RerunIfChanged { output, paths } => Ok(find_stale_file( mtime_cache, checksum_cache, &build_root.join(output), paths.iter().map(|p| (pkg_root.join(p), None)), false, )), // These have no dependencies on the filesystem, and their values // are included natively in the `Fingerprint` hash so nothing // tocheck for here. LocalFingerprint::RerunIfEnvChanged { .. } => Ok(None), LocalFingerprint::Precalculated(..) => Ok(None), } } fn kind(&self) -> &'static str { match self { LocalFingerprint::Precalculated(..) => "precalculated", LocalFingerprint::CheckDepInfo { .. } => "dep-info", LocalFingerprint::RerunIfChanged { .. } => "rerun-if-changed", LocalFingerprint::RerunIfEnvChanged { .. } => "rerun-if-env-changed", } } } impl Fingerprint { fn new() -> Fingerprint { Fingerprint { rustc: 0, target: 0, profile: 0, path: 0, features: String::new(), declared_features: String::new(), deps: Vec::new(), local: Mutex::new(Vec::new()), memoized_hash: Mutex::new(None), rustflags: Vec::new(), config: 0, compile_kind: 0, index: UnitIndex::default(), fs_status: FsStatus::Stale, outputs: Vec::new(), } } /// For performance reasons fingerprints will memoize their own hash, but /// there's also internal mutability with its `local` field which can /// change, for example with build scripts, during a build. /// /// This method can be used to bust all memoized hashes just before a build /// to ensure that after a build completes everything is up-to-date. pub fn clear_memoized(&self) { *self.memoized_hash.lock().unwrap() = None; } fn hash_u64(&self) -> u64 { if let Some(s) = *self.memoized_hash.lock().unwrap() { return s; } let ret = util::hash_u64(self); *self.memoized_hash.lock().unwrap() = Some(ret); ret } /// Compares this fingerprint with an old version which was previously /// serialized to filesystem. /// /// The purpose of this is exclusively to produce a diagnostic message /// [`DirtyReason`], indicating why we're recompiling something. fn compare(&self, old: &Fingerprint) -> DirtyReason { if self.rustc != old.rustc { return DirtyReason::RustcChanged; } if self.features != old.features { return DirtyReason::FeaturesChanged { old: old.features.clone(), new: self.features.clone(), }; } if self.declared_features != old.declared_features { return DirtyReason::DeclaredFeaturesChanged { old: old.declared_features.clone(), new: self.declared_features.clone(), }; } if self.target != old.target { return DirtyReason::TargetConfigurationChanged; } if self.path != old.path { return DirtyReason::PathToSourceChanged; } if self.profile != old.profile { return DirtyReason::ProfileConfigurationChanged; } if self.rustflags != old.rustflags { return DirtyReason::RustflagsChanged { old: old.rustflags.clone(), new: self.rustflags.clone(), }; } if self.config != old.config { return DirtyReason::ConfigSettingsChanged; } if self.compile_kind != old.compile_kind { return DirtyReason::CompileKindChanged; } let my_local = self.local.lock().unwrap(); let old_local = old.local.lock().unwrap(); if my_local.len() != old_local.len() { return DirtyReason::LocalLengthsChanged; } for (new, old) in my_local.iter().zip(old_local.iter()) { match (new, old) { (LocalFingerprint::Precalculated(a), LocalFingerprint::Precalculated(b)) => { if a != b { return DirtyReason::PrecalculatedComponentsChanged { old: b.to_string(), new: a.to_string(), }; } } ( LocalFingerprint::CheckDepInfo { dep_info: a_dep, checksum: checksum_a, }, LocalFingerprint::CheckDepInfo { dep_info: b_dep, checksum: checksum_b, }, ) => { if a_dep != b_dep { return DirtyReason::DepInfoOutputChanged { old: b_dep.clone(), new: a_dep.clone(), }; } if checksum_a != checksum_b { return DirtyReason::ChecksumUseChanged { old: *checksum_b }; } } ( LocalFingerprint::RerunIfChanged { output: a_out, paths: a_paths, }, LocalFingerprint::RerunIfChanged { output: b_out, paths: b_paths, }, ) => { if a_out != b_out { return DirtyReason::RerunIfChangedOutputFileChanged { old: b_out.clone(), new: a_out.clone(), }; } if a_paths != b_paths { return DirtyReason::RerunIfChangedOutputPathsChanged { old: b_paths.clone(), new: a_paths.clone(), }; } } ( LocalFingerprint::RerunIfEnvChanged { var: a_key, val: a_value, }, LocalFingerprint::RerunIfEnvChanged { var: b_key, val: b_value, }, ) => { if *a_key != *b_key { return DirtyReason::EnvVarsChanged { old: b_key.clone(), new: a_key.clone(), }; } if *a_value != *b_value { return DirtyReason::EnvVarChanged { name: a_key.clone(), old_value: b_value.clone(), new_value: a_value.clone(), }; } } (a, b) => { return DirtyReason::LocalFingerprintTypeChanged { old: b.kind().to_owned(), new: a.kind().to_owned(), }; } } } if self.deps.len() != old.deps.len() { return DirtyReason::NumberOfDependenciesChanged { old: old.deps.len(), new: self.deps.len(), }; } for (a, b) in self.deps.iter().zip(old.deps.iter()) { if a.name != b.name { return DirtyReason::UnitDependencyNameChanged { old: b.name, new: a.name, }; } if a.fingerprint.hash_u64() != b.fingerprint.hash_u64() { return DirtyReason::UnitDependencyInfoChanged { unit: a.fingerprint.index, }; } } if !self.fs_status.up_to_date() { return DirtyReason::FsStatusOutdated(self.fs_status.clone()); } // This typically means some filesystem modifications happened or // something transitive was odd. In general we should strive to provide // a better error message than this, so if you see this message a lot it // likely means this method needs to be updated! DirtyReason::NothingObvious } /// Dynamically inspect the local filesystem to update the `fs_status` field /// of this `Fingerprint`. /// /// This function is used just after a `Fingerprint` is constructed to check /// the local state of the filesystem and propagate any dirtiness from /// dependencies up to this unit as well. This function assumes that the /// unit starts out as [`FsStatus::Stale`] and then it will optionally switch /// it to `UpToDate` if it can. fn check_filesystem( &mut self, mtime_cache: &mut HashMap, checksum_cache: &mut HashMap, pkg: &Package, build_root: &Path, cargo_exe: &Path, gctx: &GlobalContext, ) -> CargoResult<()> { assert!(!self.fs_status.up_to_date()); let pkg_root = pkg.root(); let mut mtimes = HashMap::new(); // Get the `mtime` of all outputs. Optionally update their mtime // afterwards based on the `mtime_on_use` flag. Afterwards we want the // minimum mtime as it's the one we'll be comparing to inputs and // dependencies. for output in self.outputs.iter() { let Ok(mtime) = paths::mtime(output) else { // This path failed to report its `mtime`. It probably doesn't // exists, so leave ourselves as stale and bail out. let item = StaleItem::FailedToReadMetadata { path: output.clone(), }; self.fs_status = FsStatus::StaleItem(item); return Ok(()); }; assert!(mtimes.insert(output.clone(), mtime).is_none()); } let opt_max = mtimes.iter().max_by_key(|kv| kv.1); let Some((max_path, max_mtime)) = opt_max else { // We had no output files. This means we're an overridden build // script and we're just always up to date because we aren't // watching the filesystem. self.fs_status = FsStatus::UpToDate { mtimes }; return Ok(()); }; debug!( "max output mtime for {:?} is {:?} {}", pkg_root, max_path, max_mtime ); for dep in self.deps.iter() { let dep_mtimes = match &dep.fingerprint.fs_status { FsStatus::UpToDate { mtimes } => mtimes, // If our dependency is stale, so are we, so bail out. FsStatus::Stale | FsStatus::StaleItem(_) | FsStatus::StaleDependency { .. } | FsStatus::StaleDepFingerprint { .. } => { self.fs_status = FsStatus::StaleDepFingerprint { unit: dep.fingerprint.index, }; return Ok(()); } }; // If our dependency edge only requires the rmeta file to be present // then we only need to look at that one output file, otherwise we // need to consider all output files to see if we're out of date. let (dep_path, dep_mtime) = if dep.only_requires_rmeta { dep_mtimes .iter() .find(|(path, _mtime)| { path.extension().and_then(|s| s.to_str()) == Some("rmeta") }) .expect("failed to find rmeta") } else { match dep_mtimes.iter().max_by_key(|kv| kv.1) { Some(dep_mtime) => dep_mtime, // If our dependencies is up to date and has no filesystem // interactions, then we can move on to the next dependency. None => continue, } }; debug!( "max dep mtime for {:?} is {:?} {}", pkg_root, dep_path, dep_mtime ); // If the dependency is newer than our own output then it was // recompiled previously. We transitively become stale ourselves in // that case, so bail out. // // Note that this comparison should probably be `>=`, not `>`, but // for a discussion of why it's `>` see the discussion about #5918 // below in `find_stale`. if dep_mtime > max_mtime { info!( "dependency on `{}` is newer than we are {} > {} {:?}", dep.name, dep_mtime, max_mtime, pkg_root ); self.fs_status = FsStatus::StaleDependency { unit: dep.fingerprint.index, dep_mtime: *dep_mtime, max_mtime: *max_mtime, }; return Ok(()); } } // If we reached this far then all dependencies are up to date. Check // all our `LocalFingerprint` information to see if we have any stale // files for this package itself. If we do find something log a helpful // message and bail out so we stay stale. for local in self.local.get_mut().unwrap().iter() { if let Some(item) = local.find_stale_item( mtime_cache, checksum_cache, pkg, build_root, cargo_exe, gctx, )? { item.log(); self.fs_status = FsStatus::StaleItem(item); return Ok(()); } } // Everything was up to date! Record such. self.fs_status = FsStatus::UpToDate { mtimes }; debug!("filesystem up-to-date {:?}", pkg_root); Ok(()) } } impl hash::Hash for Fingerprint { fn hash(&self, h: &mut H) { let Fingerprint { rustc, ref features, ref declared_features, target, path, profile, ref deps, ref local, config, compile_kind, ref rustflags, .. } = *self; let local = local.lock().unwrap(); ( rustc, features, declared_features, target, path, profile, &*local, config, compile_kind, rustflags, ) .hash(h); h.write_usize(deps.len()); for DepFingerprint { pkg_id, name, public, fingerprint, only_requires_rmeta: _, // static property, no need to hash } in deps { pkg_id.hash(h); name.hash(h); public.hash(h); // use memoized dep hashes to avoid exponential blowup h.write_u64(fingerprint.hash_u64()); } } } impl DepFingerprint { fn new( build_runner: &mut BuildRunner<'_, '_>, parent: &Unit, dep: &UnitDep, ) -> CargoResult { let fingerprint = calculate(build_runner, &dep.unit)?; // We need to be careful about what we hash here. We have a goal of // supporting renaming a project directory and not rebuilding // everything. To do that, however, we need to make sure that the cwd // doesn't make its way into any hashes, and one source of that is the // `SourceId` for `path` packages. // // We already have a requirement that `path` packages all have unique // names (sort of for this same reason), so if the package source is a // `path` then we just hash the name, but otherwise we hash the full // id as it won't change when the directory is renamed. let pkg_id = if dep.unit.pkg.package_id().source_id().is_path() { util::hash_u64(dep.unit.pkg.package_id().name()) } else { util::hash_u64(dep.unit.pkg.package_id()) }; Ok(DepFingerprint { pkg_id, name: dep.extern_crate_name, public: dep.public, fingerprint, only_requires_rmeta: build_runner.only_requires_rmeta(parent, &dep.unit), }) } } impl StaleItem { /// Use the `log` crate to log a hopefully helpful message in diagnosing /// what file is considered stale and why. This is intended to be used in /// conjunction with `CARGO_LOG` to determine why Cargo is recompiling /// something. Currently there's no user-facing usage of this other than /// that. fn log(&self) { match self { StaleItem::MissingFile { path } => { info!("stale: missing {:?}", path); } StaleItem::UnableToReadFile { path } => { info!("stale: unable to read {:?}", path); } StaleItem::FailedToReadMetadata { path } => { info!("stale: couldn't read metadata {:?}", path); } StaleItem::ChangedFile { reference, reference_mtime, stale, stale_mtime, } => { info!("stale: changed {:?}", stale); info!(" (vs) {:?}", reference); info!(" {:?} < {:?}", reference_mtime, stale_mtime); } StaleItem::FileSizeChanged { path, new_size, old_size, } => { info!("stale: changed {:?}", path); info!("prior file size {old_size}"); info!(" new file size {new_size}"); } StaleItem::ChangedChecksum { source, stored_checksum, new_checksum, } => { info!("stale: changed {:?}", source); info!("prior checksum {stored_checksum}"); info!(" new checksum {new_checksum}"); } StaleItem::MissingChecksum { path } => { info!("stale: no prior checksum {:?}", path); } StaleItem::ChangedEnv { var, previous, current, } => { info!("stale: changed env {:?}", var); info!(" {:?} != {:?}", previous, current); } } } } /// Calculates the fingerprint for a [`Unit`]. /// /// This fingerprint is used by Cargo to learn about when information such as: /// /// * A non-path package changes (changes version, changes revision, etc). /// * Any dependency changes /// * The compiler changes /// * The set of features a package is built with changes /// * The profile a target is compiled with changes (e.g., opt-level changes) /// * Any other compiler flags change that will affect the result /// /// Information like file modification time is only calculated for path /// dependencies. fn calculate(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult> { // This function is slammed quite a lot, so the result is memoized. if let Some(s) = build_runner.fingerprints.get(unit) { return Ok(Arc::clone(s)); } let mut fingerprint = if unit.mode.is_run_custom_build() { calculate_run_custom_build(build_runner, unit)? } else if unit.mode.is_doc_test() { panic!("doc tests do not fingerprint"); } else { calculate_normal(build_runner, unit)? }; // After we built the initial `Fingerprint` be sure to update the // `fs_status` field of it. let build_root = build_root(build_runner); let cargo_exe = build_runner.bcx.gctx.cargo_exe()?; fingerprint.check_filesystem( &mut build_runner.mtime_cache, &mut build_runner.checksum_cache, &unit.pkg, &build_root, cargo_exe, build_runner.bcx.gctx, )?; let fingerprint = Arc::new(fingerprint); build_runner .fingerprints .insert(unit.clone(), Arc::clone(&fingerprint)); Ok(fingerprint) } /// Calculate a fingerprint for a "normal" unit, or anything that's not a build /// script. This is an internal helper of [`calculate`], don't call directly. fn calculate_normal( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult { let deps = { // Recursively calculate the fingerprint for all of our dependencies. // // Skip fingerprints of binaries because they don't actually induce a // recompile, they're just dependencies in the sense that they need to be // built. The only exception here are artifact dependencies, // which is an actual dependency that needs a recompile. // // Create Vec since mutable build_runner is needed in closure. let deps = Vec::from(build_runner.unit_deps(unit)); let mut deps = deps .into_iter() .filter(|dep| !dep.unit.target.is_bin() || dep.unit.artifact.is_true()) .map(|dep| DepFingerprint::new(build_runner, unit, &dep)) .collect::>>()?; deps.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id)); deps }; // Afterwards calculate our own fingerprint information. let build_root = build_root(build_runner); let is_any_doc_gen = unit.mode.is_doc() || unit.mode.is_doc_scrape(); let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo; let local = if is_any_doc_gen && !rustdoc_depinfo_enabled { // rustdoc does not have dep-info files. let fingerprint = pkg_fingerprint(build_runner.bcx, &unit.pkg).with_context(|| { format!( "failed to determine package fingerprint for documenting {}", unit.pkg ) })?; vec![LocalFingerprint::Precalculated(fingerprint)] } else { let dep_info = dep_info_loc(build_runner, unit); let dep_info = dep_info.strip_prefix(&build_root).unwrap().to_path_buf(); vec![LocalFingerprint::CheckDepInfo { dep_info, checksum: build_runner.bcx.gctx.cli_unstable().checksum_freshness, }] }; // Figure out what the outputs of our unit is, and we'll be storing them // into the fingerprint as well. let outputs = build_runner .outputs(unit)? .iter() .filter(|output| { !matches!( output.flavor, FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom ) }) .map(|output| output.path.clone()) .collect(); // Fill out a bunch more information that we'll be tracking typically // hashed to take up less space on disk as we just need to know when things // change. let extra_flags = if unit.mode.is_doc() || unit.mode.is_doc_scrape() { &unit.rustdocflags } else { &unit.rustflags } .to_vec(); let profile_hash = util::hash_u64(( &unit.profile, unit.mode, build_runner.bcx.extra_args_for(unit), build_runner.lto[unit], unit.pkg.manifest().lint_rustflags(), )); let mut config = StableHasher::new(); let linker = if unit.target.for_host() && !build_runner.bcx.gctx.target_applies_to_host()? { build_runner.compilation.host_linker() } else { build_runner.compilation.target_linker(unit.kind) }; if let Some(linker) = linker { linker.hash(&mut config); } if unit.mode.is_doc() && build_runner.bcx.gctx.cli_unstable().rustdoc_map { if let Ok(map) = build_runner.bcx.gctx.doc_extern_map() { map.hash(&mut config); } } if let Some(allow_features) = &build_runner.bcx.gctx.cli_unstable().allow_features { allow_features.hash(&mut config); } // -Zno-embed-metadata changes how all units are compiled, and it also changes how we tell // rustc to link to deps using `--extern`. If it changes, we should rebuild everything. build_runner .bcx .gctx .cli_unstable() .no_embed_metadata .hash(&mut config); let compile_kind = unit.kind.fingerprint_hash(); let mut declared_features = unit.pkg.summary().features().keys().collect::>(); declared_features.sort(); // to avoid useless rebuild if the user orders it's features // differently Ok(Fingerprint { rustc: util::hash_u64(&build_runner.bcx.rustc().verbose_version), target: util::hash_u64(&unit.target), profile: profile_hash, // Note that .0 is hashed here, not .1 which is the cwd. That doesn't // actually affect the output artifact so there's no need to hash it. path: util::hash_u64(path_args(build_runner.bcx.ws, unit).0), features: format!("{:?}", unit.features), declared_features: format!("{declared_features:?}"), deps, local: Mutex::new(local), memoized_hash: Mutex::new(None), config: Hasher::finish(&config), compile_kind, index: build_runner.bcx.unit_to_index[unit], rustflags: extra_flags, fs_status: FsStatus::Stale, outputs, }) } /// Calculate a fingerprint for an "execute a build script" unit. This is an /// internal helper of [`calculate`], don't call directly. fn calculate_run_custom_build( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult { assert!(unit.mode.is_run_custom_build()); // Using the `BuildDeps` information we'll have previously parsed and // inserted into `build_explicit_deps` built an initial snapshot of the // `LocalFingerprint` list for this build script. If we previously executed // the build script this means we'll be watching files and env vars. // Otherwise if we haven't previously executed it we'll just start watching // the whole crate. let (gen_local, overridden) = build_script_local_fingerprints(build_runner, unit)?; let deps = &build_runner.build_explicit_deps[unit]; let local = (gen_local)( deps, Some(&|| { const IO_ERR_MESSAGE: &str = "\ An I/O error happened. Please make sure you can access the file. By default, if your project contains a build script, cargo scans all files in it to determine whether a rebuild is needed. If you don't expect to access the file, specify `rerun-if-changed` in your build script. See https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed for more information."; pkg_fingerprint(build_runner.bcx, &unit.pkg).map_err(|err| { let mut message = format!("failed to determine package fingerprint for build script for {}", unit.pkg); if err.root_cause().is::() { message = format!("{}\n{}", message, IO_ERR_MESSAGE) } err.context(message) }) }), )? .unwrap(); let output = deps.build_script_output.clone(); // Include any dependencies of our execution, which is typically just the // compilation of the build script itself. (if the build script changes we // should be rerun!). Note though that if we're an overridden build script // we have no dependencies so no need to recurse in that case. let deps = if overridden { // Overridden build scripts don't need to track deps. vec![] } else { // Create Vec since mutable build_runner is needed in closure. let deps = Vec::from(build_runner.unit_deps(unit)); deps.into_iter() .map(|dep| DepFingerprint::new(build_runner, unit, &dep)) .collect::>>()? }; let rustflags = unit.rustflags.to_vec(); Ok(Fingerprint { local: Mutex::new(local), rustc: util::hash_u64(&build_runner.bcx.rustc().verbose_version), deps, outputs: if overridden { Vec::new() } else { vec![output] }, rustflags, index: build_runner.bcx.unit_to_index[unit], // Most of the other info is blank here as we don't really include it // in the execution of the build script, but... this may be a latent // bug in Cargo. ..Fingerprint::new() }) } /// Get ready to compute the [`LocalFingerprint`] values /// for a [`RunCustomBuild`] unit. /// /// This function has, what's on the surface, a seriously wonky interface. /// You'll call this function and it'll return a closure and a boolean. The /// boolean is pretty simple in that it indicates whether the `unit` has been /// overridden via `.cargo/config.toml`. The closure is much more complicated. /// /// This closure is intended to capture any local state necessary to compute /// the `LocalFingerprint` values for this unit. It is `Send` and `'static` to /// be sent to other threads as well (such as when we're executing build /// scripts). That deduplication is the rationale for the closure at least. /// /// The arguments to the closure are a bit weirder, though, and I'll apologize /// in advance for the weirdness too. The first argument to the closure is a /// `&BuildDeps`. This is the parsed version of a build script, and when Cargo /// starts up this is cached from previous runs of a build script. After a /// build script executes the output file is reparsed and passed in here. /// /// The second argument is the weirdest, it's *optionally* a closure to /// call [`pkg_fingerprint`]. The `pkg_fingerprint` requires access to /// "source map" located in `Context`. That's very non-`'static` and /// non-`Send`, so it can't be used on other threads, such as when we invoke /// this after a build script has finished. The `Option` allows us to for sure /// calculate it on the main thread at the beginning, and then swallow the bug /// for now where a worker thread after a build script has finished doesn't /// have access. Ideally there would be no second argument or it would be more /// "first class" and not an `Option` but something that can be sent between /// threads. In any case, it's a bug for now. /// /// This isn't the greatest of interfaces, and if there's suggestions to /// improve please do so! /// /// FIXME(#6779) - see all the words above /// /// [`RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild fn build_script_local_fingerprints( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult<( Box< dyn FnOnce( &BuildDeps, Option<&dyn Fn() -> CargoResult>, ) -> CargoResult>> + Send, >, bool, )> { assert!(unit.mode.is_run_custom_build()); // First up, if this build script is entirely overridden, then we just // return the hash of what we overrode it with. This is the easy case! if let Some(fingerprint) = build_script_override_fingerprint(build_runner, unit) { debug!("override local fingerprints deps {}", unit.pkg); return Ok(( Box::new( move |_: &BuildDeps, _: Option<&dyn Fn() -> CargoResult>| { Ok(Some(vec![fingerprint])) }, ), true, // this is an overridden build script )); } // ... Otherwise this is a "real" build script and we need to return a real // closure. Our returned closure classifies the build script based on // whether it prints `rerun-if-*`. If it *doesn't* print this it's where the // magical second argument comes into play, which fingerprints a whole // package. Remember that the fact that this is an `Option` is a bug, but a // longstanding bug, in Cargo. Recent refactorings just made it painfully // obvious. let pkg_root = unit.pkg.root().to_path_buf(); let build_dir = build_root(build_runner); let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?); let calculate = move |deps: &BuildDeps, pkg_fingerprint: Option<&dyn Fn() -> CargoResult>| { if deps.rerun_if_changed.is_empty() && deps.rerun_if_env_changed.is_empty() { match pkg_fingerprint { // FIXME: this is somewhat buggy with respect to docker and // weird filesystems. The `Precalculated` variant // constructed below will, for `path` dependencies, contain // a stringified version of the mtime for the local crate. // This violates one of the things we describe in this // module's doc comment, never hashing mtimes. We should // figure out a better scheme where a package fingerprint // may be a string (like for a registry) or a list of files // (like for a path dependency). Those list of files would // be stored here rather than the mtime of them. Some(f) => { let s = f()?; debug!( "old local fingerprints deps {:?} precalculated={:?}", pkg_root, s ); return Ok(Some(vec![LocalFingerprint::Precalculated(s)])); } None => return Ok(None), } } // Ok so now we're in "new mode" where we can have files listed as // dependencies as well as env vars listed as dependencies. Process // them all here. Ok(Some(local_fingerprints_deps( deps, &build_dir, &pkg_root, &env_config, ))) }; // Note that `false` == "not overridden" Ok((Box::new(calculate), false)) } /// Create a [`LocalFingerprint`] for an overridden build script. /// Returns None if it is not overridden. fn build_script_override_fingerprint( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, ) -> Option { // Build script output is only populated at this stage when it is // overridden. let build_script_outputs = build_runner.build_script_outputs.lock().unwrap(); let metadata = build_runner.get_run_build_script_metadata(unit); // Returns None if it is not overridden. let output = build_script_outputs.get(metadata)?; let s = format!( "overridden build state with hash: {}", util::hash_u64(output) ); Some(LocalFingerprint::Precalculated(s)) } /// Compute the [`LocalFingerprint`] values for a [`RunCustomBuild`] unit for /// non-overridden new-style build scripts only. This is only used when `deps` /// is already known to have a nonempty `rerun-if-*` somewhere. /// /// [`RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild fn local_fingerprints_deps( deps: &BuildDeps, build_root: &Path, pkg_root: &Path, env_config: &Arc>, ) -> Vec { debug!("new local fingerprints deps {:?}", pkg_root); let mut local = Vec::new(); if !deps.rerun_if_changed.is_empty() { // Note that like the module comment above says we are careful to never // store an absolute path in `LocalFingerprint`, so ensure that we strip // absolute prefixes from them. let output = deps .build_script_output .strip_prefix(build_root) .unwrap() .to_path_buf(); let paths = deps .rerun_if_changed .iter() .map(|p| p.strip_prefix(pkg_root).unwrap_or(p).to_path_buf()) .collect(); local.push(LocalFingerprint::RerunIfChanged { output, paths }); } local.extend( deps.rerun_if_env_changed .iter() .map(|s| LocalFingerprint::from_env(s, env_config)), ); local } /// Writes the short fingerprint hash value to `` /// and logs detailed JSON information to `.json`. fn write_fingerprint(loc: &Path, fingerprint: &Fingerprint) -> CargoResult<()> { debug_assert_ne!(fingerprint.rustc, 0); // fingerprint::new().rustc == 0, make sure it doesn't make it to the file system. // This is mostly so outside tools can reliably find out what rust version this file is for, // as we can use the full hash. let hash = fingerprint.hash_u64(); debug!("write fingerprint ({:x}) : {}", hash, loc.display()); paths::write(loc, util::to_hex(hash).as_bytes())?; let json = serde_json::to_string(fingerprint).unwrap(); if cfg!(debug_assertions) { let f: Fingerprint = serde_json::from_str(&json).unwrap(); assert_eq!(f.hash_u64(), hash); } paths::write(&loc.with_extension("json"), json.as_bytes())?; Ok(()) } /// Prepare for work when a package starts to build pub fn prepare_init(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<()> { let new1 = build_runner.files().fingerprint_dir(unit); // Doc tests have no output, thus no fingerprint. if !new1.exists() && !unit.mode.is_doc_test() { paths::create_dir_all(&new1)?; } Ok(()) } /// Returns the location that the dep-info file will show up at /// for the [`Unit`] specified. pub fn dep_info_loc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> PathBuf { build_runner.files().fingerprint_file_path(unit, "dep-") } /// Returns an absolute path that build directory. /// All paths are rewritten to be relative to this. fn build_root(build_runner: &BuildRunner<'_, '_>) -> PathBuf { build_runner.bcx.ws.build_dir().into_path_unlocked() } /// Reads the value from the old fingerprint hash file and compare. /// /// If dirty, it then restores the detailed information /// from the fingerprint JSON file, and provides an rich dirty reason. fn compare_old_fingerprint( unit: &Unit, old_hash_path: &Path, new_fingerprint: &Fingerprint, mtime_on_use: bool, forced: bool, ) -> FingerprintComparison { if mtime_on_use { // update the mtime so other cleaners know we used it let t = FileTime::from_system_time(SystemTime::now()); debug!("mtime-on-use forcing {:?} to {}", old_hash_path, t); paths::set_file_time_no_err(old_hash_path, t); } let compare = _compare_old_fingerprint(old_hash_path, new_fingerprint); match compare.as_ref() { Ok(FingerprintComparison::Fresh) => {} Ok(FingerprintComparison::Dirty { reason }) => { info!( "fingerprint dirty for {}/{:?}/{:?}", unit.pkg, unit.mode, unit.target, ); info!(" dirty: {reason:?}"); } Err(e) => { info!( "fingerprint error for {}/{:?}/{:?}", unit.pkg, unit.mode, unit.target, ); info!(" err: {e:?}"); } } match compare { Ok(FingerprintComparison::Fresh) if forced => FingerprintComparison::Dirty { reason: DirtyReason::Forced, }, Ok(cmp) => cmp, Err(_) => FingerprintComparison::Dirty { reason: DirtyReason::FreshBuild, }, } } fn _compare_old_fingerprint( old_hash_path: &Path, new_fingerprint: &Fingerprint, ) -> CargoResult { let old_fingerprint_short = paths::read(old_hash_path)?; let new_hash = new_fingerprint.hash_u64(); if util::to_hex(new_hash) == old_fingerprint_short && new_fingerprint.fs_status.up_to_date() { return Ok(FingerprintComparison::Fresh); } let old_fingerprint_json = paths::read(&old_hash_path.with_extension("json"))?; let old_fingerprint: Fingerprint = serde_json::from_str(&old_fingerprint_json) .with_context(|| internal("failed to deserialize json"))?; // Fingerprint can be empty after a failed rebuild (see comment in prepare_target). if !old_fingerprint_short.is_empty() { debug_assert_eq!( util::to_hex(old_fingerprint.hash_u64()), old_fingerprint_short ); } let reason = new_fingerprint.compare(&old_fingerprint); Ok(FingerprintComparison::Dirty { reason }) } /// Calculates the fingerprint of a unit thats contains no dep-info files. fn pkg_fingerprint(bcx: &BuildContext<'_, '_>, pkg: &Package) -> CargoResult { let source_id = pkg.package_id().source_id(); let sources = bcx.packages.sources(); let source = sources .get(source_id) .ok_or_else(|| internal("missing package source"))?; source.fingerprint(pkg) } /// The `reference` file is considered as "stale" if any file from `paths` has a newer mtime. fn find_stale_file( mtime_cache: &mut HashMap, checksum_cache: &mut HashMap, reference: &Path, paths: I, use_checksums: bool, ) -> Option where I: IntoIterator)>, P: AsRef, { let reference_mtime = match paths::mtime(reference) { Ok(mtime) => mtime, Err(..) => { return Some(StaleItem::MissingFile { path: reference.to_path_buf(), }); } }; let skippable_dirs = if let Ok(cargo_home) = home::cargo_home() { let skippable_dirs: Vec<_> = ["git", "registry"] .into_iter() .map(|subfolder| cargo_home.join(subfolder)) .collect(); Some(skippable_dirs) } else { None }; for (path, prior_checksum) in paths { let path = path.as_ref(); // Assuming anything in cargo_home/{git, registry} is immutable // (see also #9455 about marking the src directory readonly) which avoids rebuilds when CI // caches $CARGO_HOME/registry/{index, cache} and $CARGO_HOME/git/db across runs, keeping // the content the same but changing the mtime. if let Some(ref skippable_dirs) = skippable_dirs { if skippable_dirs.iter().any(|dir| path.starts_with(dir)) { continue; } } if use_checksums { let Some((file_len, prior_checksum)) = prior_checksum else { return Some(StaleItem::MissingChecksum { path: path.to_path_buf(), }); }; let path_buf = path.to_path_buf(); let path_checksum = match checksum_cache.entry(path_buf) { Entry::Occupied(o) => *o.get(), Entry::Vacant(v) => { let Ok(current_file_len) = fs::metadata(&path).map(|m| m.len()) else { return Some(StaleItem::FailedToReadMetadata { path: path.to_path_buf(), }); }; if current_file_len != file_len { return Some(StaleItem::FileSizeChanged { path: path.to_path_buf(), new_size: current_file_len, old_size: file_len, }); } let Ok(file) = File::open(path) else { return Some(StaleItem::MissingFile { path: path.to_path_buf(), }); }; let Ok(checksum) = Checksum::compute(prior_checksum.algo(), file) else { return Some(StaleItem::UnableToReadFile { path: path.to_path_buf(), }); }; *v.insert(checksum) } }; if path_checksum == prior_checksum { continue; } return Some(StaleItem::ChangedChecksum { source: path.to_path_buf(), stored_checksum: prior_checksum, new_checksum: path_checksum, }); } else { let path_mtime = match mtime_cache.entry(path.to_path_buf()) { Entry::Occupied(o) => *o.get(), Entry::Vacant(v) => { let Ok(mtime) = paths::mtime_recursive(path) else { return Some(StaleItem::MissingFile { path: path.to_path_buf(), }); }; *v.insert(mtime) } }; // TODO: fix #5918. // Note that equal mtimes should be considered "stale". For filesystems with // not much timestamp precision like 1s this is would be a conservative approximation // to handle the case where a file is modified within the same second after // a build starts. We want to make sure that incremental rebuilds pick that up! // // For filesystems with nanosecond precision it's been seen in the wild that // its "nanosecond precision" isn't really nanosecond-accurate. It turns out that // kernels may cache the current time so files created at different times actually // list the same nanosecond precision. Some digging on #5919 picked up that the // kernel caches the current time between timer ticks, which could mean that if // a file is updated at most 10ms after a build starts then Cargo may not // pick up the build changes. // // All in all, an equality check here would be a conservative assumption that, // if equal, files were changed just after a previous build finished. // Unfortunately this became problematic when (in #6484) cargo switch to more accurately // measuring the start time of builds. if path_mtime <= reference_mtime { continue; } return Some(StaleItem::ChangedFile { reference: reference.to_path_buf(), reference_mtime, stale: path.to_path_buf(), stale_mtime: path_mtime, }); } } debug!( "all paths up-to-date relative to {:?} mtime={}", reference, reference_mtime ); None } ================================================ FILE: src/cargo/core/compiler/fingerprint/rustdoc.rs ================================================ use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; use anyhow::Context as _; use cargo_util::paths; use serde::Deserialize; use serde::Serialize; use crate::CargoResult; use crate::core::compiler::BuildRunner; use crate::core::compiler::CompileKind; /// JSON Schema of the [`RustdocFingerprint`] file. #[derive(Debug, Serialize, Deserialize)] struct RustdocFingerprintJson { /// `rustc -vV` verbose version output. pub rustc_vv: String, /// Relative paths to cross crate info JSON files from previous `cargo doc` invocations. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub doc_parts: Vec, } /// Structure used to deal with Rustdoc fingerprinting /// /// This is important because the `.js`/`.html` & `.css` files /// that are generated by Rustc don't have any versioning yet /// (see ). /// Therefore, we can end up with weird bugs and behaviours /// if we mix different versions of these files. /// /// We need to make sure that if there were any previous docs already compiled, /// they were compiled with the same Rustc version that we're currently using. /// Otherwise we must remove the `doc/` folder and compile again forcing a rebuild. #[derive(Debug)] pub struct RustdocFingerprint { /// Path to the fingerprint file. path: PathBuf, /// `rustc -vV` verbose version output for the current session. rustc_vv: String, /// Absolute paths to new cross crate info JSON files generated in the current session. doc_parts: Vec, /// The fingerprint file on disk. on_disk: Option, } impl RustdocFingerprint { /// Checks whether the latest version of rustc used to compile this workspace's docs /// was the same as the one is currently being used in this `cargo doc` call. /// /// In case it's not, /// it takes care of removing the `/doc/` folder /// as well as overwriting the rustdoc fingerprint info. /// This is to guarantee that we won't end up with mixed versions of the `js/html/css` files /// which `rustdoc` autogenerates without any versioning. /// /// Each requested target platform maintains its own fingerprint file. /// That is, if you run `cargo doc` and then `cargo doc --target wasm32-wasip1`, /// you will have two separate fingerprint files: /// /// * `/.rustdoc_fingerprint.json` for host /// * `/wasm32-wasip1/.rustdoc_fingerprint.json` pub fn check_rustdoc_fingerprint(build_runner: &BuildRunner<'_, '_>) -> CargoResult<()> { if build_runner .bcx .gctx .cli_unstable() .skip_rustdoc_fingerprint { return Ok(()); } let new_fingerprint = RustdocFingerprintJson { rustc_vv: build_runner.bcx.rustc().verbose_version.clone(), doc_parts: Vec::new(), }; for kind in &build_runner.bcx.build_config.requested_kinds { check_fingerprint(build_runner, &new_fingerprint, *kind)?; } Ok(()) } /// Creates a new fingerprint with given doc parts paths. pub fn new( build_runner: &BuildRunner<'_, '_>, kind: CompileKind, doc_parts: Vec, ) -> Self { let path = fingerprint_path(build_runner, kind); let rustc_vv = build_runner.bcx.rustc().verbose_version.clone(); let on_disk = load_on_disk(&path); Self { path, rustc_vv, doc_parts, on_disk, } } /// Persists the fingerprint. /// /// The closure will run before persisting the fingerprint, /// and will be given a list of doc parts directories for passing to /// `rustdoc --include-parts-dir`. pub fn persist(&self, exec: F) -> CargoResult<()> where // 1. paths for `--include-parts-dir` F: Fn(&[&Path]) -> CargoResult<()>, { // Dedupe crate with the same name by file stem (which is effectively crate name), // since rustdoc doesn't distinguish different crate versions. // // Rules applied here: // // * If name collides, favor the one selected via CLI over cached ones // (done by the insertion order) let base = self.path.parent().unwrap(); let on_disk_doc_parts: Vec<_> = self .on_disk .iter() .flat_map(|on_disk| { on_disk .doc_parts .iter() // Make absolute so that we can pass to rustdoc .map(|p| base.join(p)) // Doc parts may be selectively cleaned by `cargo clean -p `. // We should stop caching those no-exist. .filter(|p| p.exists()) }) .collect(); let dedup_map = on_disk_doc_parts .iter() .chain(self.doc_parts.iter()) .map(|p| (p.file_stem(), p)) .collect::>(); let mut doc_parts: Vec<_> = dedup_map.into_values().collect(); doc_parts.sort_unstable(); // Prepare args for `rustdoc --include-parts-dir` let doc_parts_dirs: Vec<_> = doc_parts.iter().map(|p| p.parent().unwrap()).collect(); exec(&doc_parts_dirs)?; // Persist with relative paths to the directory where fingerprint file is at. let json = RustdocFingerprintJson { rustc_vv: self.rustc_vv.clone(), doc_parts: doc_parts .iter() .map(|p| p.strip_prefix(base).unwrap_or(p).to_owned()) .collect(), }; paths::write(&self.path, serde_json::to_string(&json)?)?; Ok(()) } /// Checks if the fingerprint is outdated comparing against given doc parts file paths. pub fn is_dirty(&self) -> bool { let Some(on_disk) = self.on_disk.as_ref() else { return true; }; let Some(fingerprint_mtime) = paths::mtime(&self.path).ok() else { return true; }; if self.rustc_vv != on_disk.rustc_vv { return true; } for path in &self.doc_parts { let parts_mtime = match paths::mtime(&path) { Ok(mtime) => mtime, Err(e) => { tracing::debug!("failed to read mtime of {}: {e}", path.display()); return true; } }; if parts_mtime > fingerprint_mtime { return true; } } false } } /// Returns the path to rustdoc fingerprint file for a given [`CompileKind`]. fn fingerprint_path(build_runner: &BuildRunner<'_, '_>, kind: CompileKind) -> PathBuf { build_runner .files() .layout(kind) .build_dir() .root() .join(".rustdoc_fingerprint.json") } /// Checks rustdoc fingerprint file for a given [`CompileKind`]. fn check_fingerprint( build_runner: &BuildRunner<'_, '_>, new_fingerprint: &RustdocFingerprintJson, kind: CompileKind, ) -> CargoResult<()> { let fingerprint_path = fingerprint_path(build_runner, kind); let write_fingerprint = || -> CargoResult<()> { paths::write(&fingerprint_path, serde_json::to_string(new_fingerprint)?) }; let Ok(rustdoc_data) = paths::read(&fingerprint_path) else { // If the fingerprint does not exist, do not clear out the doc // directories. Otherwise this ran into problems where projects // like bootstrap were creating the doc directory before running // `cargo doc` in a way that deleting it would break it. return write_fingerprint(); }; match serde_json::from_str::(&rustdoc_data) { Ok(on_disk_fingerprint) => { if on_disk_fingerprint.rustc_vv == new_fingerprint.rustc_vv { return Ok(()); } else { tracing::debug!( "doc fingerprint changed:\noriginal:\n{}\nnew:\n{}", on_disk_fingerprint.rustc_vv, new_fingerprint.rustc_vv ); } } Err(e) => { tracing::debug!("could not deserialize {:?}: {}", fingerprint_path, e); } }; // Fingerprint does not match, delete the doc directories and write a new fingerprint. tracing::debug!( "fingerprint {:?} mismatch, clearing doc directories", fingerprint_path ); let doc_dir = build_runner .files() .layout(kind) .artifact_dir() .expect("artifact-dir was not locked") .doc(); if doc_dir.exists() { clean_doc(doc_dir)?; } write_fingerprint()?; Ok(()) } /// Loads an on-disk fingerprint JSON file. fn load_on_disk(path: &Path) -> Option { let on_disk = match paths::read(path) { Ok(data) => data, Err(e) => { tracing::debug!("failed to read rustdoc fingerprint at {path:?}: {e}"); return None; } }; match serde_json::from_str::(&on_disk) { Ok(on_disk) => Some(on_disk), Err(e) => { tracing::debug!("could not deserialize {path:?}: {e}"); None } } } fn clean_doc(path: &Path) -> CargoResult<()> { let entries = path .read_dir() .with_context(|| format!("failed to read directory `{}`", path.display()))?; for entry in entries { let entry = entry?; // Don't remove hidden files. Rustdoc does not create them, // but the user might have. if entry .file_name() .to_str() .map_or(false, |name| name.starts_with('.')) { continue; } let path = entry.path(); if entry.file_type()?.is_dir() { paths::remove_dir_all(path)?; } else { paths::remove_file(path)?; } } Ok(()) } ================================================ FILE: src/cargo/core/compiler/future_incompat.rs ================================================ //! Support for [future-incompatible warning reporting][1]. //! //! Here is an overview of how Cargo handles future-incompatible reports. //! //! ## Receive reports from the compiler //! //! When receiving a compiler message during a build, if it is effectively //! a [`FutureIncompatReport`], Cargo gathers and forwards it as a //! `Message::FutureIncompatReport` to the main thread. //! //! To have the correct layout of structures for deserializing a report //! emitted by the compiler, most of structure definitions, for example //! [`FutureIncompatReport`], are copied either partially or entirely from //! [compiler/rustc_errors/src/json.rs][2] in rust-lang/rust repository. //! //! ## Persist reports on disk //! //! When a build comes to an end, by calling [`save_and_display_report`] //! Cargo saves the report on disk, and displays it directly if requested //! via command line or configuration. The information of the on-disk file can //! be found in [`FUTURE_INCOMPAT_FILE`]. //! //! During the persistent process, Cargo will attempt to query the source of //! each package emitting the report, for the sake of providing an upgrade //! information as a solution to fix the incompatibility. //! //! ## Display reports to users //! //! Users can run `cargo report future-incompat` to retrieve a report. This is //! done by [`OnDiskReports::load`]. Cargo simply prints reports to the //! standard output. //! //! [1]: https://doc.rust-lang.org/nightly/cargo/reference/future-incompat-report.html //! [2]: https://github.com/rust-lang/rust/blob/9bb6e60d1f1360234aae90c97964c0fa5524f141/compiler/rustc_errors/src/json.rs#L312-L315 use crate::core::compiler::BuildContext; use crate::core::{Dependency, PackageId, Workspace}; use crate::sources::SourceConfigMap; use crate::sources::source::QueryKind; use crate::util::CargoResult; use crate::util::cache_lock::CacheLockMode; use anyhow::{Context, bail, format_err}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt::Write as _; use std::io::{Read, Write}; use std::task::Poll; pub const REPORT_PREAMBLE: &str = "\ The following warnings were discovered during the build. These warnings are an indication that the packages contain code that will become an error in a future release of Rust. These warnings typically cover changes to close soundness problems, unintended or undocumented behavior, or critical problems that cannot be fixed in a backwards-compatible fashion, and are not expected to be in wide use. Each warning should contain a link for more information on what the warning means and how to resolve it. "; /// Current version of the on-disk format. const ON_DISK_VERSION: u32 = 0; /// The future incompatibility report, emitted by the compiler as a JSON message. #[derive(serde::Deserialize)] pub struct FutureIncompatReport { pub future_incompat_report: Vec, } /// Structure used for collecting reports in-memory. pub struct FutureIncompatReportPackage { pub package_id: PackageId, /// Whether or not this is a local package, or a remote dependency. pub is_local: bool, pub items: Vec, } /// A single future-incompatible warning emitted by rustc. #[derive(Serialize, Deserialize)] pub struct FutureBreakageItem { /// The date at which this lint will become an error. /// Currently unused pub future_breakage_date: Option, /// The original diagnostic emitted by the compiler pub diagnostic: Diagnostic, } /// A diagnostic emitted by the compiler as a JSON message. /// We only care about the 'rendered' field #[derive(Serialize, Deserialize)] pub struct Diagnostic { pub rendered: String, pub level: String, } /// The filename in the top-level `build-dir` directory where we store /// the report const FUTURE_INCOMPAT_FILE: &str = ".future-incompat-report.json"; /// Max number of reports to save on disk. const MAX_REPORTS: usize = 5; /// The structure saved to disk containing the reports. #[derive(Serialize, Deserialize)] pub struct OnDiskReports { /// A schema version number, to handle older cargo's from trying to read /// something that they don't understand. version: u32, /// The report ID to use for the next report to save. next_id: u32, /// Available reports. reports: Vec, } /// A single report for a given compilation session. #[derive(Serialize, Deserialize)] struct OnDiskReport { /// Unique reference to the report for the `--id` CLI flag. id: u32, /// A message describing suggestions for fixing the /// reported issues suggestion_message: String, /// Report, suitable for printing to the console. /// Maps package names to the corresponding report /// We use a `BTreeMap` so that the iteration order /// is stable across multiple runs of `cargo` per_package: BTreeMap, } impl Default for OnDiskReports { fn default() -> OnDiskReports { OnDiskReports { version: ON_DISK_VERSION, next_id: 1, reports: Vec::new(), } } } impl OnDiskReports { /// Saves a new report returning its id pub fn save_report( mut self, ws: &Workspace<'_>, suggestion_message: String, per_package: BTreeMap, ) -> u32 { if let Some(existing_id) = self.has_report(&per_package) { return existing_id; } let report = OnDiskReport { id: self.next_id, suggestion_message, per_package, }; let saved_id = report.id; self.next_id += 1; self.reports.push(report); if self.reports.len() > MAX_REPORTS { self.reports.remove(0); } let on_disk = serde_json::to_vec(&self).unwrap(); if let Err(e) = ws .build_dir() .open_rw_exclusive_create( FUTURE_INCOMPAT_FILE, ws.gctx(), "Future incompatibility report", ) .and_then(|file| { let mut file = file.file(); file.set_len(0)?; file.write_all(&on_disk)?; Ok(()) }) { crate::display_warning_with_error( "failed to write on-disk future incompatible report", &e, &mut ws.gctx().shell(), ); } saved_id } /// Returns the ID of a report if it is already on disk. fn has_report(&self, rendered_per_package: &BTreeMap) -> Option { self.reports .iter() .find(|existing| &existing.per_package == rendered_per_package) .map(|report| report.id) } /// Loads the on-disk reports. pub fn load(ws: &Workspace<'_>) -> CargoResult { let report_file = match ws.build_dir().open_ro_shared( FUTURE_INCOMPAT_FILE, ws.gctx(), "Future incompatible report", ) { Ok(r) => r, Err(e) => { if let Some(io_err) = e.downcast_ref::() { if io_err.kind() == std::io::ErrorKind::NotFound { bail!("no reports are currently available"); } } return Err(e); } }; let mut file_contents = String::new(); report_file .file() .read_to_string(&mut file_contents) .context("failed to read report")?; let on_disk_reports: OnDiskReports = serde_json::from_str(&file_contents).context("failed to load report")?; if on_disk_reports.version != ON_DISK_VERSION { bail!("unable to read reports; reports were saved from a future version of Cargo"); } Ok(on_disk_reports) } /// Returns the most recent report ID. pub fn last_id(&self) -> u32 { self.reports.last().map(|r| r.id).unwrap() } /// Returns an ANSI-styled report pub fn get_report(&self, id: u32, package: Option<&str>) -> CargoResult { let report = self.reports.iter().find(|r| r.id == id).ok_or_else(|| { let available = itertools::join(self.reports.iter().map(|r| r.id), ", "); format_err!( "could not find report with ID {}\n\ Available IDs are: {}", id, available ) })?; let mut to_display = report.suggestion_message.clone(); to_display += "\n"; let package_report = if let Some(package) = package { report .per_package .get(package) .ok_or_else(|| { format_err!( "could not find package with ID `{}`\n Available packages are: {}\n Omit the `--package` flag to display a report for all packages", package, itertools::join(report.per_package.keys(), ", ") ) })? .to_string() } else { report .per_package .values() .cloned() .collect::>() .join("\n") }; to_display += &package_report; Ok(to_display) } } fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMap { let mut report: BTreeMap = BTreeMap::new(); for per_package in per_package_reports { let package_spec = format!( "{}@{}", per_package.package_id.name(), per_package.package_id.version() ); let rendered = report.entry(package_spec).or_default(); rendered.push_str(&format!( "The package `{}` currently triggers the following future incompatibility lints:\n", per_package.package_id )); for item in &per_package.items { rendered.extend( item.diagnostic .rendered .lines() .map(|l| format!("> {}\n", l)), ); } } report } /// Returns a user-readable message explaining which of /// the packages in `package_ids` have updates available. /// This is best-effort - if an error occurs, `None` will be returned. fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option { // This in general ignores all errors since this is opportunistic. let _lock = ws .gctx() .acquire_package_cache_lock(CacheLockMode::DownloadExclusive) .ok()?; // Create a set of updated registry sources. let map = SourceConfigMap::new(ws.gctx()).ok()?; let mut package_ids: BTreeSet<_> = package_ids .iter() .filter(|pkg_id| pkg_id.source_id().is_registry()) .collect(); let source_ids: HashSet<_> = package_ids .iter() .map(|pkg_id| pkg_id.source_id()) .collect(); let mut sources: HashMap<_, _> = source_ids .into_iter() .filter_map(|sid| { let source = map.load(sid, &HashSet::new()).ok()?; Some((sid, source)) }) .collect(); // Query the sources for new versions, mapping `package_ids` into `summaries`. let mut summaries = Vec::new(); while !package_ids.is_empty() { package_ids.retain(|&pkg_id| { let Some(source) = sources.get_mut(&pkg_id.source_id()) else { return false; }; let Ok(dep) = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()) else { return false; }; match source.query_vec(&dep, QueryKind::Exact) { Poll::Ready(Ok(sum)) => { summaries.push((pkg_id, sum)); false } Poll::Ready(Err(_)) => false, Poll::Pending => true, } }); for (_, source) in sources.iter_mut() { source.block_until_ready().ok()?; } } let mut updates = String::new(); for (pkg_id, summaries) in summaries { let mut updated_versions: Vec<_> = summaries .iter() .map(|summary| summary.as_summary().version()) .filter(|version| *version > pkg_id.version()) .collect(); updated_versions.sort(); if !updated_versions.is_empty() { let updated_versions = itertools::join(updated_versions, ", "); write!( updates, " - {} has the following newer versions available: {}", pkg_id, updated_versions ) .unwrap(); } } Some(updates) } /// Writes a future-incompat report to disk, using the per-package /// reports gathered during the build. If requested by the user, /// a message is also displayed in the build output. pub fn save_and_display_report( bcx: &BuildContext<'_, '_>, per_package_future_incompat_reports: &[FutureIncompatReportPackage], ) { let should_display_message = match bcx.gctx.future_incompat_config() { Ok(config) => config.should_display_message(), Err(e) => { crate::display_warning_with_error( "failed to read future-incompat config from disk", &e, &mut bcx.gctx.shell(), ); true } }; if per_package_future_incompat_reports.is_empty() { // Explicitly passing a command-line flag overrides // `should_display_message` from the config file if bcx.build_config.future_incompat_report { drop( bcx.gctx .shell() .note("0 dependencies had future-incompatible warnings"), ); } return; } let current_reports = match OnDiskReports::load(bcx.ws) { Ok(r) => r, Err(e) => { tracing::debug!( "saving future-incompatible reports failed to load current reports: {:?}", e ); OnDiskReports::default() } }; let rendered_report = render_report(per_package_future_incompat_reports); // If the report is already on disk, then it will reuse the same ID, // otherwise prepare for the next ID. let report_id = current_reports .has_report(&rendered_report) .unwrap_or(current_reports.next_id); // Get a list of unique and sorted package name/versions. let package_ids: BTreeSet<_> = per_package_future_incompat_reports .iter() .map(|r| r.package_id) .collect(); let package_vers: Vec<_> = package_ids.iter().map(|pid| pid.to_string()).collect(); let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); let update_message = if !updated_versions.is_empty() { format!( "\ update to a newer version to see if the issue has been fixed{updated_versions}", updated_versions = updated_versions ) } else { String::new() }; let upstream_info = package_ids .iter() .map(|package_id| { let manifest = bcx.packages.get_one(*package_id).unwrap().manifest(); format!( " - {package_spec} - repository: {url} - detailed warning command: `cargo report future-incompatibilities --id {id} --package {package_spec}`", package_spec = format!("{}@{}", package_id.name(), package_id.version()), url = manifest .metadata() .repository .as_deref() .unwrap_or(""), id = report_id, ) }) .collect::>() .join("\n\n"); let all_is_local = per_package_future_incompat_reports .iter() .all(|report| report.is_local); let suggestion_header = "to solve this problem, you can try the following approaches:"; let mut suggestions = Vec::new(); if !all_is_local { if !update_message.is_empty() { suggestions.push(update_message); } suggestions.push(format!( "\ ensure the maintainers know of this problem (e.g. creating a bug report if needed) or even helping with a fix (e.g. by creating a pull request) {upstream_info}" )); suggestions.push( "\ use your own version of the dependency with the `[patch]` section in `Cargo.toml` For more information, see: https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section" .to_owned(), ); } let suggestion_message = if suggestions.is_empty() { String::new() } else { let mut suggestion_message = String::new(); writeln!(&mut suggestion_message, "{suggestion_header}").unwrap(); for suggestion in &suggestions { writeln!( &mut suggestion_message, " - {suggestion}" ) .unwrap(); } suggestion_message }; let saved_report_id = current_reports.save_report(bcx.ws, suggestion_message.clone(), rendered_report); if should_display_message || bcx.build_config.future_incompat_report { use annotate_snippets::*; let mut report = vec![Group::with_title(Level::WARNING.secondary_title(format!( "the following packages contain code that will be rejected by a future \ version of Rust: {}", package_vers.join(", ") )))]; if bcx.build_config.future_incompat_report { for suggestion in &suggestions { report.push(Group::with_title(Level::HELP.secondary_title(suggestion))); } report.push(Group::with_title(Level::NOTE.secondary_title(format!( "this report can be shown with `cargo report \ future-incompatibilities --id {}`", saved_report_id )))); } else if should_display_message { report.push(Group::with_title(Level::NOTE.secondary_title(format!( "to see what the problems were, use the option \ `--future-incompat-report`, or run `cargo report \ future-incompatibilities --id {}`", saved_report_id )))); } drop(bcx.gctx.shell().print_report(&report, false)) } } ================================================ FILE: src/cargo/core/compiler/job_queue/job.rs ================================================ //! See [`Job`] and [`Work`]. use std::fmt; use std::mem; use super::JobState; use crate::core::compiler::fingerprint::DirtyReason; use crate::util::CargoResult; /// Represents a unit of [`Work`] with a [`Freshness`] for caller /// to determine whether to re-execute or not. pub struct Job { work: Work, fresh: Freshness, } /// The basic unit of work. /// /// Each proc should send its description before starting. /// It should send either once or close immediately. pub struct Work { inner: Box) -> CargoResult<()> + Send>, } impl Work { /// Creates a unit of work. pub fn new(f: F) -> Work where F: FnOnce(&JobState<'_, '_>) -> CargoResult<()> + Send + 'static, { Work { inner: Box::new(f) } } /// Creates a unit of work that does nothing. pub fn noop() -> Work { Work::new(|_| Ok(())) } /// Consumes this work by running it. pub fn call(self, tx: &JobState<'_, '_>) -> CargoResult<()> { (self.inner)(tx) } /// Creates a new unit of work that chains `next` after ourself. pub fn then(self, next: Work) -> Work { Work::new(move |state| { self.call(state)?; next.call(state) }) } } impl Job { /// Creates a new job that does nothing. pub fn new_fresh() -> Job { Job { work: Work::noop(), fresh: Freshness::Fresh, } } /// Creates a new job representing a unit of work. pub fn new_dirty(work: Work, dirty_reason: DirtyReason) -> Job { Job { work, fresh: Freshness::Dirty(dirty_reason), } } /// Consumes this job by running it, returning the result of the /// computation. pub fn run(self, state: &JobState<'_, '_>) -> CargoResult<()> { self.work.call(state) } /// Returns whether this job was fresh/dirty, where "fresh" means we're /// likely to perform just some small bookkeeping where "dirty" means we'll /// probably do something slow like invoke rustc. pub fn freshness(&self) -> &Freshness { &self.fresh } /// Chains the given work by putting it in front of our own unit of work. pub fn before(&mut self, next: Work) { let prev = mem::replace(&mut self.work, Work::noop()); self.work = next.then(prev); } /// Chains the given work by putting it after of our own unit of work. pub fn after(&mut self, next: Work) { let prev = mem::replace(&mut self.work, Work::noop()); self.work = prev.then(next); } } impl fmt::Debug for Job { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Job {{ ... }}") } } /// Indication of the freshness of a package. /// /// A fresh package does not necessarily need to be rebuilt (unless a dependency /// was also rebuilt), and a dirty package must always be rebuilt. #[derive(Debug, Clone)] pub enum Freshness { Fresh, Dirty(DirtyReason), } impl Freshness { pub fn is_dirty(&self) -> bool { matches!(self, Freshness::Dirty(_)) } pub fn is_fresh(&self) -> bool { matches!(self, Freshness::Fresh) } } ================================================ FILE: src/cargo/core/compiler/job_queue/job_state.rs ================================================ //! See [`JobState`]. use std::{cell::Cell, marker, sync::Arc}; use cargo_util::ProcessBuilder; use crate::core::compiler::future_incompat::FutureBreakageItem; use crate::core::compiler::locking::LockKey; use crate::core::compiler::timings::SectionTiming; use crate::util::Queue; use crate::{CargoResult, core::compiler::locking::LockManager}; use super::{Artifact, DiagDedupe, Job, JobId, Message}; /// A `JobState` is constructed by `JobQueue::run` and passed to `Job::run`. It includes everything /// necessary to communicate between the main thread and the execution of the job. /// /// The job may execute on either a dedicated thread or the main thread. If the job executes on the /// main thread, the `output` field must be set to prevent a deadlock. pub struct JobState<'a, 'gctx> { /// Channel back to the main thread to coordinate messages and such. /// /// When the `output` field is `Some`, care must be taken to avoid calling `push_bounded` on /// the message queue to prevent a deadlock. messages: Arc>, /// Normally output is sent to the job queue with backpressure. When the job is fresh /// however we need to immediately display the output to prevent a deadlock as the /// output messages are processed on the same thread as they are sent from. `output` /// defines where to output in this case. /// /// Currently the [`Shell`] inside [`GlobalContext`] is wrapped in a `RefCell` and thus can't /// be passed between threads. This means that it isn't possible for multiple output messages /// to be interleaved. In the future, it may be wrapped in a `Mutex` instead. In this case /// interleaving is still prevented as the lock would be held for the whole printing of an /// output message. /// /// [`Shell`]: crate::core::Shell /// [`GlobalContext`]: crate::GlobalContext output: Option<&'a DiagDedupe<'gctx>>, /// The job id that this state is associated with, used when sending /// messages back to the main thread. id: JobId, /// Whether or not we're expected to have a call to `rmeta_produced`. Once /// that method is called this is dynamically set to `false` to prevent /// sending a double message later on. rmeta_required: Cell, /// Manages locks for build units when fine grain locking is enabled. lock_manager: Arc, // Historical versions of Cargo made use of the `'a` argument here, so to // leave the door open to future refactorings keep it here. _marker: marker::PhantomData<&'a ()>, } impl<'a, 'gctx> JobState<'a, 'gctx> { pub(super) fn new( id: JobId, messages: Arc>, output: Option<&'a DiagDedupe<'gctx>>, rmeta_required: bool, lock_manager: Arc, ) -> Self { Self { id, messages, output, rmeta_required: Cell::new(rmeta_required), lock_manager, _marker: marker::PhantomData, } } pub fn running(&self, cmd: &ProcessBuilder) { self.messages.push(Message::Run(self.id, cmd.to_string())); } pub fn stdout(&self, stdout: String) -> CargoResult<()> { if let Some(dedupe) = self.output { writeln!(dedupe.gctx.shell().out(), "{}", stdout)?; } else { self.messages.push_bounded(Message::Stdout(stdout)); } Ok(()) } pub fn stderr(&self, stderr: String) -> CargoResult<()> { if let Some(dedupe) = self.output { let mut shell = dedupe.gctx.shell(); shell.print_ansi_stderr(stderr.as_bytes())?; shell.err().write_all(b"\n")?; } else { self.messages.push_bounded(Message::Stderr(stderr)); } Ok(()) } /// See [`Message::Diagnostic`] and [`Message::WarningCount`]. pub fn emit_diag( &self, level: &str, diag: String, lint: bool, fixable: bool, ) -> CargoResult<()> { if let Some(dedupe) = self.output { let emitted = dedupe.emit_diag(&diag)?; if level == "warning" { self.messages.push(Message::WarningCount { id: self.id, lint, emitted, fixable, }); } } else { self.messages.push_bounded(Message::Diagnostic { id: self.id, level: level.to_string(), diag, lint, fixable, }); } Ok(()) } /// See [`Message::Warning`]. pub fn warning(&self, warning: String) { self.messages.push_bounded(Message::Warning { id: self.id, warning, }); } /// A method used to signal to the coordinator thread that the rmeta file /// for an rlib has been produced. This is only called for some rmeta /// builds when required, and can be called at any time before a job ends. /// This should only be called once because a metadata file can only be /// produced once! pub fn rmeta_produced(&self) { self.rmeta_required.set(false); self.messages .push(Message::Finish(self.id, Artifact::Metadata, Ok(()))); } pub fn lock_exclusive(&self, lock: &LockKey) -> CargoResult<()> { self.lock_manager.lock(lock) } pub fn downgrade_to_shared(&self, lock: &LockKey) -> CargoResult<()> { self.lock_manager.downgrade_to_shared(lock) } pub fn on_section_timing_emitted(&self, section: SectionTiming) { self.messages.push(Message::SectionTiming(self.id, section)); } /// Drives a [`Job`] to finish. This ensures that a [`Message::Finish`] is /// sent even if our job panics. pub(super) fn run_to_finish(self, job: Job) { let mut sender = FinishOnDrop { messages: &self.messages, id: self.id, result: None, }; sender.result = Some(job.run(&self)); // If the `rmeta_required` wasn't consumed but it was set // previously, then we either have: // // 1. The `job` didn't do anything because it was "fresh". // 2. The `job` returned an error and didn't reach the point where // it called `rmeta_produced`. // 3. We forgot to call `rmeta_produced` and there's a bug in Cargo. // // Ruling out the third, the other two are pretty common for 2 // we'll just naturally abort the compilation operation but for 1 // we need to make sure that the metadata is flagged as produced so // send a synthetic message here. if self.rmeta_required.get() && sender.result.as_ref().unwrap().is_ok() { self.messages .push(Message::Finish(self.id, Artifact::Metadata, Ok(()))); } // Use a helper struct with a `Drop` implementation to guarantee // that a `Finish` message is sent even if our job panics. We // shouldn't panic unless there's a bug in Cargo, so we just need // to make sure nothing hangs by accident. struct FinishOnDrop<'a> { messages: &'a Queue, id: JobId, result: Option>, } impl Drop for FinishOnDrop<'_> { fn drop(&mut self) { let result = self .result .take() .unwrap_or_else(|| Err(anyhow::format_err!("worker panicked"))); self.messages .push(Message::Finish(self.id, Artifact::All, result)); } } } pub fn future_incompat_report(&self, report: Vec) { self.messages .push(Message::FutureIncompatReport(self.id, report)); } } ================================================ FILE: src/cargo/core/compiler/job_queue/mod.rs ================================================ //! Management of the interaction between the main `cargo` and all spawned jobs. //! //! ## Overview //! //! This module implements a job queue. A job here represents a unit of work, //! which is roughly a rustc invocation, a build script run, or just a no-op. //! The job queue primarily handles the following things: //! //! * Spawns concurrent jobs. Depending on its [`Freshness`], a job could be //! either executed on a spawned thread or ran on the same thread to avoid //! the threading overhead. //! * Controls the number of concurrency. It allocates and manages [`jobserver`] //! tokens to each spawned off rustc and build scripts. //! * Manages the communication between the main `cargo` process and its //! spawned jobs. Those [`Message`]s are sent over a [`Queue`] shared //! across threads. //! * Schedules the execution order of each [`Job`]. Priorities are determined //! when calling [`JobQueue::enqueue`] to enqueue a job. The scheduling is //! relatively rudimentary and could likely be improved. //! //! A rough outline of building a queue and executing jobs is: //! //! 1. [`JobQueue::new`] to simply create one queue. //! 2. [`JobQueue::enqueue`] to add new jobs onto the queue. //! 3. Consumes the queue and executes all jobs via [`JobQueue::execute`]. //! //! The primary loop happens insides [`JobQueue::execute`], which is effectively //! [`DrainState::drain_the_queue`]. [`DrainState`] is, as its name tells, //! the running state of the job queue getting drained. //! //! ## Jobserver //! //! As of Feb. 2023, Cargo and rustc have a relatively simple jobserver //! relationship with each other. They share a single jobserver amongst what //! is potentially hundreds of threads of work on many-cored systems. //! The jobserver could come from either the environment (e.g., from a `make` //! invocation), or from Cargo creating its own jobserver server if there is no //! jobserver to inherit from. //! //! Cargo wants to complete the build as quickly as possible, fully saturating //! all cores (as constrained by the `-j=N`) parameter. Cargo also must not spawn //! more than N threads of work: the total amount of tokens we have floating //! around must always be limited to N. //! //! It is not really possible to optimally choose which crate should build //! first or last; nor is it possible to decide whether to give an additional //! token to rustc first or rather spawn a new crate of work. The algorithm in //! Cargo prioritizes spawning as many crates (i.e., rustc processes) as //! possible. In short, the jobserver relationship among Cargo and rustc //! processes is **1 `cargo` to N `rustc`**. Cargo knows nothing beyond rustc //! processes in terms of parallelism[^parallel-rustc]. //! //! We integrate with the [jobserver] crate, originating from GNU make //! [POSIX jobserver], to make sure that build scripts which use make to //! build C code can cooperate with us on the number of used tokens and //! avoid overfilling the system we're on. //! //! ## Scheduling //! //! The current scheduling algorithm is not really polished. It is simply based //! on a dependency graph [`DependencyQueue`]. We continue adding nodes onto //! the graph until we finalize it. When the graph gets finalized, it finds the //! sum of the cost of each dependencies of each node, including transitively. //! The sum of dependency cost turns out to be the cost of each given node. //! //! At the time being, the cost is just passed as a fixed placeholder in //! [`JobQueue::enqueue`]. In the future, we could explore more possibilities //! around it. For instance, we start persisting timing information for each //! build somewhere. For a subsequent build, we can look into the historical //! data and perform a PGO-like optimization to prioritize jobs, making a build //! fully pipelined. //! //! ## Message queue //! //! Each spawned thread running a process uses the message queue [`Queue`] to //! send messages back to the main thread (the one running `cargo`). //! The main thread coordinates everything, and handles printing output. //! //! It is important to be careful which messages use [`push`] vs [`push_bounded`]. //! `push` is for priority messages (like tokens, or "finished") where the //! sender shouldn't block. We want to handle those so real work can proceed //! ASAP. //! //! `push_bounded` is only for messages being printed to stdout/stderr. Being //! bounded prevents a flood of messages causing a large amount of memory //! being used. //! //! `push` also avoids blocking which helps avoid deadlocks. For example, when //! the diagnostic server thread is dropped, it waits for the thread to exit. //! But if the thread is blocked on a full queue, and there is a critical //! error, the drop will deadlock. This should be fixed at some point in the //! future. The jobserver thread has a similar problem, though it will time //! out after 1 second. //! //! To access the message queue, each running `Job` is given its own [`JobState`], //! containing everything it needs to communicate with the main thread. //! //! See [`Message`] for all available message kinds. //! //! [^parallel-rustc]: In fact, `jobserver` that Cargo uses also manages the //! allocation of tokens to rustc beyond the implicit token each rustc owns //! (i.e., the ones used for parallel LLVM work and parallel rustc threads). //! See also ["Rust Compiler Development Guide: Parallel Compilation"] //! and [this comment][rustc-codegen] in rust-lang/rust. //! //! ["Rust Compiler Development Guide: Parallel Compilation"]: https://rustc-dev-guide.rust-lang.org/parallel-rustc.html //! [rustc-codegen]: https://github.com/rust-lang/rust/blob/5423745db8b434fcde54888b35f518f00cce00e4/compiler/rustc_codegen_ssa/src/back/write.rs#L1204-L1217 //! [jobserver]: https://docs.rs/jobserver //! [POSIX jobserver]: https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html //! [`push`]: Queue::push //! [`push_bounded`]: Queue::push_bounded mod job; mod job_state; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::fmt::Write as _; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::thread::{self, Scope}; use std::time::Duration; use std::{env, io}; use anyhow::{Context as _, format_err}; use jobserver::{Acquired, HelperThread}; use semver::Version; use tracing::{debug, trace}; pub use self::job::Freshness::{self, Dirty, Fresh}; pub use self::job::{Job, Work}; pub use self::job_state::JobState; use super::BuildContext; use super::BuildRunner; use super::CompileMode; use super::Unit; use super::UnitIndex; use super::custom_build::Severity; use super::timings::SectionTiming; use super::timings::Timings; use crate::core::compiler::descriptive_pkg_name; use crate::core::compiler::future_incompat::{ self, FutureBreakageItem, FutureIncompatReportPackage, }; use crate::core::resolver::ResolveBehavior; use crate::core::{PackageId, Shell, TargetKind}; use crate::util::CargoResult; use crate::util::context::WarningHandling; use crate::util::diagnostic_server::{self, DiagnosticPrinter}; use crate::util::errors::AlreadyPrintedError; use crate::util::machine_message::{self, Message as _}; use crate::util::{self, internal}; use crate::util::{DependencyQueue, GlobalContext, Progress, ProgressStyle, Queue}; /// This structure is backed by the `DependencyQueue` type and manages the /// queueing of compilation steps for each package. Packages enqueue units of /// work and then later on the entire graph is converted to `DrainState` and /// executed. pub struct JobQueue<'gctx> { queue: DependencyQueue, counts: HashMap, timings: Timings<'gctx>, } /// This structure is backed by the `DependencyQueue` type and manages the /// actual compilation step of each package. Packages enqueue units of work and /// then later on the entire graph is processed and compiled. /// /// It is created from `JobQueue` when we have fully assembled the crate graph /// (i.e., all package dependencies are known). struct DrainState<'gctx> { // This is the length of the DependencyQueue when starting out total_units: usize, queue: DependencyQueue, messages: Arc>, /// Diagnostic deduplication support. diag_dedupe: DiagDedupe<'gctx>, /// Count of warnings, used to print a summary after the job succeeds warning_count: HashMap, active: HashMap, compiled: HashSet, documented: HashSet, scraped: HashSet, counts: HashMap, progress: Progress<'gctx>, next_id: u32, timings: Timings<'gctx>, /// Map from unit index to unit, for looking up dependency information. index_to_unit: HashMap, /// Tokens that are currently owned by this Cargo, and may be "associated" /// with a rustc process. They may also be unused, though if so will be /// dropped on the next loop iteration. /// /// Note that the length of this may be zero, but we will still spawn work, /// as we share the implicit token given to this Cargo process with a /// single rustc process. tokens: Vec, /// The list of jobs that we have not yet started executing, but have /// retrieved from the `queue`. We eagerly pull jobs off the main queue to /// allow us to request jobserver tokens pretty early. pending_queue: Vec<(Unit, Job, usize)>, print: DiagnosticPrinter<'gctx>, /// How many jobs we've finished finished: usize, per_package_future_incompat_reports: Vec, } /// Count of warnings, used to print a summary after the job succeeds #[derive(Default, Clone)] pub struct WarningCount { /// total number of warnings pub total: usize, /// number of lint warnings pub lints: usize, /// number of warnings that were suppressed because they /// were duplicates of a previous warning pub duplicates: usize, /// number of fixable warnings set to `NotAllowed` /// if any errors have been seen for the current /// target pub fixable: FixableWarnings, } impl WarningCount { /// If an error is seen this should be called /// to set `fixable` to `NotAllowed` fn disallow_fixable(&mut self) { self.fixable = FixableWarnings::NotAllowed; } /// Checks fixable if warnings are allowed /// fixable warnings are allowed if no /// errors have been seen for the current /// target. If an error was seen `fixable` /// will be `NotAllowed`. fn fixable_allowed(&self) -> bool { match &self.fixable { FixableWarnings::NotAllowed => false, _ => true, } } } /// Used to keep track of how many fixable warnings there are /// and if fixable warnings are allowed #[derive(Default, Copy, Clone)] pub enum FixableWarnings { NotAllowed, #[default] Zero, Positive(usize), } pub struct ErrorsDuringDrain { pub count: usize, } struct ErrorToHandle { error: anyhow::Error, /// This field is true for "interesting" errors and false for "mundane" /// errors. If false, we print the above error only if it's the first one /// encountered so far while draining the job queue. /// /// At most places that an error is propagated, we set this to false to /// avoid scenarios where Cargo might end up spewing tons of redundant error /// messages. For example if an i/o stream got closed somewhere, we don't /// care about individually reporting every thread that it broke; just the /// first is enough. /// /// The exception where `print_always` is true is that we do report every /// instance of a rustc invocation that failed with diagnostics. This /// corresponds to errors from `Message::Finish`. print_always: bool, } impl From for ErrorToHandle where anyhow::Error: From, { fn from(error: E) -> Self { ErrorToHandle { error: anyhow::Error::from(error), print_always: false, } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct JobId(pub u32); impl std::fmt::Display for JobId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } /// Handler for deduplicating diagnostics. struct DiagDedupe<'gctx> { seen: RefCell>, gctx: &'gctx GlobalContext, } impl<'gctx> DiagDedupe<'gctx> { fn new(gctx: &'gctx GlobalContext) -> Self { DiagDedupe { seen: RefCell::new(HashSet::new()), gctx, } } /// Emits a diagnostic message. /// /// Returns `true` if the message was emitted, or `false` if it was /// suppressed for being a duplicate. fn emit_diag(&self, diag: &str) -> CargoResult { let h = util::hash_u64(diag); if !self.seen.borrow_mut().insert(h) { return Ok(false); } let mut shell = self.gctx.shell(); shell.print_ansi_stderr(diag.as_bytes())?; shell.err().write_all(b"\n")?; Ok(true) } } /// Possible artifacts that can be produced by compilations, used as edge values /// in the dependency graph. /// /// As edge values we can have multiple kinds of edges depending on one node, /// for example some units may only depend on the metadata for an rlib while /// others depend on the full rlib. This `Artifact` enum is used to distinguish /// this case and track the progress of compilations as they proceed. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] enum Artifact { /// A generic placeholder for "depends on everything run by a step" and /// means that we can't start the next compilation until the previous has /// finished entirely. All, /// A node indicating that we only depend on the metadata of a compilation, /// but the compilation is typically also producing an rlib. We can start /// our step, however, before the full rlib is available. Metadata, } enum Message { Run(JobId, String), Stdout(String), Stderr(String), // This is for general stderr output from subprocesses Diagnostic { id: JobId, level: String, diag: String, lint: bool, fixable: bool, }, // This handles duplicate output that is suppressed, for showing // only a count of duplicate messages instead WarningCount { id: JobId, lint: bool, emitted: bool, fixable: bool, }, // This is for warnings generated by Cargo's interpretation of the // subprocess output, e.g. scrape-examples prints a warning if a // unit fails to be scraped Warning { id: JobId, warning: String, }, FixDiagnostic(diagnostic_server::Message), Token(io::Result), Finish(JobId, Artifact, CargoResult<()>), FutureIncompatReport(JobId, Vec), SectionTiming(JobId, SectionTiming), } impl<'gctx> JobQueue<'gctx> { pub fn new(bcx: &BuildContext<'_, 'gctx>) -> JobQueue<'gctx> { JobQueue { queue: DependencyQueue::new(), counts: HashMap::new(), timings: Timings::new(bcx), } } pub fn enqueue( &mut self, build_runner: &BuildRunner<'_, 'gctx>, unit: &Unit, job: Job, ) -> CargoResult<()> { let dependencies = build_runner.unit_deps(unit); let mut queue_deps = dependencies .iter() .filter(|dep| { // Binaries aren't actually needed to *compile* tests, just to run // them, so we don't include this dependency edge in the job graph. // But we shouldn't filter out dependencies being scraped for Rustdoc. (!dep.unit.target.is_test() && !dep.unit.target.is_bin()) || dep.unit.artifact.is_true() || dep.unit.mode.is_doc_scrape() }) .map(|dep| { // Handle the case here where our `unit -> dep` dependency may // only require the metadata, not the full compilation to // finish. Use the tables in `build_runner` to figure out what // kind of artifact is associated with this dependency. let artifact = if build_runner.only_requires_rmeta(unit, &dep.unit) { Artifact::Metadata } else { Artifact::All }; (dep.unit.clone(), artifact) }) .collect::>(); // This is somewhat tricky, but we may need to synthesize some // dependencies for this target if it requires full upstream // compilations to have completed. Because of pipelining, some // dependency edges may be `Metadata` due to the above clause (as // opposed to everything being `All`). For example consider: // // a (binary) // └ b (lib) // └ c (lib) // // Here the dependency edge from B to C will be `Metadata`, and the // dependency edge from A to B will be `All`. For A to be compiled, // however, it currently actually needs the full rlib of C. This means // that we need to synthesize a dependency edge for the dependency graph // from A to C. That's done here. // // This will walk all dependencies of the current target, and if any of // *their* dependencies are `Metadata` then we depend on the `All` of // the target as well. This should ensure that edges changed to // `Metadata` propagate upwards `All` dependencies to anything that // transitively contains the `Metadata` edge. if unit.requires_upstream_objects() { for dep in dependencies { depend_on_deps_of_deps(build_runner, &mut queue_deps, dep.unit.clone()); } fn depend_on_deps_of_deps( build_runner: &BuildRunner<'_, '_>, deps: &mut HashMap, unit: Unit, ) { for dep in build_runner.unit_deps(&unit) { if deps.insert(dep.unit.clone(), Artifact::All).is_none() { depend_on_deps_of_deps(build_runner, deps, dep.unit.clone()); } } } } // For now we use a fixed placeholder value for the cost of each unit, but // in the future this could be used to allow users to provide hints about // relative expected costs of units, or this could be automatically set in // a smarter way using timing data from a previous compilation. self.queue.queue(unit.clone(), job, queue_deps, 100); *self.counts.entry(unit.pkg.package_id()).or_insert(0) += 1; Ok(()) } /// Executes all jobs necessary to build the dependency graph. /// /// This function will spawn off `config.jobs()` workers to build all of the /// necessary dependencies, in order. Freshness is propagated as far as /// possible along each dependency chain. #[tracing::instrument(skip_all)] pub fn execute(mut self, build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> { self.queue.queue_finished(); let progress = Progress::with_style("Building", ProgressStyle::Ratio, build_runner.bcx.gctx); let state = DrainState { total_units: self.queue.len(), queue: self.queue, // 100 here is somewhat arbitrary. It is a few screenfulls of // output, and hopefully at most a few megabytes of memory for // typical messages. If you change this, please update the test // caching_large_output, too. messages: Arc::new(Queue::new(100)), diag_dedupe: DiagDedupe::new(build_runner.bcx.gctx), warning_count: HashMap::new(), active: HashMap::new(), compiled: HashSet::new(), documented: HashSet::new(), scraped: HashSet::new(), counts: self.counts, progress, next_id: 0, timings: self.timings, index_to_unit: build_runner .bcx .unit_to_index .iter() .map(|(unit, &index)| (index, unit.clone())) .collect(), tokens: Vec::new(), pending_queue: Vec::new(), print: DiagnosticPrinter::new( build_runner.bcx.gctx, &build_runner.bcx.rustc().workspace_wrapper, ), finished: 0, per_package_future_incompat_reports: Vec::new(), }; // Create a helper thread for acquiring jobserver tokens let messages = state.messages.clone(); let helper = build_runner .jobserver .clone() .into_helper_thread(move |token| { messages.push(Message::Token(token)); }) .context("failed to create helper thread for jobserver management")?; // Create a helper thread to manage the diagnostics for rustfix if // necessary. let messages = state.messages.clone(); // It is important that this uses `push` instead of `push_bounded` for // now. If someone wants to fix this to be bounded, the `drop` // implementation needs to be changed to avoid possible deadlocks. let _diagnostic_server = build_runner .bcx .build_config .rustfix_diagnostic_server .borrow_mut() .take() .map(move |srv| srv.start(move |msg| messages.push(Message::FixDiagnostic(msg)))); thread::scope( move |scope| match state.drain_the_queue(build_runner, scope, &helper) { Some(err) => Err(err), None => Ok(()), }, ) } } impl<'gctx> DrainState<'gctx> { fn spawn_work_if_possible<'s>( &mut self, build_runner: &mut BuildRunner<'_, '_>, jobserver_helper: &HelperThread, scope: &'s Scope<'s, '_>, ) -> CargoResult<()> { // Dequeue as much work as we can, learning about everything // possible that can run. Note that this is also the point where we // start requesting job tokens. Each job after the first needs to // request a token. while let Some((unit, job, priority)) = self.queue.dequeue() { // We want to keep the pieces of work in the `pending_queue` sorted // by their priorities, and insert the current job at its correctly // sorted position: following the lower priority jobs, and the ones // with the same priority (since they were dequeued before the // current one, we also keep that relation). let idx = self .pending_queue .partition_point(|&(_, _, p)| p <= priority); self.pending_queue.insert(idx, (unit, job, priority)); if self.active.len() + self.pending_queue.len() > 1 { jobserver_helper.request_token(); } } // Now that we've learned of all possible work that we can execute // try to spawn it so long as we've got a jobserver token which says // we're able to perform some parallel work. // The `pending_queue` is sorted in ascending priority order, and we // remove items from its end to schedule the highest priority items // sooner. while self.has_extra_tokens() && !self.pending_queue.is_empty() { let (unit, job, _) = self.pending_queue.pop().unwrap(); *self.counts.get_mut(&unit.pkg.package_id()).unwrap() -= 1; // Print out some nice progress information. // NOTE: An error here will drop the job without starting it. // That should be OK, since we want to exit as soon as // possible during an error. self.note_working_on( build_runner.bcx.gctx, build_runner.bcx.ws.root(), &unit, job.freshness(), )?; self.run(&unit, job, build_runner, scope); } Ok(()) } fn has_extra_tokens(&self) -> bool { self.active.len() < self.tokens.len() + 1 } fn handle_event( &mut self, build_runner: &mut BuildRunner<'_, '_>, event: Message, ) -> Result<(), ErrorToHandle> { let warning_handling = build_runner.bcx.gctx.warning_handling()?; match event { Message::Run(id, cmd) => { build_runner .bcx .gctx .shell() .verbose(|c| c.status("Running", &cmd))?; self.timings .unit_start(build_runner, id, self.active[&id].clone()); } Message::Stdout(out) => { writeln!(build_runner.bcx.gctx.shell().out(), "{}", out)?; } Message::Stderr(err) => { let mut shell = build_runner.bcx.gctx.shell(); shell.print_ansi_stderr(err.as_bytes())?; shell.err().write_all(b"\n")?; } Message::Diagnostic { id, level, diag, lint, fixable, } => { let emitted = self.diag_dedupe.emit_diag(&diag)?; if level == "warning" { self.bump_warning_count(id, lint, emitted, fixable); } if level == "error" { let count = self.warning_count.entry(id).or_default(); // If there is an error, the `cargo fix` message should not show count.disallow_fixable(); } } Message::Warning { id, warning } => { if warning_handling != WarningHandling::Allow { build_runner.bcx.gctx.shell().warn(warning)?; } let lint = false; let emitted = true; let fixable = false; self.bump_warning_count(id, lint, emitted, fixable); } Message::WarningCount { id, lint, emitted, fixable, } => { self.bump_warning_count(id, lint, emitted, fixable); } Message::FixDiagnostic(msg) => { self.print.print(&msg)?; } Message::Finish(id, artifact, mut result) => { let unit = match artifact { // If `id` has completely finished we remove it // from the `active` map ... Artifact::All => { trace!("end: {:?}", id); self.finished += 1; let unit = self.active.remove(&id).unwrap(); // An error could add an entry for a `Unit` // with 0 warnings but having fixable // warnings be disallowed let count = self .warning_count .get(&id) .filter(|count| 0 < count.total) .cloned(); if let Some(count) = count { self.report_warning_count( build_runner, &unit, &count, &build_runner.bcx.rustc().workspace_wrapper, warning_handling, ); let stop_on_warnings = warning_handling == WarningHandling::Deny && 0 < count.lints && !build_runner.bcx.build_config.keep_going; if stop_on_warnings { result = Err(anyhow::format_err!( "warnings are denied by `build.warnings` configuration" )) } } unit } // ... otherwise if it hasn't finished we leave it // in there as we'll get another `Finish` later on. Artifact::Metadata => { trace!("end (meta): {:?}", id); self.active[&id].clone() } }; debug!("end ({:?}): {:?}", unit, result); match result { Ok(()) => self.finish(id, &unit, artifact, build_runner)?, Err(_) if build_runner.bcx.unit_can_fail_for_docscraping(&unit) => { build_runner .failed_scrape_units .lock() .unwrap() .insert(build_runner.files().metadata(&unit).unit_id()); self.queue.finish(&unit, &artifact); } Err(error) => { let show_warnings = true; self.emit_log_messages(&unit, build_runner, show_warnings)?; self.back_compat_notice(build_runner, &unit)?; return Err(ErrorToHandle { error, print_always: true, }); } } } Message::FutureIncompatReport(id, items) => { let unit = &self.active[&id]; let package_id = unit.pkg.package_id(); let is_local = unit.is_local(); self.per_package_future_incompat_reports .push(FutureIncompatReportPackage { package_id, is_local, items, }); } Message::Token(acquired_token) => { let token = acquired_token.context("failed to acquire jobserver token")?; self.tokens.push(token); } Message::SectionTiming(id, section) => { self.timings.unit_section_timing(build_runner, id, §ion); } } Ok(()) } // This will also tick the progress bar as appropriate fn wait_for_events(&mut self) -> Vec { // Drain all events at once to avoid displaying the progress bar // unnecessarily. If there's no events we actually block waiting for // an event, but we keep a "heartbeat" going to allow `record_cpu` // to run above to calculate CPU usage over time. To do this we // listen for a message with a timeout, and on timeout we run the // previous parts of the loop again. let mut events = self.messages.try_pop_all(); if events.is_empty() { loop { self.tick_progress(); self.tokens.truncate(self.active.len() - 1); match self.messages.pop(Duration::from_millis(500)) { Some(message) => { events.push(message); break; } None => continue, } } } events } /// This is the "main" loop, where Cargo does all work to run the /// compiler. /// /// This returns an Option to prevent the use of `?` on `Result` types /// because it is important for the loop to carefully handle errors. fn drain_the_queue<'s>( mut self, build_runner: &mut BuildRunner<'_, '_>, scope: &'s Scope<'s, '_>, jobserver_helper: &HelperThread, ) -> Option { trace!("queue: {:#?}", self.queue); // Iteratively execute the entire dependency graph. Each turn of the // loop starts out by scheduling as much work as possible (up to the // maximum number of parallel jobs we have tokens for). A local queue // is maintained separately from the main dependency queue as one // dequeue may actually dequeue quite a bit of work (e.g., 10 binaries // in one package). // // After a job has finished we update our internal state if it was // successful and otherwise wait for pending work to finish if it failed // and then immediately return (or keep going, if requested by the build // config). let mut errors = ErrorsDuringDrain { count: 0 }; // CAUTION! Do not use `?` or break out of the loop early. Every error // must be handled in such a way that the loop is still allowed to // drain event messages. loop { if errors.count == 0 || build_runner.bcx.build_config.keep_going { if let Err(e) = self.spawn_work_if_possible(build_runner, jobserver_helper, scope) { self.handle_error(&mut build_runner.bcx.gctx.shell(), &mut errors, e); } } // If after all that we're not actually running anything then we're // done! if self.active.is_empty() { break; } // And finally, before we block waiting for the next event, drop any // excess tokens we may have accidentally acquired. Due to how our // jobserver interface is architected we may acquire a token that we // don't actually use, and if this happens just relinquish it back // to the jobserver itself. for event in self.wait_for_events() { if let Err(event_err) = self.handle_event(build_runner, event) { self.handle_error(&mut build_runner.bcx.gctx.shell(), &mut errors, event_err); } } } self.progress.clear(); let profile_name = build_runner.bcx.build_config.requested_profile; // NOTE: this may be a bit inaccurate, since this may not display the // profile for what was actually built. Profile overrides can change // these settings, and in some cases different targets are built with // different profiles. To be accurate, it would need to collect a // list of Units built, and maybe display a list of the different // profiles used. However, to keep it simple and compatible with old // behavior, we just display what the base profile is. let profile = build_runner.bcx.profiles.base_profile(); let mut opt_type = String::from(if profile.opt_level.as_str() == "0" { "unoptimized" } else { "optimized" }); if profile.debuginfo.is_turned_on() { opt_type += " + debuginfo"; } let time_elapsed = util::elapsed(build_runner.bcx.gctx.creation_time().elapsed()); if let Err(e) = self .timings .finished(build_runner, &errors.to_error()) .context("failed to render timing report") { self.handle_error(&mut build_runner.bcx.gctx.shell(), &mut errors, e); } if build_runner.bcx.build_config.emit_json() { let mut shell = build_runner.bcx.gctx.shell(); let msg = machine_message::BuildFinished { success: errors.count == 0, } .to_json_string(); if let Err(e) = writeln!(shell.out(), "{}", msg) { self.handle_error(&mut shell, &mut errors, e); } } if let Some(error) = errors.to_error() { // Any errors up to this point have already been printed via the // `display_error` inside `handle_error`. Some(anyhow::Error::new(AlreadyPrintedError::new(error))) } else if self.queue.is_empty() && self.pending_queue.is_empty() { let profile_link = build_runner.bcx.gctx.shell().err_hyperlink( "https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles", ); let message = format!( "{profile_link}`{profile_name}` profile [{opt_type}]{profile_link:#} target(s) in {time_elapsed}", ); // It doesn't really matter if this fails. let _ = build_runner.bcx.gctx.shell().status("Finished", message); future_incompat::save_and_display_report( build_runner.bcx, &self.per_package_future_incompat_reports, ); None } else { debug!("queue: {:#?}", self.queue); Some(internal("finished with jobs still left in the queue")) } } fn handle_error( &mut self, shell: &mut Shell, err_state: &mut ErrorsDuringDrain, new_err: impl Into, ) { let new_err = new_err.into(); if new_err.print_always || err_state.count == 0 { crate::display_error(&new_err.error, shell); if err_state.count == 0 && !self.active.is_empty() { self.progress.indicate_error(); let _ = shell.warn("build failed, waiting for other jobs to finish..."); } err_state.count += 1; } else { tracing::warn!("{:?}", new_err.error); } } // This also records CPU usage and marks concurrency; we roughly want to do // this as often as we spin on the events receiver (at least every 500ms or // so). fn tick_progress(&mut self) { // Record some timing information if `--timings` is enabled, and // this'll end up being a noop if we're not recording this // information. self.timings.record_cpu(); let active_names = self .active .values() .map(|u| self.name_for_progress(u)) .collect::>(); let _ = self.progress.tick_now( self.finished, self.total_units, &format!(": {}", active_names.join(", ")), ); } fn name_for_progress(&self, unit: &Unit) -> String { let pkg_name = unit.pkg.name(); let target_name = unit.target.name(); match unit.mode { CompileMode::Doc { .. } => format!("{}(doc)", pkg_name), CompileMode::RunCustomBuild => format!("{}(build)", pkg_name), CompileMode::Test | CompileMode::Check { test: true } => match unit.target.kind() { TargetKind::Lib(_) => format!("{}(test)", target_name), TargetKind::CustomBuild => panic!("cannot test build script"), TargetKind::Bin => format!("{}(bin test)", target_name), TargetKind::Test => format!("{}(test)", target_name), TargetKind::Bench => format!("{}(bench)", target_name), TargetKind::ExampleBin | TargetKind::ExampleLib(_) => { format!("{}(example test)", target_name) } }, _ => match unit.target.kind() { TargetKind::Lib(_) => pkg_name.to_string(), TargetKind::CustomBuild => format!("{}(build.rs)", pkg_name), TargetKind::Bin => format!("{}(bin)", target_name), TargetKind::Test => format!("{}(test)", target_name), TargetKind::Bench => format!("{}(bench)", target_name), TargetKind::ExampleBin | TargetKind::ExampleLib(_) => { format!("{}(example)", target_name) } }, } } /// Executes a job. /// /// Fresh jobs block until finished (which should be very fast!), Dirty /// jobs will spawn a thread in the background and return immediately. fn run<'s>( &mut self, unit: &Unit, job: Job, build_runner: &BuildRunner<'_, '_>, scope: &'s Scope<'s, '_>, ) { let id = JobId(self.next_id); self.next_id = self.next_id.checked_add(1).unwrap(); debug!("start {}: {:?}", id, unit); assert!(self.active.insert(id, unit.clone()).is_none()); let messages = self.messages.clone(); let is_fresh = job.freshness().is_fresh(); let rmeta_required = build_runner.rmeta_required(unit); let lock_manager = build_runner.lock_manager.clone(); let doit = move |diag_dedupe| { let state = JobState::new(id, messages, diag_dedupe, rmeta_required, lock_manager); state.run_to_finish(job); }; match is_fresh { true => { // Running a fresh job on the same thread is often much faster than spawning a new // thread to run the job. doit(Some(&self.diag_dedupe)); } false => { scope.spawn(move || doit(None)); } } } fn emit_log_messages( &self, unit: &Unit, build_runner: &mut BuildRunner<'_, '_>, show_warnings: bool, ) -> CargoResult<()> { let outputs = build_runner.build_script_outputs.lock().unwrap(); let Some(metadata_vec) = build_runner.find_build_script_metadatas(unit) else { return Ok(()); }; let bcx = &mut build_runner.bcx; for metadata in metadata_vec { if let Some(output) = outputs.get(metadata) { if !output.log_messages.is_empty() && (show_warnings || output .log_messages .iter() .any(|(severity, _)| *severity == Severity::Error)) { let msg_with_package = |msg: &str| format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), msg); for (severity, message) in output.log_messages.iter() { match severity { Severity::Error => { bcx.gctx.shell().error(msg_with_package(message))?; } Severity::Warning => { bcx.gctx.shell().warn(msg_with_package(message))?; } } } } } } Ok(()) } fn bump_warning_count(&mut self, id: JobId, lint: bool, emitted: bool, fixable: bool) { let count = self.warning_count.entry(id).or_default(); count.total += 1; if lint { let unit = self.active.get(&id).unwrap(); // If this is an upstream dep but we *do* want warnings, make sure that they // don't fail compilation. if unit.is_local() { count.lints += 1; } } if !emitted { count.duplicates += 1; // Don't add to fixable if it's already been emitted } else if fixable { // Do not add anything to the fixable warning count if // is `NotAllowed` since that indicates there was an // error while building this `Unit` if count.fixable_allowed() { count.fixable = match count.fixable { FixableWarnings::NotAllowed => FixableWarnings::NotAllowed, FixableWarnings::Zero => FixableWarnings::Positive(1), FixableWarnings::Positive(fixable) => FixableWarnings::Positive(fixable + 1), }; } } } /// Displays a final report of the warnings emitted by a particular job. fn report_warning_count( &mut self, runner: &mut BuildRunner<'_, '_>, unit: &Unit, count: &WarningCount, rustc_workspace_wrapper: &Option, warning_handling: WarningHandling, ) { let gctx = runner.bcx.gctx; runner.compilation.lint_warning_count += count.lints; let mut message = descriptive_pkg_name(&unit.pkg.name(), &unit.target, &unit.mode); message.push_str(" generated "); match count.total { 1 => message.push_str("1 warning"), n => { let _ = write!(message, "{} warnings", n); } }; match count.duplicates { 0 => {} 1 => message.push_str(" (1 duplicate)"), n => { let _ = write!(message, " ({} duplicates)", n); } } // Only show the `cargo fix` message if its a local `Unit` if unit.is_local() { // Do not show this if there are any errors or no fixable warnings if let FixableWarnings::Positive(fixable) = count.fixable { // `cargo fix` doesn't have an option for custom builds if !unit.target.is_custom_build() { // To make sure the correct command is shown for `clippy` we // check if `RUSTC_WORKSPACE_WRAPPER` is set and pointing towards // `clippy-driver`. let clippy = std::ffi::OsStr::new("clippy-driver"); let is_clippy = rustc_workspace_wrapper.as_ref().and_then(|x| x.file_stem()) == Some(clippy); let command = if is_clippy { "cargo clippy --fix" } else { "cargo fix" }; let mut args = format!("{} -p {}", unit.target.description_named(), unit.pkg.name()); if unit.mode.is_rustc_test() && !(unit.target.is_test() || unit.target.is_bench()) { args.push_str(" --tests"); } let mut suggestions = format!("{} suggestion", fixable); if fixable > 1 { suggestions.push_str("s") } #[expect(clippy::disallowed_methods, reason = "consistency with clippy")] let _ = write!( message, " (run `{command} --{args}{}` to apply {suggestions})", if let Some(cli_lints_os) = env::var_os("CLIPPY_ARGS") && let Ok(cli_lints) = cli_lints_os.into_string() && is_clippy { // Clippy can take lints through the CLI, each lint flag is separated by "__CLIPPY_HACKERY__". let cli_lints = cli_lints.replace("__CLIPPY_HACKERY__", " "); let cli_lints = cli_lints.trim_ascii_end(); // Remove that last space left by __CLIPPY_HACKERY__ format!(" -- {cli_lints}") } else { "".to_owned() } ); } } } // Errors are ignored here because it is tricky to handle them // correctly, and they aren't important. let _ = if warning_handling == WarningHandling::Deny && 0 < count.lints { gctx.shell().error(message) } else { gctx.shell().warn(message) }; } fn finish( &mut self, id: JobId, unit: &Unit, artifact: Artifact, build_runner: &mut BuildRunner<'_, '_>, ) -> CargoResult<()> { if unit.mode.is_run_custom_build() { self.emit_log_messages( unit, build_runner, unit.show_warnings(build_runner.bcx.gctx), )?; } let unblocked = self.queue.finish(unit, &artifact); match artifact { Artifact::All => self.timings.unit_finished(build_runner, id, unblocked), Artifact::Metadata => self .timings .unit_rmeta_finished(build_runner, id, unblocked), } Ok(()) } // This isn't super trivial because we don't want to print loads and // loads of information to the console, but we also want to produce a // faithful representation of what's happening. This is somewhat nuanced // as a package can start compiling *very* early on because of custom // build commands and such. // // In general, we try to print "Compiling" for the first nontrivial task // run for a package, regardless of when that is. We then don't print // out any more information for a package after we've printed it once. fn note_working_on( &mut self, gctx: &GlobalContext, ws_root: &Path, unit: &Unit, fresh: &Freshness, ) -> CargoResult<()> { if (self.compiled.contains(&unit.pkg.package_id()) && !unit.mode.is_doc() && !unit.mode.is_doc_scrape()) || (self.documented.contains(&unit.pkg.package_id()) && unit.mode.is_doc()) || (self.scraped.contains(&unit.pkg.package_id()) && unit.mode.is_doc_scrape()) { return Ok(()); } match fresh { // Any dirty stage which runs at least one command gets printed as // being a compiled package. Dirty(dirty_reason) => { if !dirty_reason.is_fresh_build() { gctx.shell().verbose(|shell| { dirty_reason.present_to(shell, unit, ws_root, &self.index_to_unit) })?; } if unit.mode.is_doc() { self.documented.insert(unit.pkg.package_id()); gctx.shell().status("Documenting", &unit.pkg)?; } else if unit.mode.is_doc_test() { // Skip doc test. } else if unit.mode.is_doc_scrape() { self.scraped.insert(unit.pkg.package_id()); gctx.shell().status("Scraping", &unit.pkg)?; } else { self.compiled.insert(unit.pkg.package_id()); if unit.mode.is_check() { gctx.shell().status("Checking", &unit.pkg)?; } else { gctx.shell().status("Compiling", &unit.pkg)?; } } } Fresh => { // If doc test are last, only print "Fresh" if nothing has been printed. if self.counts[&unit.pkg.package_id()] == 0 && !(unit.mode.is_doc_test() && self.compiled.contains(&unit.pkg.package_id())) { self.compiled.insert(unit.pkg.package_id()); gctx.shell().verbose(|c| c.status("Fresh", &unit.pkg))?; } } } Ok(()) } fn back_compat_notice( &self, build_runner: &BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult<()> { if unit.pkg.name() != "diesel" || unit.pkg.version() >= &Version::new(1, 4, 8) || build_runner.bcx.ws.resolve_behavior() == ResolveBehavior::V1 || !unit.pkg.package_id().source_id().is_registry() || !unit.features.is_empty() { return Ok(()); } if !build_runner .bcx .unit_graph .keys() .any(|unit| unit.pkg.name() == "diesel" && !unit.features.is_empty()) { return Ok(()); } build_runner.bcx.gctx.shell().note( "\ This error may be due to an interaction between diesel and Cargo's new feature resolver. Try updating to diesel 1.4.8 to fix this error. ", )?; Ok(()) } } impl ErrorsDuringDrain { fn to_error(&self) -> Option { match self.count { 0 => None, 1 => Some(format_err!("1 job failed")), n => Some(format_err!("{} jobs failed", n)), } } } ================================================ FILE: src/cargo/core/compiler/layout.rs ================================================ //! Management of the directory layout of a build //! //! The directory layout is a little tricky at times, hence a separate file to //! house this logic. Cargo stores build artifacts in two directories: `artifact-dir` and //! `build-dir` //! //! ## `artifact-dir` layout //! //! `artifact-dir` is where final artifacts like binaries are stored. //! The `artifact-dir` layout is consider part of the public API and //! cannot be easily changed. //! //! ```text //! / //! //! # Compilation files are grouped by build target and profile. //! # The target is omitted if not explicitly specified. //! []// # e.g. `debug` / `release` //! //! # File used to lock the directory to prevent multiple cargo processes //! # from using it at the same time. //! .cargo-lock //! //! # Root directory for all compiled examples. //! examples/ //! //! # Output from rustdoc //! doc/ //! //! # Output from `cargo package` to build a `.crate` file. //! package/ //! ``` //! //! ## `build-dir` layout //! //! `build-dir` is where intermediate build artifacts are stored. //! The `build-dir` layout is considered an internal implementation detail of Cargo //! meaning that we can change this if needed. However, in reality many tools rely on //! implementation details of Cargo so breaking changes need to be done carefully. //! //! ```text //! / //! //! # Cache of `rustc -Vv` output for performance. //! .rustc-info.json //! //! # Compilation files are grouped by build target and profile. //! # The target is omitted if not explicitly specified. //! []// # e.g. `debug` / `release` //! //! # File used to lock the directory to prevent multiple cargo processes //! # from using it at the same time. //! .cargo-lock //! //! # Hidden directory that holds all of the fingerprint files for all //! # packages //! .fingerprint/ //! # Each package is in a separate directory. //! # Note that different target kinds have different filename prefixes. //! $pkgname-$META/ //! # Set of source filenames for this package. //! dep-lib-$targetname //! # Timestamp when this package was last built. //! invoked.timestamp //! # The fingerprint hash. //! lib-$targetname //! # Detailed information used for logging the reason why //! # something is being recompiled. //! lib-$targetname.json //! # The console output from the compiler. This is cached //! # so that warnings can be redisplayed for "fresh" units. //! output-lib-$targetname //! //! # This is the root directory for all rustc artifacts except build //! # scripts, examples, and test and bench executables. Almost every //! # artifact should have a metadata hash added to its filename to //! # prevent collisions. One notable exception is dynamic libraries. //! deps/ //! //! # Each artifact dependency gets in its own directory. //! /artifact/$pkgname-$META/$kind //! //! # Root directory for all compiled examples. //! examples/ //! //! # Directory used to store incremental data for the compiler (when //! # incremental is enabled. //! incremental/ //! //! # This is the location at which the output of all custom build //! # commands are rooted. //! build/ //! //! # Each package gets its own directory where its build script and //! # script output are placed //! $pkgname-$META/ # For the build script itself. //! # The build script executable (name may be changed by user). //! build-script-build-$META //! # Hard link to build-script-build-$META. //! build-script-build //! # Dependency information generated by rustc. //! build-script-build-$META.d //! # Debug information, depending on platform and profile //! # settings. //! //! //! # The package shows up twice with two different metadata hashes. //! $pkgname-$META/ # For the output of the build script. //! # Timestamp when the build script was last executed. //! invoked.timestamp //! # Directory where script can output files ($OUT_DIR). //! out/ //! # Output from the build script. //! output //! # Path to `out`, used to help when the target directory is //! # moved. //! root-output //! # Stderr output from the build script. //! stderr //! //! # Used by `cargo package` and `cargo publish` to build a `.crate` file. //! package/ //! //! # Experimental feature for generated build scripts. //! .metabuild/ //! ``` //! //! ### New `build-dir` layout //! //! `build-dir` supports a new "build unit" based layout that is unstable. //! It can be enabled via `-Zbuild-dir-new-layout`. //! For more info about the layout transition see: [#15010](https://github.com/rust-lang/cargo/issues/15010) //! //! ```text //! / //! //! # Cache of `rustc -Vv` output for performance. //! .rustc-info.json //! //! # Compilation files are grouped by build target and profile. //! # The target is omitted if not explicitly specified. //! []// # e.g. `debug` / `release` //! //! # File used to lock the directory to prevent multiple cargo processes //! # from using it at the same time. //! .cargo-lock //! //! # Directory used to store incremental data for the compiler (when //! # incremental is enabled. //! incremental/ //! //! # Main directory for storing build unit related files. //! # Files are organized by Cargo build unit (`$pkgname/$META`) so that //! # related files are stored in a single directory. //! build/ //! //! # This is the location at which the output of all files related to //! # a given build unit. These files are organized together so that we can //! # treat this directly like a single unit for locking and caching. //! $pkgname/ //! $META/ //! # The general purpose output directory for build units. //! # For compilation units, the rustc artifact will be located here. //! # For build script run units, this is the $OUT_DIR //! out/ //! //! # For artifact dependency units, the output is nested by the kind //! artifact/$kind //! //! # Directory that holds all of the fingerprint files for the build unit. //! fingerprint/ //! # Set of source filenames for this package. //! dep-lib-$targetname //! # Timestamp when this package was last built. //! invoked.timestamp //! # The fingerprint hash. //! lib-$targetname //! # Detailed information used for logging the reason why //! # something is being recompiled. //! lib-$targetname.json //! # The console output from the compiler. This is cached //! # so that warnings can be redisplayed for "fresh" units. //! output-lib-$targetname //! //! # Directory for "execution" units that spawn a process (excluding compilation with //! # rustc). Contains the process execution details. //! # Currently the only execution unit Cargo supports is running build script //! # binaries. //! run/ //! # Timestamp of last execution. //! invoked.timestamp //! # Stdout output from the process. //! stdout //! # Stderr output from the process. //! stderr //! # Path to `out`, used to help when the target directory is //! # moved. (build scripts) //! root-output //! //! # Used by `cargo package` and `cargo publish` to build a `.crate` file. //! package/ //! //! # Experimental feature for generated build scripts. //! .metabuild/ //! ``` //! //! When cross-compiling, the layout is the same, except it appears in //! `target/$TRIPLE`. use crate::core::Workspace; use crate::core::compiler::CompileTarget; use crate::util::flock::is_on_nfs_mount; use crate::util::{CargoResult, FileLock}; use cargo_util::paths; use std::path::{Path, PathBuf}; /// Contains the paths of all target output locations. /// /// See module docs for more information. pub struct Layout { artifact_dir: Option, build_dir: BuildDirLayout, } impl Layout { /// Calculate the paths for build output, lock the build directory, and return as a Layout. /// /// This function will block if the directory is already locked. /// /// `dest` should be the final artifact directory name. Currently either /// "debug" or "release". pub fn new( ws: &Workspace<'_>, target: Option, dest: &str, must_take_artifact_dir_lock: bool, must_take_build_dir_lock_exclusively: bool, ) -> CargoResult { let is_new_layout = ws.gctx().cli_unstable().build_dir_new_layout; let mut root = ws.target_dir(); let mut build_root = ws.build_dir(); if let Some(target) = target { root.push(target.short_name()); build_root.push(target.short_name()); } let build_dest = build_root.join(dest); let dest = root.join(dest); // If the root directory doesn't already exist go ahead and create it // here. Use this opportunity to exclude it from backups as well if the // system supports it since this is a freshly created folder. // paths::create_dir_all_excluded_from_backups_atomic(root.as_path_unlocked())?; if root != build_root { paths::create_dir_all_excluded_from_backups_atomic(build_root.as_path_unlocked())?; } // Now that the excluded from backups target root is created we can create the // actual destination (sub)subdirectory. paths::create_dir_all(dest.as_path_unlocked())?; let build_dir_lock = if is_on_nfs_mount(build_root.as_path_unlocked()) { None } else { if ws.gctx().cli_unstable().fine_grain_locking && !must_take_build_dir_lock_exclusively { Some(build_dest.open_ro_shared_create( ".cargo-build-lock", ws.gctx(), "build directory", )?) } else { Some(build_dest.open_rw_exclusive_create( ".cargo-build-lock", ws.gctx(), "build directory", )?) } }; let build_root = build_root.into_path_unlocked(); let build_dest = build_dest.as_path_unlocked(); let deps = build_dest.join("deps"); let artifact = deps.join("artifact"); let artifact_dir = if must_take_artifact_dir_lock { // For now we don't do any more finer-grained locking on the artifact // directory, so just lock the entire thing for the duration of this // compile. let artifact_dir_lock = if is_on_nfs_mount(root.as_path_unlocked()) { None } else { Some(dest.open_rw_exclusive_create( ".cargo-lock", ws.gctx(), "artifact directory", )?) }; let root = root.into_path_unlocked(); let dest = dest.into_path_unlocked(); Some(ArtifactDirLayout { dest: dest.clone(), examples: dest.join("examples"), doc: root.join("doc"), timings: root.join("cargo-timings"), _lock: artifact_dir_lock, }) } else { None }; Ok(Layout { artifact_dir, build_dir: BuildDirLayout { root: build_root.clone(), deps, build: build_dest.join("build"), artifact, incremental: build_dest.join("incremental"), fingerprint: build_dest.join(".fingerprint"), examples: build_dest.join("examples"), tmp: build_root.join("tmp"), _lock: build_dir_lock, is_new_layout, }, }) } /// Makes sure all directories stored in the Layout exist on the filesystem. pub fn prepare(&mut self) -> CargoResult<()> { if let Some(ref mut artifact_dir) = self.artifact_dir { artifact_dir.prepare()?; } self.build_dir.prepare()?; Ok(()) } pub fn artifact_dir(&self) -> Option<&ArtifactDirLayout> { self.artifact_dir.as_ref() } pub fn build_dir(&self) -> &BuildDirLayout { &self.build_dir } } pub struct ArtifactDirLayout { /// The final artifact destination: `/debug` (or `release`). dest: PathBuf, /// The directory for examples examples: PathBuf, /// The directory for rustdoc output doc: PathBuf, /// The directory for --timings output timings: PathBuf, /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this /// struct is `drop`ped. _lock: Option, } impl ArtifactDirLayout { /// Makes sure all directories stored in the Layout exist on the filesystem. pub fn prepare(&mut self) -> CargoResult<()> { paths::create_dir_all(&self.examples)?; Ok(()) } /// Fetch the destination path for final artifacts (`/…/target/debug`). pub fn dest(&self) -> &Path { &self.dest } /// Fetch the examples path. pub fn examples(&self) -> &Path { &self.examples } /// Fetch the doc path. pub fn doc(&self) -> &Path { &self.doc } /// Fetch the cargo-timings path. pub fn timings(&self) -> &Path { &self.timings } } pub struct BuildDirLayout { /// The root directory: `/path/to/build-dir`. /// If cross compiling: `/path/to/build-dir/$TRIPLE`. root: PathBuf, /// The directory with rustc artifacts deps: PathBuf, /// The primary directory for build files build: PathBuf, /// The directory for artifacts, i.e. binaries, cdylibs, staticlibs artifact: PathBuf, /// The directory for incremental files incremental: PathBuf, /// The directory for fingerprints fingerprint: PathBuf, /// The directory for pre-uplifted examples: `build-dir/debug/examples` examples: PathBuf, /// The directory for temporary data of integration tests and benches tmp: PathBuf, /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this /// struct is `drop`ped. /// /// Will be `None` when the build-dir and target-dir are the same path as we cannot /// lock the same path twice. _lock: Option, is_new_layout: bool, } impl BuildDirLayout { /// Makes sure all directories stored in the Layout exist on the filesystem. pub fn prepare(&mut self) -> CargoResult<()> { if !self.is_new_layout { paths::create_dir_all(&self.deps)?; paths::create_dir_all(&self.fingerprint)?; paths::create_dir_all(&self.examples)?; } paths::create_dir_all(&self.incremental)?; paths::create_dir_all(&self.build)?; Ok(()) } /// Fetch the deps path. pub fn deps(&self, pkg_dir: &str) -> PathBuf { if self.is_new_layout { self.out_force_new_layout(pkg_dir) } else { self.legacy_deps().to_path_buf() } } /// Fetch the output path for build units. (new layout only) /// /// New features should consider using this so we can avoid their migrations. pub fn out_force_new_layout(&self, pkg_dir: &str) -> PathBuf { self.build_unit(pkg_dir).join("out") } /// Fetch the deps path. (old layout) pub fn legacy_deps(&self) -> &Path { &self.deps } pub fn root(&self) -> &Path { &self.root } /// Fetch the build examples path. pub fn examples(&self) -> &Path { &self.examples } /// Fetch the incremental path. pub fn incremental(&self) -> &Path { &self.incremental } /// Fetch the fingerprint path. pub fn fingerprint(&self, pkg_dir: &str) -> PathBuf { if self.is_new_layout { self.build_unit(pkg_dir).join("fingerprint") } else { self.legacy_fingerprint().to_path_buf().join(pkg_dir) } } /// Fetch the fingerprint path. (old layout) pub fn legacy_fingerprint(&self) -> &Path { &self.fingerprint } /// Fetch the build path. pub fn build(&self) -> &Path { &self.build } /// Fetch the build script path. pub fn build_script(&self, pkg_dir: &str) -> PathBuf { if self.is_new_layout { self.deps(pkg_dir) } else { self.build().join(pkg_dir) } } /// Fetch the build script execution path. pub fn build_script_execution(&self, pkg_dir: &str) -> PathBuf { if self.is_new_layout { self.build_unit(pkg_dir).join("run") } else { self.build().join(pkg_dir) } } /// Fetch the artifact path. pub fn artifact(&self, pkg_dir: &str, kind: &str) -> PathBuf { if self.is_new_layout { self.build_unit(pkg_dir).join("artifact").join(kind) } else { self.artifact.join(pkg_dir).join(kind) } } /// Fetch the build unit path pub fn build_unit(&self, pkg_dir: &str) -> PathBuf { self.build().join(pkg_dir) } /// Create and return the tmp path. pub fn prepare_tmp(&self) -> CargoResult<&Path> { paths::create_dir_all(&self.tmp)?; Ok(&self.tmp) } } ================================================ FILE: src/cargo/core/compiler/links.rs ================================================ use super::unit_graph::UnitGraph; use crate::core::resolver::errors::describe_path; use crate::core::{PackageId, Resolve}; use crate::util::errors::CargoResult; use std::collections::{HashMap, HashSet}; /// Validates [`package.links`] field in the manifest file does not conflict /// between packages. /// /// NOTE: This is the *old* links validator. Links are usually validated in the /// resolver. However, the `links` field was added to the index in early 2018 /// (see [rust-lang/cargo#4978]). However, `links` has been around since 2014, /// so there are still many crates in the index that don't have `links` /// properly set in the index (over 600 at the time of this writing in 2019). /// This can probably be removed at some point in the future, though it might /// be worth considering fixing the index. /// /// [rust-lang/cargo#4978]: https://github.com/rust-lang/cargo/pull/4978 /// [`package.links`]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#the-links-manifest-key pub fn validate_links(resolve: &Resolve, unit_graph: &UnitGraph) -> CargoResult<()> { let mut validated: HashSet = HashSet::new(); let mut links: HashMap = HashMap::new(); let mut units: Vec<_> = unit_graph.keys().collect(); // Sort primarily to make testing easier. units.sort_unstable(); for unit in units { if !validated.insert(unit.pkg.package_id()) { continue; } let Some(lib) = unit.pkg.manifest().links() else { continue; }; if let Some(&prev) = links.get(lib) { let prev_path = resolve .path_to_top(&prev) .into_iter() .map(|(p, d)| (p, d.and_then(|d| d.iter().next()))); let pkg = unit.pkg.package_id(); let path = resolve .path_to_top(&pkg) .into_iter() .map(|(p, d)| (p, d.and_then(|d| d.iter().next()))); anyhow::bail!( "multiple packages link to native library `{}`, \ but a native library can be linked only once\n\ \n\ {}\nlinks to native library `{}`\n\ \n\ {}\nalso links to native library `{}`", lib, describe_path(prev_path), lib, describe_path(path), lib ) } links.insert(lib.to_string(), unit.pkg.package_id()); } Ok(()) } ================================================ FILE: src/cargo/core/compiler/locking.rs ================================================ //! This module handles the locking logic during compilation. use crate::{ CargoResult, core::compiler::{BuildRunner, Unit}, util::{FileLock, Filesystem}, }; use anyhow::bail; use std::{ collections::HashMap, fmt::{Display, Formatter}, path::PathBuf, sync::RwLock, }; use tracing::instrument; /// A struct to store the lock handles for build units during compilation. pub struct LockManager { locks: RwLock>, } impl LockManager { pub fn new() -> Self { Self { locks: RwLock::new(HashMap::new()), } } /// Takes a shared lock on a given [`Unit`] /// This prevents other Cargo instances from compiling (writing) to /// this build unit. /// /// This function returns a [`LockKey`] which can be used to /// upgrade/unlock the lock. #[instrument(skip_all, fields(key))] pub fn lock_shared( &self, build_runner: &BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult { let key = LockKey::from_unit(build_runner, unit); tracing::Span::current().record("key", key.0.to_str()); let mut locks = self.locks.write().unwrap(); if let Some(lock) = locks.get_mut(&key) { lock.file().lock_shared()?; } else { let fs = Filesystem::new(key.0.clone()); let lock_msg = format!( "{} ({})", unit.pkg.name(), build_runner.files().unit_hash(unit) ); let lock = fs.open_ro_shared_create(&key.0, build_runner.bcx.gctx, &lock_msg)?; locks.insert(key.clone(), lock); } Ok(key) } #[instrument(skip(self))] pub fn lock(&self, key: &LockKey) -> CargoResult<()> { let locks = self.locks.read().unwrap(); if let Some(lock) = locks.get(&key) { lock.file().lock()?; } else { bail!("lock was not found in lock manager: {key}"); } Ok(()) } /// Upgrades an existing exclusive lock into a shared lock. #[instrument(skip(self))] pub fn downgrade_to_shared(&self, key: &LockKey) -> CargoResult<()> { let locks = self.locks.read().unwrap(); let Some(lock) = locks.get(key) else { bail!("lock was not found in lock manager: {key}"); }; lock.file().lock_shared()?; Ok(()) } #[instrument(skip(self))] pub fn unlock(&self, key: &LockKey) -> CargoResult<()> { let locks = self.locks.read().unwrap(); if let Some(lock) = locks.get(key) { lock.file().unlock()?; }; Ok(()) } } #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct LockKey(PathBuf); impl LockKey { fn from_unit(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self { Self(build_runner.files().build_unit_lock(unit)) } } impl Display for LockKey { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0.display()) } } ================================================ FILE: src/cargo/core/compiler/lto.rs ================================================ use crate::core::compiler::{BuildContext, CompileMode, CrateType, Unit}; use crate::core::profiles; use crate::util::interning::InternedString; use crate::util::errors::CargoResult; use std::collections::hash_map::{Entry, HashMap}; /// Possible ways to run rustc and request various parts of [LTO]. /// /// Variant | Flag | Object Code | Bitcode /// -------------------|------------------------|-------------|-------- /// `Run` | `-C lto=foo` | n/a | n/a /// `Off` | `-C lto=off` | n/a | n/a /// `OnlyBitcode` | `-C linker-plugin-lto` | | ✓ /// `ObjectAndBitcode` | | ✓ | ✓ /// `OnlyObject` | `-C embed-bitcode=no` | ✓ | /// /// [LTO]: https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#lto #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Lto { /// LTO is run for this rustc, and it's `-Clto=foo`. If the given value is /// None, that corresponds to `-Clto` with no argument, which means do /// "fat" LTO. Run(Option), /// LTO has been explicitly listed as "off". This means no thin-local-LTO, /// no LTO anywhere, I really mean it! Off, /// This rustc invocation only needs to produce bitcode (it is *only* used /// for LTO), there's no need to produce object files, so we can pass /// `-Clinker-plugin-lto` OnlyBitcode, /// This rustc invocation needs to embed bitcode in object files. This means /// that object files may be used for a normal link, and the crate may be /// loaded for LTO later, so both are required. ObjectAndBitcode, /// This should not include bitcode. This is primarily to reduce disk /// space usage. OnlyObject, } pub fn generate(bcx: &BuildContext<'_, '_>) -> CargoResult> { let mut map = HashMap::new(); for unit in bcx.roots.iter() { let root_lto = match unit.profile.lto { // LTO not requested, no need for bitcode. profiles::Lto::Bool(false) => Lto::OnlyObject, profiles::Lto::Off => Lto::Off, _ => { let crate_types = unit.target.rustc_crate_types(); if unit.target.for_host() { Lto::OnlyObject } else if needs_object(&crate_types) { lto_when_needs_object(&crate_types) } else { // This may or may not participate in LTO, let's start // with the minimum requirements. This may be expanded in // `calculate` below if necessary. Lto::OnlyBitcode } } }; calculate(bcx, &mut map, unit, root_lto)?; } Ok(map) } /// Whether or not any of these crate types need object code. fn needs_object(crate_types: &[CrateType]) -> bool { crate_types.iter().any(|k| k.can_lto() || k.is_dynamic()) } /// Lto setting to use when this unit needs object code. fn lto_when_needs_object(crate_types: &[CrateType]) -> Lto { if crate_types.iter().all(|ct| *ct == CrateType::Dylib) { // A dylib whose parent is running LTO. rustc currently // doesn't support LTO with dylibs, so bitcode is not // needed. Lto::OnlyObject } else { // Mixed rlib with a dylib or cdylib whose parent is running LTO. This // needs both: bitcode for the rlib (for LTO) and object code for the // dylib. Lto::ObjectAndBitcode } } fn calculate( bcx: &BuildContext<'_, '_>, map: &mut HashMap, unit: &Unit, parent_lto: Lto, ) -> CargoResult<()> { let crate_types = match unit.mode { // Note: Doctest ignores LTO, but for now we'll compute it as-if it is // a Bin, in case it is ever supported in the future. CompileMode::Test | CompileMode::Doctest => vec![CrateType::Bin], // Notes on other modes: // - Check: Treat as the underlying type, it doesn't really matter. // - Doc: LTO is N/A for the Doc unit itself since rustdoc does not // support codegen flags. We still compute the dependencies, which // are mostly `Check`. // - RunCustomBuild is ignored because it is always "for_host". _ => unit.target.rustc_crate_types(), }; // LTO can only be performed if *all* of the crate types support it. // For example, a cdylib/rlib combination won't allow LTO. let all_lto_types = crate_types.iter().all(CrateType::can_lto); // Compute the LTO based on the profile, and what our parent requires. let lto = if unit.target.for_host() { // Disable LTO for host builds since we only really want to perform LTO // for the final binary, and LTO on plugins/build scripts/proc macros is // largely not desired. Lto::OnlyObject } else if all_lto_types { // Note that this ignores the `parent_lto` because this isn't a // linkable crate type; this unit is not being embedded in the parent. match unit.profile.lto { profiles::Lto::Named(s) => Lto::Run(Some(s)), profiles::Lto::Off => Lto::Off, profiles::Lto::Bool(true) => Lto::Run(None), profiles::Lto::Bool(false) => Lto::OnlyObject, } } else { match (parent_lto, needs_object(&crate_types)) { // An rlib whose parent is running LTO, we only need bitcode. (Lto::Run(_), false) => Lto::OnlyBitcode, // LTO when something needs object code. (Lto::Run(_), true) | (Lto::OnlyBitcode, true) => lto_when_needs_object(&crate_types), // LTO is disabled, continue to disable it. (Lto::Off, _) => Lto::Off, // If this doesn't have any requirements, or the requirements are // already satisfied, then stay with our parent. (_, false) | (Lto::OnlyObject, true) | (Lto::ObjectAndBitcode, true) => parent_lto, } }; // Merge the computed LTO. If this unit appears multiple times in the // graph, the merge may expand the requirements. let merged_lto = match map.entry(unit.clone()) { // If we haven't seen this unit before then insert our value and keep // going. Entry::Vacant(v) => *v.insert(lto), Entry::Occupied(mut v) => { let result = match (lto, v.get()) { // No change in requirements. (Lto::OnlyBitcode, Lto::OnlyBitcode) => Lto::OnlyBitcode, (Lto::OnlyObject, Lto::OnlyObject) => Lto::OnlyObject, // Once we're running LTO we keep running LTO. We should always // calculate the same thing here each iteration because if we // see this twice then it means, for example, two unit tests // depend on a binary, which is normal. (Lto::Run(s), _) | (_, &Lto::Run(s)) => Lto::Run(s), // Off means off! This has the same reasoning as `Lto::Run`. (Lto::Off, _) | (_, Lto::Off) => Lto::Off, // Once a target has requested both, that's the maximal amount // of work that can be done, so we just keep doing that work. (Lto::ObjectAndBitcode, _) | (_, Lto::ObjectAndBitcode) => Lto::ObjectAndBitcode, // Upgrade so that both requirements can be met. // // This is where the trickiness happens. This unit needs // bitcode and the previously calculated value for this unit // says it didn't need bitcode (or vice versa). This means that // we're a shared dependency between some targets which require // LTO and some which don't. This means that instead of being // either only-objects or only-bitcode we have to embed both in // rlibs (used for different compilations), so we switch to // including both. (Lto::OnlyObject, Lto::OnlyBitcode) | (Lto::OnlyBitcode, Lto::OnlyObject) => { Lto::ObjectAndBitcode } }; // No need to recurse if we calculated the same value as before. if result == *v.get() { return Ok(()); } v.insert(result); result } }; for dep in &bcx.unit_graph[unit] { calculate(bcx, map, &dep.unit, merged_lto)?; } Ok(()) } ================================================ FILE: src/cargo/core/compiler/mod.rs ================================================ //! # Interact with the compiler //! //! If you consider [`ops::cargo_compile::compile`] as a `rustc` driver but on //! Cargo side, this module is kinda the `rustc_interface` for that merits. //! It contains all the interaction between Cargo and the rustc compiler, //! from preparing the context for the entire build process, to scheduling //! and executing each unit of work (e.g. running `rustc`), to managing and //! caching the output artifact of a build. //! //! However, it hasn't yet exposed a clear definition of each phase or session, //! like what rustc has done. Also, no one knows if Cargo really needs that. //! To be pragmatic, here we list a handful of items you may want to learn: //! //! * [`BuildContext`] is a static context containing all information you need //! before a build gets started. //! * [`BuildRunner`] is the center of the world, coordinating a running build and //! collecting information from it. //! * [`custom_build`] is the home of build script executions and output parsing. //! * [`fingerprint`] not only defines but also executes a set of rules to //! determine if a re-compile is needed. //! * [`job_queue`] is where the parallelism, job scheduling, and communication //! machinery happen between Cargo and the compiler. //! * [`layout`] defines and manages output artifacts of a build in the filesystem. //! * [`unit_dependencies`] is for building a dependency graph for compilation //! from a result of dependency resolution. //! * [`Unit`] contains sufficient information to build something, usually //! turning into a compiler invocation in a later phase. //! //! [`ops::cargo_compile::compile`]: crate::ops::compile pub mod artifact; mod build_config; pub(crate) mod build_context; pub(crate) mod build_runner; mod compilation; mod compile_kind; mod crate_type; mod custom_build; pub(crate) mod fingerprint; pub mod future_incompat; pub(crate) mod job_queue; pub(crate) mod layout; mod links; mod locking; mod lto; mod output_depinfo; mod output_sbom; pub mod rustdoc; pub mod standard_lib; pub mod timings; mod unit; pub mod unit_dependencies; pub mod unit_graph; use std::borrow::Cow; use std::cell::OnceCell; use std::collections::{BTreeMap, HashMap, HashSet}; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt::Display; use std::fs::{self, File}; use std::io::{BufRead, BufWriter, Write}; use std::ops::Range; use std::path::{Path, PathBuf}; use std::sync::{Arc, LazyLock}; use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; use anyhow::{Context as _, Error}; use cargo_platform::{Cfg, Platform}; use itertools::Itertools; use regex::Regex; use tracing::{debug, instrument, trace}; pub use self::build_config::UserIntent; pub use self::build_config::{BuildConfig, CompileMode, MessageFormat}; pub use self::build_context::BuildContext; pub use self::build_context::FileFlavor; pub use self::build_context::FileType; pub use self::build_context::RustcTargetData; pub use self::build_context::TargetInfo; pub use self::build_runner::{BuildRunner, Metadata, UnitHash}; pub use self::compilation::{Compilation, Doctest, UnitOutput}; pub use self::compile_kind::{CompileKind, CompileKindFallback, CompileTarget}; pub use self::crate_type::CrateType; pub use self::custom_build::LinkArgTarget; pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts, LibraryPath}; pub(crate) use self::fingerprint::DirtyReason; pub use self::fingerprint::RustdocFingerprint; pub use self::job_queue::Freshness; use self::job_queue::{Job, JobQueue, JobState, Work}; pub(crate) use self::layout::Layout; pub use self::lto::Lto; use self::output_depinfo::output_depinfo; use self::output_sbom::build_sbom; use self::unit_graph::UnitDep; use crate::core::compiler::future_incompat::FutureIncompatReport; use crate::core::compiler::locking::LockKey; use crate::core::compiler::timings::SectionTiming; pub use crate::core::compiler::unit::Unit; pub use crate::core::compiler::unit::UnitIndex; pub use crate::core::compiler::unit::UnitInterner; use crate::core::manifest::TargetSourcePath; use crate::core::profiles::{PanicStrategy, Profile, StripInner}; use crate::core::{Feature, PackageId, Target, Verbosity}; use crate::lints::get_key_value; use crate::util::OnceExt; use crate::util::context::WarningHandling; use crate::util::errors::{CargoResult, VerboseError}; use crate::util::interning::InternedString; use crate::util::machine_message::{self, Message}; use crate::util::{add_path_args, internal, path_args}; use cargo_util::{ProcessBuilder, ProcessError, paths}; use cargo_util_schemas::manifest::TomlDebugInfo; use cargo_util_schemas::manifest::TomlTrimPaths; use cargo_util_schemas::manifest::TomlTrimPathsValue; use rustfix::diagnostics::Applicability; const RUSTDOC_CRATE_VERSION_FLAG: &str = "--crate-version"; /// A glorified callback for executing calls to rustc. Rather than calling rustc /// directly, we'll use an `Executor`, giving clients an opportunity to intercept /// the build calls. pub trait Executor: Send + Sync + 'static { /// Called after a rustc process invocation is prepared up-front for a given /// unit of work (may still be modified for runtime-known dependencies, when /// the work is actually executed). fn init(&self, _build_runner: &BuildRunner<'_, '_>, _unit: &Unit) {} /// In case of an `Err`, Cargo will not continue with the build process for /// this package. fn exec( &self, cmd: &ProcessBuilder, id: PackageId, target: &Target, mode: CompileMode, on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>, on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>, ) -> CargoResult<()>; /// Queried when queuing each unit of work. If it returns true, then the /// unit will always be rebuilt, independent of whether it needs to be. fn force_rebuild(&self, _unit: &Unit) -> bool { false } } /// A `DefaultExecutor` calls rustc without doing anything else. It is Cargo's /// default behaviour. #[derive(Copy, Clone)] pub struct DefaultExecutor; impl Executor for DefaultExecutor { #[instrument(name = "rustc", skip_all, fields(package = id.name().as_str(), process = cmd.to_string()))] fn exec( &self, cmd: &ProcessBuilder, id: PackageId, _target: &Target, _mode: CompileMode, on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>, on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>, ) -> CargoResult<()> { cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false) .map(drop) } } /// Builds up and enqueue a list of pending jobs onto the `job` queue. /// /// Starting from the `unit`, this function recursively calls itself to build /// all jobs for dependencies of the `unit`. Each of these jobs represents /// compiling a particular package. /// /// Note that **no actual work is executed as part of this**, that's all done /// next as part of [`JobQueue::execute`] function which will run everything /// in order with proper parallelism. #[tracing::instrument(skip(build_runner, jobs, exec))] fn compile<'gctx>( build_runner: &mut BuildRunner<'_, 'gctx>, jobs: &mut JobQueue<'gctx>, unit: &Unit, exec: &Arc, force_rebuild: bool, ) -> CargoResult<()> { let bcx = build_runner.bcx; if !build_runner.compiled.insert(unit.clone()) { return Ok(()); } let lock = if build_runner.bcx.gctx.cli_unstable().fine_grain_locking { Some(build_runner.lock_manager.lock_shared(build_runner, unit)?) } else { None }; // If we are in `--compile-time-deps` and the given unit is not a compile time // dependency, skip compiling the unit and jumps to dependencies, which still // have chances to be compile time dependencies if !unit.skip_non_compile_time_dep { // Build up the work to be done to compile this unit, enqueuing it once // we've got everything constructed. fingerprint::prepare_init(build_runner, unit)?; let job = if unit.mode.is_run_custom_build() { custom_build::prepare(build_runner, unit)? } else if unit.mode.is_doc_test() { // We run these targets later, so this is just a no-op for now. Job::new_fresh() } else { let force = exec.force_rebuild(unit) || force_rebuild; let mut job = fingerprint::prepare_target(build_runner, unit, force)?; job.before(if job.freshness().is_dirty() { let work = if unit.mode.is_doc() || unit.mode.is_doc_scrape() { rustdoc(build_runner, unit)? } else { rustc(build_runner, unit, exec)? }; work.then(link_targets(build_runner, unit, false)?) } else { // We always replay the output cache, // since it might contain future-incompat-report messages let show_diagnostics = unit.show_warnings(bcx.gctx) && build_runner.bcx.gctx.warning_handling()? != WarningHandling::Allow; let manifest = ManifestErrorContext::new(build_runner, unit); let work = replay_output_cache( unit.pkg.package_id(), manifest, &unit.target, build_runner.files().message_cache_path(unit), build_runner.bcx.build_config.message_format, show_diagnostics, ); // Need to link targets on both the dirty and fresh. work.then(link_targets(build_runner, unit, true)?) }); // If -Zfine-grain-locking is enabled, we wrap the job with an upgrade to exclusive // lock before starting, then downgrade to a shared lock after the job is finished. if build_runner.bcx.gctx.cli_unstable().fine_grain_locking && job.freshness().is_dirty() { if let Some(lock) = lock { // Here we unlock the current shared lock to avoid deadlocking with other cargo // processes. Then we configure our compile job to take an exclusive lock // before starting. Once we are done compiling (including both rmeta and rlib) // we downgrade to a shared lock to allow other cargo's to read the build unit. // We will hold this shared lock for the remainder of compilation to prevent // other cargo from re-compiling while we are still using the unit. build_runner.lock_manager.unlock(&lock)?; job.before(prebuild_lock_exclusive(lock.clone())); job.after(downgrade_lock_to_shared(lock)); } } job }; jobs.enqueue(build_runner, unit, job)?; } // Be sure to compile all dependencies of this target as well. let deps = Vec::from(build_runner.unit_deps(unit)); // Create vec due to mutable borrow. for dep in deps { compile(build_runner, jobs, &dep.unit, exec, false)?; } Ok(()) } /// Generates the warning message used when fallible doc-scrape units fail, /// either for rustdoc or rustc. fn make_failed_scrape_diagnostic( build_runner: &BuildRunner<'_, '_>, unit: &Unit, top_line: impl Display, ) -> String { let manifest_path = unit.pkg.manifest_path(); let relative_manifest_path = manifest_path .strip_prefix(build_runner.bcx.ws.root()) .unwrap_or(&manifest_path); format!( "\ {top_line} Try running with `--verbose` to see the error message. If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in {}", relative_manifest_path.display() ) } /// Creates a unit of work invoking `rustc` for building the `unit`. fn rustc( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, exec: &Arc, ) -> CargoResult { let mut rustc = prepare_rustc(build_runner, unit)?; let name = unit.pkg.name(); let outputs = build_runner.outputs(unit)?; let root = build_runner.files().output_dir(unit); // Prepare the native lib state (extra `-L` and `-l` flags). let build_script_outputs = Arc::clone(&build_runner.build_script_outputs); let current_id = unit.pkg.package_id(); let manifest = ManifestErrorContext::new(build_runner, unit); let build_scripts = build_runner.build_scripts.get(unit).cloned(); // If we are a binary and the package also contains a library, then we // don't pass the `-l` flags. let pass_l_flag = unit.target.is_lib() || !unit.pkg.targets().iter().any(|t| t.is_lib()); let dep_info_name = if let Some(c_extra_filename) = build_runner.files().metadata(unit).c_extra_filename() { format!("{}-{}.d", unit.target.crate_name(), c_extra_filename) } else { format!("{}.d", unit.target.crate_name()) }; let rustc_dep_info_loc = root.join(dep_info_name); let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit); let mut output_options = OutputOptions::new(build_runner, unit); let package_id = unit.pkg.package_id(); let target = Target::clone(&unit.target); let mode = unit.mode; exec.init(build_runner, unit); let exec = exec.clone(); let root_output = build_runner.files().host_dest().map(|v| v.to_path_buf()); let build_dir = build_runner.bcx.ws.build_dir().into_path_unlocked(); let pkg_root = unit.pkg.root().to_path_buf(); let cwd = rustc .get_cwd() .unwrap_or_else(|| build_runner.bcx.gctx.cwd()) .to_path_buf(); let fingerprint_dir = build_runner.files().fingerprint_dir(unit); let script_metadatas = build_runner.find_build_script_metadatas(unit); let is_local = unit.is_local(); let artifact = unit.artifact; let sbom_files = build_runner.sbom_output_files(unit)?; let sbom = build_sbom(build_runner, unit)?; let hide_diagnostics_for_scrape_unit = build_runner.bcx.unit_can_fail_for_docscraping(unit) && !matches!( build_runner.bcx.gctx.shell().verbosity(), Verbosity::Verbose ); let failed_scrape_diagnostic = hide_diagnostics_for_scrape_unit.then(|| { // If this unit is needed for doc-scraping, then we generate a diagnostic that // describes the set of reverse-dependencies that cause the unit to be needed. let target_desc = unit.target.description_named(); let mut for_scrape_units = build_runner .bcx .scrape_units_have_dep_on(unit) .into_iter() .map(|unit| unit.target.description_named()) .collect::>(); for_scrape_units.sort(); let for_scrape_units = for_scrape_units.join(", "); make_failed_scrape_diagnostic(build_runner, unit, format_args!("failed to check {target_desc} in package `{name}` as a prerequisite for scraping examples from: {for_scrape_units}")) }); if hide_diagnostics_for_scrape_unit { output_options.show_diagnostics = false; } let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?); return Ok(Work::new(move |state| { // Artifacts are in a different location than typical units, // hence we must assure the crate- and target-dependent // directory is present. if artifact.is_true() { paths::create_dir_all(&root)?; } // Only at runtime have we discovered what the extra -L and -l // arguments are for native libraries, so we process those here. We // also need to be sure to add any -L paths for our plugins to the // dynamic library load path as a plugin's dynamic library may be // located somewhere in there. // Finally, if custom environment variables have been produced by // previous build scripts, we include them in the rustc invocation. if let Some(build_scripts) = build_scripts { let script_outputs = build_script_outputs.lock().unwrap(); add_native_deps( &mut rustc, &script_outputs, &build_scripts, pass_l_flag, &target, current_id, mode, )?; if let Some(ref root_output) = root_output { add_plugin_deps(&mut rustc, &script_outputs, &build_scripts, root_output)?; } add_custom_flags(&mut rustc, &script_outputs, script_metadatas)?; } for output in outputs.iter() { // If there is both an rmeta and rlib, rustc will prefer to use the // rlib, even if it is older. Therefore, we must delete the rlib to // force using the new rmeta. if output.path.extension() == Some(OsStr::new("rmeta")) { let dst = root.join(&output.path).with_extension("rlib"); if dst.exists() { paths::remove_file(&dst)?; } } // Some linkers do not remove the executable, but truncate and modify it. // That results in the old hard-link being modified even after renamed. // We delete the old artifact here to prevent this behavior from confusing users. // See rust-lang/cargo#8348. if output.hardlink.is_some() && output.path.exists() { _ = paths::remove_file(&output.path).map_err(|e| { tracing::debug!( "failed to delete previous output file `{:?}`: {e:?}", output.path ); }); } } state.running(&rustc); let timestamp = paths::set_invocation_time(&fingerprint_dir)?; for file in sbom_files { tracing::debug!("writing sbom to {}", file.display()); let outfile = BufWriter::new(paths::create(&file)?); serde_json::to_writer(outfile, &sbom)?; } let result = exec .exec( &rustc, package_id, &target, mode, &mut |line| on_stdout_line(state, line, package_id, &target), &mut |line| { on_stderr_line( state, line, package_id, &manifest, &target, &mut output_options, ) }, ) .map_err(|e| { if output_options.errors_seen == 0 { // If we didn't expect an error, do not require --verbose to fail. // This is intended to debug // https://github.com/rust-lang/crater/issues/733, where we are seeing // Cargo exit unsuccessfully while seeming to not show any errors. e } else { verbose_if_simple_exit_code(e) } }) .with_context(|| { // adapted from rustc_errors/src/lib.rs let warnings = match output_options.warnings_seen { 0 => String::new(), 1 => "; 1 warning emitted".to_string(), count => format!("; {} warnings emitted", count), }; let errors = match output_options.errors_seen { 0 => String::new(), 1 => " due to 1 previous error".to_string(), count => format!(" due to {} previous errors", count), }; let name = descriptive_pkg_name(&name, &target, &mode); format!("could not compile {name}{errors}{warnings}") }); if let Err(e) = result { if let Some(diagnostic) = failed_scrape_diagnostic { state.warning(diagnostic); } return Err(e); } // Exec should never return with success *and* generate an error. debug_assert_eq!(output_options.errors_seen, 0); if rustc_dep_info_loc.exists() { fingerprint::translate_dep_info( &rustc_dep_info_loc, &dep_info_loc, &cwd, &pkg_root, &build_dir, &rustc, // Do not track source files in the fingerprint for registry dependencies. is_local, &env_config, ) .with_context(|| { internal(format!( "could not parse/generate dep info at: {}", rustc_dep_info_loc.display() )) })?; // This mtime shift allows Cargo to detect if a source file was // modified in the middle of the build. paths::set_file_time_no_err(dep_info_loc, timestamp); } // This mtime shift for .rmeta is a workaround as rustc incremental build // since rust-lang/rust#114669 (1.90.0) skips unnecessary rmeta generation. // // The situation is like this: // // 1. When build script execution's external dependendies // (rerun-if-changed, rerun-if-env-changed) got updated, // the execution unit reran and got a newer mtime. // 2. rustc type-checked the associated crate, though with incremental // compilation, no rmeta regeneration. Its `.rmeta` stays old. // 3. Run `cargo check` again. Cargo found build script execution had // a new mtime than existing crate rmeta, so re-checking the crate. // However the check is a no-op (input has no change), so stuck. if mode.is_check() { for output in outputs.iter() { paths::set_file_time_no_err(&output.path, timestamp); } } Ok(()) })); // Add all relevant `-L` and `-l` flags from dependencies (now calculated and // present in `state`) to the command provided. fn add_native_deps( rustc: &mut ProcessBuilder, build_script_outputs: &BuildScriptOutputs, build_scripts: &BuildScripts, pass_l_flag: bool, target: &Target, current_id: PackageId, mode: CompileMode, ) -> CargoResult<()> { let mut library_paths = vec![]; for key in build_scripts.to_link.iter() { let output = build_script_outputs.get(key.1).ok_or_else(|| { internal(format!( "couldn't find build script output for {}/{}", key.0, key.1 )) })?; library_paths.extend(output.library_paths.iter()); } // NOTE: This very intentionally does not use the derived ord from LibraryPath because we need to // retain relative ordering within the same type (i.e. not lexicographic). The use of a stable sort // is also important here because it ensures that paths of the same type retain the same relative // ordering (for an unstable sort to work here, the list would need to retain the idx of each element // and then sort by that idx when the type is equivalent. library_paths.sort_by_key(|p| match p { LibraryPath::CargoArtifact(_) => 0, LibraryPath::External(_) => 1, }); for path in library_paths.iter() { rustc.arg("-L").arg(path.as_ref()); } for key in build_scripts.to_link.iter() { let output = build_script_outputs.get(key.1).ok_or_else(|| { internal(format!( "couldn't find build script output for {}/{}", key.0, key.1 )) })?; if key.0 == current_id { if pass_l_flag { for name in output.library_links.iter() { rustc.arg("-l").arg(name); } } } for (lt, arg) in &output.linker_args { // There was an unintentional change where cdylibs were // allowed to be passed via transitive dependencies. This // clause should have been kept in the `if` block above. For // now, continue allowing it for cdylib only. // See https://github.com/rust-lang/cargo/issues/9562 if lt.applies_to(target, mode) && (key.0 == current_id || *lt == LinkArgTarget::Cdylib) { rustc.arg("-C").arg(format!("link-arg={}", arg)); } } } Ok(()) } } fn verbose_if_simple_exit_code(err: Error) -> Error { // If a signal on unix (`code == None`) or an abnormal termination // on Windows (codes like `0xC0000409`), don't hide the error details. match err .downcast_ref::() .as_ref() .and_then(|perr| perr.code) { Some(n) if cargo_util::is_simple_exit_code(n) => VerboseError::new(err).into(), _ => err, } } fn prebuild_lock_exclusive(lock: LockKey) -> Work { Work::new(move |state| { state.lock_exclusive(&lock)?; Ok(()) }) } fn downgrade_lock_to_shared(lock: LockKey) -> Work { Work::new(move |state| { state.downgrade_to_shared(&lock)?; Ok(()) }) } /// Link the compiled target (often of form `foo-{metadata_hash}`) to the /// final target. This must happen during both "Fresh" and "Compile". fn link_targets( build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, fresh: bool, ) -> CargoResult { let bcx = build_runner.bcx; let outputs = build_runner.outputs(unit)?; let export_dir = build_runner.files().export_dir(); let package_id = unit.pkg.package_id(); let manifest_path = PathBuf::from(unit.pkg.manifest_path()); let profile = unit.profile.clone(); let unit_mode = unit.mode; let features = unit.features.iter().map(|s| s.to_string()).collect(); let json_messages = bcx.build_config.emit_json(); let executable = build_runner.get_executable(unit)?; let mut target = Target::clone(&unit.target); if let TargetSourcePath::Metabuild = target.src_path() { // Give it something to serialize. let path = unit .pkg .manifest() .metabuild_path(build_runner.bcx.ws.build_dir()); target.set_src_path(TargetSourcePath::Path(path)); } Ok(Work::new(move |state| { // If we're a "root crate", e.g., the target of this compilation, then we // hard link our outputs out of the `deps` directory into the directory // above. This means that `cargo build` will produce binaries in // `target/debug` which one probably expects. let mut destinations = vec![]; for output in outputs.iter() { let src = &output.path; // This may have been a `cargo rustc` command which changes the // output, so the source may not actually exist. if !src.exists() { continue; } let Some(dst) = output.hardlink.as_ref() else { destinations.push(src.clone()); continue; }; destinations.push(dst.clone()); paths::link_or_copy(src, dst)?; if let Some(ref path) = output.export_path { let export_dir = export_dir.as_ref().unwrap(); paths::create_dir_all(export_dir)?; paths::link_or_copy(src, path)?; } } if json_messages { let debuginfo = match profile.debuginfo.into_inner() { TomlDebugInfo::None => machine_message::ArtifactDebuginfo::Int(0), TomlDebugInfo::Limited => machine_message::ArtifactDebuginfo::Int(1), TomlDebugInfo::Full => machine_message::ArtifactDebuginfo::Int(2), TomlDebugInfo::LineDirectivesOnly => { machine_message::ArtifactDebuginfo::Named("line-directives-only") } TomlDebugInfo::LineTablesOnly => { machine_message::ArtifactDebuginfo::Named("line-tables-only") } }; let art_profile = machine_message::ArtifactProfile { opt_level: profile.opt_level.as_str(), debuginfo: Some(debuginfo), debug_assertions: profile.debug_assertions, overflow_checks: profile.overflow_checks, test: unit_mode.is_any_test(), }; let msg = machine_message::Artifact { package_id: package_id.to_spec(), manifest_path, target: &target, profile: art_profile, features, filenames: destinations, executable, fresh, } .to_json_string(); state.stdout(msg)?; } Ok(()) })) } // For all plugin dependencies, add their -L paths (now calculated and present // in `build_script_outputs`) to the dynamic library load path for the command // to execute. fn add_plugin_deps( rustc: &mut ProcessBuilder, build_script_outputs: &BuildScriptOutputs, build_scripts: &BuildScripts, root_output: &Path, ) -> CargoResult<()> { let var = paths::dylib_path_envvar(); let search_path = rustc.get_env(var).unwrap_or_default(); let mut search_path = env::split_paths(&search_path).collect::>(); for (pkg_id, metadata) in &build_scripts.plugins { let output = build_script_outputs .get(*metadata) .ok_or_else(|| internal(format!("couldn't find libs for plugin dep {}", pkg_id)))?; search_path.append(&mut filter_dynamic_search_path( output.library_paths.iter().map(AsRef::as_ref), root_output, )); } let search_path = paths::join_paths(&search_path, var)?; rustc.env(var, &search_path); Ok(()) } fn get_dynamic_search_path(path: &Path) -> &Path { match path.to_str().and_then(|s| s.split_once("=")) { Some(("native" | "crate" | "dependency" | "framework" | "all", path)) => Path::new(path), _ => path, } } // Determine paths to add to the dynamic search path from -L entries // // Strip off prefixes like "native=" or "framework=" and filter out directories // **not** inside our output directory since they are likely spurious and can cause // clashes with system shared libraries (issue #3366). fn filter_dynamic_search_path<'a, I>(paths: I, root_output: &Path) -> Vec where I: Iterator, { let mut search_path = vec![]; for dir in paths { let dir = get_dynamic_search_path(dir); if dir.starts_with(&root_output) { search_path.push(dir.to_path_buf()); } else { debug!( "Not including path {} in runtime library search path because it is \ outside target root {}", dir.display(), root_output.display() ); } } search_path } /// Prepares flags and environments we can compute for a `rustc` invocation /// before the job queue starts compiling any unit. /// /// This builds a static view of the invocation. Flags depending on the /// completion of other units will be added later in runtime, such as flags /// from build scripts. fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult { let gctx = build_runner.bcx.gctx; let is_primary = build_runner.is_primary_package(unit); let is_workspace = build_runner.bcx.ws.is_member(&unit.pkg); let mut base = build_runner .compilation .rustc_process(unit, is_primary, is_workspace)?; build_base_args(build_runner, &mut base, unit)?; if unit.pkg.manifest().is_embedded() { if !gctx.cli_unstable().script { anyhow::bail!( "parsing `{}` requires `-Zscript`", unit.pkg.manifest_path().display() ); } base.arg("-Z").arg("crate-attr=feature(frontmatter)"); base.arg("-Z").arg("crate-attr=allow(unused_features)"); } base.inherit_jobserver(&build_runner.jobserver); build_deps_args(&mut base, build_runner, unit)?; add_cap_lints(build_runner.bcx, unit, &mut base); if let Some(args) = build_runner.bcx.extra_args_for(unit) { base.args(args); } base.args(&unit.rustflags); if gctx.cli_unstable().binary_dep_depinfo { base.arg("-Z").arg("binary-dep-depinfo"); } if build_runner.bcx.gctx.cli_unstable().checksum_freshness { base.arg("-Z").arg("checksum-hash-algorithm=blake3"); } if is_primary { base.env("CARGO_PRIMARY_PACKAGE", "1"); let file_list = build_runner.sbom_output_files(unit)?; if !file_list.is_empty() { let file_list = std::env::join_paths(file_list)?; base.env("CARGO_SBOM_PATH", file_list); } } if unit.target.is_test() || unit.target.is_bench() { let tmp = build_runner .files() .layout(unit.kind) .build_dir() .prepare_tmp()?; base.env("CARGO_TARGET_TMPDIR", tmp.display().to_string()); } Ok(base) } /// Prepares flags and environments we can compute for a `rustdoc` invocation /// before the job queue starts compiling any unit. /// /// This builds a static view of the invocation. Flags depending on the /// completion of other units will be added later in runtime, such as flags /// from build scripts. fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult { let bcx = build_runner.bcx; // script_metadata is not needed here, it is only for tests. let mut rustdoc = build_runner.compilation.rustdoc_process(unit, None)?; if unit.pkg.manifest().is_embedded() { if !bcx.gctx.cli_unstable().script { anyhow::bail!( "parsing `{}` requires `-Zscript`", unit.pkg.manifest_path().display() ); } rustdoc.arg("-Z").arg("crate-attr=feature(frontmatter)"); rustdoc.arg("-Z").arg("crate-attr=allow(unused_features)"); } rustdoc.inherit_jobserver(&build_runner.jobserver); let crate_name = unit.target.crate_name(); rustdoc.arg("--crate-name").arg(&crate_name); add_path_args(bcx.ws, unit, &mut rustdoc); add_cap_lints(bcx, unit, &mut rustdoc); unit.kind.add_target_arg(&mut rustdoc); let doc_dir = build_runner.files().output_dir(unit); rustdoc.arg("-o").arg(&doc_dir); rustdoc.args(&features_args(unit)); rustdoc.args(&check_cfg_args(unit)); add_error_format_and_color(build_runner, &mut rustdoc); add_allow_features(build_runner, &mut rustdoc); if build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo { // html-static-files is required for keeping the shared styling resources // html-non-static-files is required for keeping the original rustdoc emission let mut arg = if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info { // toolchain resources are written at the end, at the same time as merging OsString::from("--emit=html-non-static-files,dep-info=") } else { // if not using mergeable CCI, everything is written every time OsString::from("--emit=html-static-files,html-non-static-files,dep-info=") }; arg.push(rustdoc_dep_info_loc(build_runner, unit)); rustdoc.arg(arg); if build_runner.bcx.gctx.cli_unstable().checksum_freshness { rustdoc.arg("-Z").arg("checksum-hash-algorithm=blake3"); } rustdoc.arg("-Zunstable-options"); } else if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info { // toolchain resources are written at the end, at the same time as merging rustdoc.arg("--emit=html-non-static-files"); rustdoc.arg("-Zunstable-options"); } if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info { // write out mergeable data to be imported rustdoc.arg("--merge=none"); let mut arg = OsString::from("--parts-out-dir="); // `-Zrustdoc-mergeable-info` always uses the new layout. arg.push(build_runner.files().out_dir_new_layout(unit)); rustdoc.arg(arg); } if let Some(trim_paths) = unit.profile.trim_paths.as_ref() { trim_paths_args_rustdoc(&mut rustdoc, build_runner, unit, trim_paths)?; } rustdoc.args(unit.pkg.manifest().lint_rustflags()); let metadata = build_runner.metadata_for_doc_units[unit]; rustdoc .arg("-C") .arg(format!("metadata={}", metadata.c_metadata())); if unit.mode.is_doc_scrape() { debug_assert!(build_runner.bcx.scrape_units.contains(unit)); if unit.target.is_test() { rustdoc.arg("--scrape-tests"); } rustdoc.arg("-Zunstable-options"); rustdoc .arg("--scrape-examples-output-path") .arg(scrape_output_path(build_runner, unit)?); // Only scrape example for items from crates in the workspace, to reduce generated file size for pkg in build_runner.bcx.packages.packages() { let names = pkg .targets() .iter() .map(|target| target.crate_name()) .collect::>(); for name in names { rustdoc.arg("--scrape-examples-target-crate").arg(name); } } } if should_include_scrape_units(build_runner.bcx, unit) { rustdoc.arg("-Zunstable-options"); } build_deps_args(&mut rustdoc, build_runner, unit)?; rustdoc::add_root_urls(build_runner, unit, &mut rustdoc)?; rustdoc::add_output_format(build_runner, &mut rustdoc)?; if let Some(args) = build_runner.bcx.extra_args_for(unit) { rustdoc.args(args); } rustdoc.args(&unit.rustdocflags); if !crate_version_flag_already_present(&rustdoc) { append_crate_version_flag(unit, &mut rustdoc); } Ok(rustdoc) } /// Creates a unit of work invoking `rustdoc` for documenting the `unit`. fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult { let mut rustdoc = prepare_rustdoc(build_runner, unit)?; let crate_name = unit.target.crate_name(); let doc_dir = build_runner.files().output_dir(unit); // Create the documentation directory ahead of time as rustdoc currently has // a bug where concurrent invocations will race to create this directory if // it doesn't already exist. paths::create_dir_all(&doc_dir)?; let target_desc = unit.target.description_named(); let name = unit.pkg.name(); let build_script_outputs = Arc::clone(&build_runner.build_script_outputs); let package_id = unit.pkg.package_id(); let target = Target::clone(&unit.target); let manifest = ManifestErrorContext::new(build_runner, unit); let rustdoc_dep_info_loc = rustdoc_dep_info_loc(build_runner, unit); let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit); let build_dir = build_runner.bcx.ws.build_dir().into_path_unlocked(); let pkg_root = unit.pkg.root().to_path_buf(); let cwd = rustdoc .get_cwd() .unwrap_or_else(|| build_runner.bcx.gctx.cwd()) .to_path_buf(); let fingerprint_dir = build_runner.files().fingerprint_dir(unit); let is_local = unit.is_local(); let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?); let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo; let mut output_options = OutputOptions::new(build_runner, unit); let script_metadatas = build_runner.find_build_script_metadatas(unit); let scrape_outputs = if should_include_scrape_units(build_runner.bcx, unit) { Some( build_runner .bcx .scrape_units .iter() .map(|unit| { Ok(( build_runner.files().metadata(unit).unit_id(), scrape_output_path(build_runner, unit)?, )) }) .collect::>>()?, ) } else { None }; let failed_scrape_units = Arc::clone(&build_runner.failed_scrape_units); let hide_diagnostics_for_scrape_unit = build_runner.bcx.unit_can_fail_for_docscraping(unit) && !matches!( build_runner.bcx.gctx.shell().verbosity(), Verbosity::Verbose ); let failed_scrape_diagnostic = hide_diagnostics_for_scrape_unit.then(|| { make_failed_scrape_diagnostic( build_runner, unit, format_args!("failed to scan {target_desc} in package `{name}` for example code usage"), ) }); if hide_diagnostics_for_scrape_unit { output_options.show_diagnostics = false; } Ok(Work::new(move |state| { add_custom_flags( &mut rustdoc, &build_script_outputs.lock().unwrap(), script_metadatas, )?; // Add the output of scraped examples to the rustdoc command. // This action must happen after the unit's dependencies have finished, // because some of those deps may be Docscrape units which have failed. // So we dynamically determine which `--with-examples` flags to pass here. if let Some(scrape_outputs) = scrape_outputs { let failed_scrape_units = failed_scrape_units.lock().unwrap(); for (metadata, output_path) in &scrape_outputs { if !failed_scrape_units.contains(metadata) { rustdoc.arg("--with-examples").arg(output_path); } } } let crate_dir = doc_dir.join(&crate_name); if crate_dir.exists() { // Remove output from a previous build. This ensures that stale // files for removed items are removed. debug!("removing pre-existing doc directory {:?}", crate_dir); paths::remove_dir_all(crate_dir)?; } state.running(&rustdoc); let timestamp = paths::set_invocation_time(&fingerprint_dir)?; let result = rustdoc .exec_with_streaming( &mut |line| on_stdout_line(state, line, package_id, &target), &mut |line| { on_stderr_line( state, line, package_id, &manifest, &target, &mut output_options, ) }, false, ) .map_err(verbose_if_simple_exit_code) .with_context(|| format!("could not document `{}`", name)); if let Err(e) = result { if let Some(diagnostic) = failed_scrape_diagnostic { state.warning(diagnostic); } return Err(e); } if rustdoc_depinfo_enabled && rustdoc_dep_info_loc.exists() { fingerprint::translate_dep_info( &rustdoc_dep_info_loc, &dep_info_loc, &cwd, &pkg_root, &build_dir, &rustdoc, // Should we track source file for doc gen? is_local, &env_config, ) .with_context(|| { internal(format_args!( "could not parse/generate dep info at: {}", rustdoc_dep_info_loc.display() )) })?; // This mtime shift allows Cargo to detect if a source file was // modified in the middle of the build. paths::set_file_time_no_err(dep_info_loc, timestamp); } Ok(()) })) } // The --crate-version flag could have already been passed in RUSTDOCFLAGS // or as an extra compiler argument for rustdoc fn crate_version_flag_already_present(rustdoc: &ProcessBuilder) -> bool { rustdoc.get_args().any(|flag| { flag.to_str() .map_or(false, |flag| flag.starts_with(RUSTDOC_CRATE_VERSION_FLAG)) }) } fn append_crate_version_flag(unit: &Unit, rustdoc: &mut ProcessBuilder) { rustdoc .arg(RUSTDOC_CRATE_VERSION_FLAG) .arg(unit.pkg.version().to_string()); } /// Adds [`--cap-lints`] to the command to execute. /// /// [`--cap-lints`]: https://doc.rust-lang.org/nightly/rustc/lints/levels.html#capping-lints fn add_cap_lints(bcx: &BuildContext<'_, '_>, unit: &Unit, cmd: &mut ProcessBuilder) { // If this is an upstream dep we don't want warnings from, turn off all // lints. if !unit.show_warnings(bcx.gctx) { cmd.arg("--cap-lints").arg("allow"); // If this is an upstream dep but we *do* want warnings, make sure that they // don't fail compilation. } else if !unit.is_local() { cmd.arg("--cap-lints").arg("warn"); } } /// Forwards [`-Zallow-features`] if it is set for cargo. /// /// [`-Zallow-features`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#allow-features fn add_allow_features(build_runner: &BuildRunner<'_, '_>, cmd: &mut ProcessBuilder) { if let Some(allow) = &build_runner.bcx.gctx.cli_unstable().allow_features { use std::fmt::Write; let mut arg = String::from("-Zallow-features="); for f in allow { let _ = write!(&mut arg, "{f},"); } cmd.arg(arg.trim_end_matches(',')); } } /// Adds [`--error-format`] to the command to execute. /// /// Cargo always uses JSON output. This has several benefits, such as being /// easier to parse, handles changing formats (for replaying cached messages), /// ensures atomic output (so messages aren't interleaved), allows for /// intercepting messages like rmeta artifacts, etc. rustc includes a /// "rendered" field in the JSON message with the message properly formatted, /// which Cargo will extract and display to the user. /// /// [`--error-format`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#--error-format-control-how-errors-are-produced fn add_error_format_and_color(build_runner: &BuildRunner<'_, '_>, cmd: &mut ProcessBuilder) { let enable_timings = build_runner.bcx.gctx.cli_unstable().section_timings && build_runner.bcx.logger.is_some(); if enable_timings { cmd.arg("-Zunstable-options"); } cmd.arg("--error-format=json"); let mut json = String::from("--json=diagnostic-rendered-ansi,artifacts,future-incompat"); if let MessageFormat::Short | MessageFormat::Json { short: true, .. } = build_runner.bcx.build_config.message_format { json.push_str(",diagnostic-short"); } else if build_runner.bcx.gctx.shell().err_unicode() && build_runner.bcx.gctx.cli_unstable().rustc_unicode { json.push_str(",diagnostic-unicode"); } if enable_timings { json.push_str(",timings"); } cmd.arg(json); let gctx = build_runner.bcx.gctx; if let Some(width) = gctx.shell().err_width().diagnostic_terminal_width() { cmd.arg(format!("--diagnostic-width={width}")); } } /// Adds essential rustc flags and environment variables to the command to execute. fn build_base_args( build_runner: &BuildRunner<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit, ) -> CargoResult<()> { assert!(!unit.mode.is_run_custom_build()); let bcx = build_runner.bcx; let Profile { ref opt_level, codegen_backend, codegen_units, debuginfo, debug_assertions, split_debuginfo, overflow_checks, rpath, ref panic, incremental, strip, rustflags: profile_rustflags, trim_paths, hint_mostly_unused: profile_hint_mostly_unused, .. } = unit.profile.clone(); let hints = unit.pkg.hints().cloned().unwrap_or_default(); let test = unit.mode.is_any_test(); let warn = |msg: &str| { bcx.gctx.shell().warn(format!( "{}@{}: {msg}", unit.pkg.package_id().name(), unit.pkg.package_id().version() )) }; let unit_capped_warn = |msg: &str| { if unit.show_warnings(bcx.gctx) { warn(msg) } else { Ok(()) } }; cmd.arg("--crate-name").arg(&unit.target.crate_name()); let edition = unit.target.edition(); edition.cmd_edition_arg(cmd); add_path_args(bcx.ws, unit, cmd); add_error_format_and_color(build_runner, cmd); add_allow_features(build_runner, cmd); let mut contains_dy_lib = false; if !test { for crate_type in &unit.target.rustc_crate_types() { cmd.arg("--crate-type").arg(crate_type.as_str()); contains_dy_lib |= crate_type == &CrateType::Dylib; } } if unit.mode.is_check() { cmd.arg("--emit=dep-info,metadata"); } else if build_runner.bcx.gctx.cli_unstable().no_embed_metadata { // Nightly rustc supports the -Zembed-metadata=no flag, which tells it to avoid including // full metadata in rlib/dylib artifacts, to save space on disk. In this case, metadata // will only be stored in .rmeta files. // When we use this flag, we should also pass --emit=metadata to all artifacts that // contain useful metadata (rlib/dylib/proc macros), so that a .rmeta file is actually // generated. If we didn't do this, the full metadata would not get written anywhere. // However, we do not want to pass --emit=metadata to artifacts that never produce useful // metadata, such as binaries, because that would just unnecessarily create empty .rmeta // files on disk. if unit.benefits_from_no_embed_metadata() { cmd.arg("--emit=dep-info,metadata,link"); cmd.args(&["-Z", "embed-metadata=no"]); } else { cmd.arg("--emit=dep-info,link"); } } else { // If we don't use -Zembed-metadata=no, we emit .rmeta files only for rlib outputs. // This metadata may be used in this session for a pipelined compilation, or it may // be used in a future Cargo session as part of a pipelined compile. if !unit.requires_upstream_objects() { cmd.arg("--emit=dep-info,metadata,link"); } else { cmd.arg("--emit=dep-info,link"); } } let prefer_dynamic = (unit.target.for_host() && !unit.target.is_custom_build()) || (contains_dy_lib && !build_runner.is_primary_package(unit)); if prefer_dynamic { cmd.arg("-C").arg("prefer-dynamic"); } if opt_level.as_str() != "0" { cmd.arg("-C").arg(&format!("opt-level={}", opt_level)); } if *panic != PanicStrategy::Unwind { cmd.arg("-C").arg(format!("panic={}", panic)); } if *panic == PanicStrategy::ImmediateAbort { cmd.arg("-Z").arg("unstable-options"); } cmd.args(<o_args(build_runner, unit)); if let Some(backend) = codegen_backend { cmd.arg("-Z").arg(&format!("codegen-backend={}", backend)); } if let Some(n) = codegen_units { cmd.arg("-C").arg(&format!("codegen-units={}", n)); } let debuginfo = debuginfo.into_inner(); // Shorten the number of arguments if possible. if debuginfo != TomlDebugInfo::None { cmd.arg("-C").arg(format!("debuginfo={debuginfo}")); // This is generally just an optimization on build time so if we don't // pass it then it's ok. The values for the flag (off, packed, unpacked) // may be supported or not depending on the platform, so availability is // checked per-value. For example, at the time of writing this code, on // Windows the only stable valid value for split-debuginfo is "packed", // while on Linux "unpacked" is also stable. if let Some(split) = split_debuginfo { if build_runner .bcx .target_data .info(unit.kind) .supports_debuginfo_split(split) { cmd.arg("-C").arg(format!("split-debuginfo={split}")); } } } if let Some(trim_paths) = trim_paths { trim_paths_args(cmd, build_runner, unit, &trim_paths)?; } cmd.args(unit.pkg.manifest().lint_rustflags()); cmd.args(&profile_rustflags); // `-C overflow-checks` is implied by the setting of `-C debug-assertions`, // so we only need to provide `-C overflow-checks` if it differs from // the value of `-C debug-assertions` we would provide. if opt_level.as_str() != "0" { if debug_assertions { cmd.args(&["-C", "debug-assertions=on"]); if !overflow_checks { cmd.args(&["-C", "overflow-checks=off"]); } } else if overflow_checks { cmd.args(&["-C", "overflow-checks=on"]); } } else if !debug_assertions { cmd.args(&["-C", "debug-assertions=off"]); if overflow_checks { cmd.args(&["-C", "overflow-checks=on"]); } } else if !overflow_checks { cmd.args(&["-C", "overflow-checks=off"]); } if test && unit.target.harness() { cmd.arg("--test"); // Cargo has historically never compiled `--test` binaries with // `panic=abort` because the `test` crate itself didn't support it. // Support is now upstream, however, but requires an unstable flag to be // passed when compiling the test. We require, in Cargo, an unstable // flag to pass to rustc, so register that here. Eventually this flag // will simply not be needed when the behavior is stabilized in the Rust // compiler itself. if *panic == PanicStrategy::Abort || *panic == PanicStrategy::ImmediateAbort { cmd.arg("-Z").arg("panic-abort-tests"); } } else if test { cmd.arg("--cfg").arg("test"); } cmd.args(&features_args(unit)); cmd.args(&check_cfg_args(unit)); let meta = build_runner.files().metadata(unit); cmd.arg("-C") .arg(&format!("metadata={}", meta.c_metadata())); if let Some(c_extra_filename) = meta.c_extra_filename() { cmd.arg("-C") .arg(&format!("extra-filename=-{c_extra_filename}")); } if rpath { cmd.arg("-C").arg("rpath"); } cmd.arg("--out-dir") .arg(&build_runner.files().output_dir(unit)); unit.kind.add_target_arg(cmd); add_codegen_linker(cmd, build_runner, unit, bcx.gctx.target_applies_to_host()?); if incremental { add_codegen_incremental(cmd, build_runner, unit) } let pkg_hint_mostly_unused = match hints.mostly_unused { None => None, Some(toml::Value::Boolean(b)) => Some(b), Some(v) => { unit_capped_warn(&format!( "ignoring unsupported value type ({}) for 'hints.mostly-unused', which expects a boolean", v.type_str() ))?; None } }; if profile_hint_mostly_unused .or(pkg_hint_mostly_unused) .unwrap_or(false) { if bcx.gctx.cli_unstable().profile_hint_mostly_unused { cmd.arg("-Zhint-mostly-unused"); } else { if profile_hint_mostly_unused.is_some() { // Profiles come from the top-level unit, so we don't use `unit_capped_warn` here. warn( "ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it", )?; } else if pkg_hint_mostly_unused.is_some() { unit_capped_warn( "ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it", )?; } } } let strip = strip.into_inner(); if strip != StripInner::None { cmd.arg("-C").arg(format!("strip={}", strip)); } if unit.is_std { // -Zforce-unstable-if-unmarked prevents the accidental use of // unstable crates within the sysroot (such as "extern crate libc" or // any non-public crate in the sysroot). // // RUSTC_BOOTSTRAP allows unstable features on stable. cmd.arg("-Z") .arg("force-unstable-if-unmarked") .env("RUSTC_BOOTSTRAP", "1"); } Ok(()) } /// All active features for the unit passed as `--cfg features=`. fn features_args(unit: &Unit) -> Vec { let mut args = Vec::with_capacity(unit.features.len() * 2); for feat in &unit.features { args.push(OsString::from("--cfg")); args.push(OsString::from(format!("feature=\"{}\"", feat))); } args } /// Like [`trim_paths_args`] but for rustdoc invocations. fn trim_paths_args_rustdoc( cmd: &mut ProcessBuilder, build_runner: &BuildRunner<'_, '_>, unit: &Unit, trim_paths: &TomlTrimPaths, ) -> CargoResult<()> { match trim_paths { // rustdoc supports diagnostics trimming only. TomlTrimPaths::Values(values) if !values.contains(&TomlTrimPathsValue::Diagnostics) => { return Ok(()); } _ => {} } // feature gate was checked during manifest/config parsing. cmd.arg("-Zunstable-options"); // Order of `--remap-path-prefix` flags is important for `-Zbuild-std`. // We want to show `/rustc//library/std` instead of `std-0.0.0`. cmd.arg(package_remap(build_runner, unit)); cmd.arg(build_dir_remap(build_runner)); cmd.arg(sysroot_remap(build_runner, unit)); Ok(()) } /// Generates the `--remap-path-scope` and `--remap-path-prefix` for [RFC 3127]. /// See also unstable feature [`-Ztrim-paths`]. /// /// [RFC 3127]: https://rust-lang.github.io/rfcs/3127-trim-paths.html /// [`-Ztrim-paths`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-trim-paths-option fn trim_paths_args( cmd: &mut ProcessBuilder, build_runner: &BuildRunner<'_, '_>, unit: &Unit, trim_paths: &TomlTrimPaths, ) -> CargoResult<()> { if trim_paths.is_none() { return Ok(()); } // feature gate was checked during manifest/config parsing. cmd.arg(format!("--remap-path-scope={trim_paths}")); // Order of `--remap-path-prefix` flags is important for `-Zbuild-std`. // We want to show `/rustc//library/std` instead of `std-0.0.0`. cmd.arg(package_remap(build_runner, unit)); cmd.arg(build_dir_remap(build_runner)); cmd.arg(sysroot_remap(build_runner, unit)); Ok(()) } /// Path prefix remap rules for sysroot. /// /// This remap logic aligns with rustc: /// fn sysroot_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OsString { let mut remap = OsString::from("--remap-path-prefix="); remap.push({ // See also `detect_sysroot_src_path()`. let mut sysroot = build_runner.bcx.target_data.info(unit.kind).sysroot.clone(); sysroot.push("lib"); sysroot.push("rustlib"); sysroot.push("src"); sysroot.push("rust"); sysroot }); remap.push("="); remap.push("/rustc/"); if let Some(commit_hash) = build_runner.bcx.rustc().commit_hash.as_ref() { remap.push(commit_hash); } else { remap.push(build_runner.bcx.rustc().version.to_string()); } remap } /// Path prefix remap rules for dependencies. /// /// * Git dependencies: remove `~/.cargo/git/checkouts` prefix. /// * Registry dependencies: remove `~/.cargo/registry/src` prefix. /// * Others (e.g. path dependencies): /// * relative paths to workspace root if inside the workspace directory. /// * otherwise remapped to `-`. fn package_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OsString { let pkg_root = unit.pkg.root(); let ws_root = build_runner.bcx.ws.root(); let mut remap = OsString::from("--remap-path-prefix="); let source_id = unit.pkg.package_id().source_id(); if source_id.is_git() { remap.push( build_runner .bcx .gctx .git_checkouts_path() .as_path_unlocked(), ); remap.push("="); } else if source_id.is_registry() { remap.push( build_runner .bcx .gctx .registry_source_path() .as_path_unlocked(), ); remap.push("="); } else if pkg_root.strip_prefix(ws_root).is_ok() { remap.push(ws_root); remap.push("=."); // remap to relative rustc work dir explicitly } else { remap.push(pkg_root); remap.push("="); remap.push(unit.pkg.name()); remap.push("-"); remap.push(unit.pkg.version().to_string()); } remap } /// Remap all paths pointing to `build.build-dir`, /// i.e., `[BUILD_DIR]/debug/deps/foo-[HASH].dwo` would be remapped to /// `/cargo/build-dir/debug/deps/foo-[HASH].dwo` /// (note the `/cargo/build-dir` prefix). /// /// This covers scenarios like: /// /// * Build script generated code. For example, a build script may call `file!` /// macros, and the associated crate uses [`include!`] to include the expanded /// [`file!`] macro in-place via the `OUT_DIR` environment. /// * On Linux, `DW_AT_GNU_dwo_name` that contains paths to split debuginfo /// files (dwp and dwo). fn build_dir_remap(build_runner: &BuildRunner<'_, '_>) -> OsString { let build_dir = build_runner.bcx.ws.build_dir(); let mut remap = OsString::from("--remap-path-prefix="); remap.push(build_dir.as_path_unlocked()); remap.push("=/cargo/build-dir"); remap } /// Generates the `--check-cfg` arguments for the `unit`. fn check_cfg_args(unit: &Unit) -> Vec { // The routine below generates the --check-cfg arguments. Our goals here are to // enable the checking of conditionals and pass the list of declared features. // // In the simplified case, it would resemble something like this: // // --check-cfg=cfg() --check-cfg=cfg(feature, values(...)) // // but having `cfg()` is redundant with the second argument (as well-known names // and values are implicitly enabled when one or more `--check-cfg` argument is // passed) so we don't emit it and just pass: // // --check-cfg=cfg(feature, values(...)) // // This way, even if there are no declared features, the config `feature` will // still be expected, meaning users would get "unexpected value" instead of name. // This wasn't always the case, see rust-lang#119930 for some details. let gross_cap_estimation = unit.pkg.summary().features().len() * 7 + 25; let mut arg_feature = OsString::with_capacity(gross_cap_estimation); arg_feature.push("cfg(feature, values("); for (i, feature) in unit.pkg.summary().features().keys().enumerate() { if i != 0 { arg_feature.push(", "); } arg_feature.push("\""); arg_feature.push(feature); arg_feature.push("\""); } arg_feature.push("))"); // In addition to the package features, we also include the `test` cfg (since // compiler-team#785, as to be able to someday apply it conditionally), as well // the `docsrs` cfg from the docs.rs service. // // We include `docsrs` here (in Cargo) instead of rustc, since there is a much closer // relationship between Cargo and docs.rs than rustc and docs.rs. In particular, all // users of docs.rs use Cargo, but not all users of rustc (like Rust-for-Linux) use docs.rs. vec![ OsString::from("--check-cfg"), OsString::from("cfg(docsrs,test)"), OsString::from("--check-cfg"), arg_feature, ] } /// Adds LTO related codegen flags. fn lto_args(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec { let mut result = Vec::new(); let mut push = |arg: &str| { result.push(OsString::from("-C")); result.push(OsString::from(arg)); }; match build_runner.lto[unit] { lto::Lto::Run(None) => push("lto"), lto::Lto::Run(Some(s)) => push(&format!("lto={}", s)), lto::Lto::Off => { push("lto=off"); push("embed-bitcode=no"); } lto::Lto::ObjectAndBitcode => {} // this is rustc's default lto::Lto::OnlyBitcode => push("linker-plugin-lto"), lto::Lto::OnlyObject => push("embed-bitcode=no"), } result } /// Adds dependency-relevant rustc flags and environment variables /// to the command to execute, such as [`-L`] and [`--extern`]. /// /// [`-L`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#-l-add-a-directory-to-the-library-search-path /// [`--extern`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#--extern-specify-where-an-external-library-is-located fn build_deps_args( cmd: &mut ProcessBuilder, build_runner: &BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult<()> { let bcx = build_runner.bcx; for arg in lib_search_paths(build_runner, unit)? { cmd.arg(arg); } let deps = build_runner.unit_deps(unit); // If there is not one linkable target but should, rustc fails later // on if there is an `extern crate` for it. This may turn into a hard // error in the future (see PR #4797). if !deps .iter() .any(|dep| !dep.unit.mode.is_doc() && dep.unit.target.is_linkable()) { if let Some(dep) = deps.iter().find(|dep| { !dep.unit.mode.is_doc() && dep.unit.target.is_lib() && !dep.unit.artifact.is_true() }) { let dep_name = dep.unit.target.crate_name(); let name = unit.target.crate_name(); bcx.gctx.shell().print_report(&[ Level::WARNING.secondary_title(format!("the package `{dep_name}` provides no linkable target")) .elements([ Level::NOTE.message(format!("this might cause `{name}` to fail compilation")), Level::NOTE.message("this warning might turn into a hard error in the future"), Level::HELP.message(format!("consider adding 'dylib' or 'rlib' to key 'crate-type' in `{dep_name}`'s Cargo.toml")) ]) ], false)?; } } let mut unstable_opts = false; // Add `OUT_DIR` environment variables for build scripts let first_custom_build_dep = deps.iter().find(|dep| dep.unit.mode.is_run_custom_build()); if let Some(dep) = first_custom_build_dep { let out_dir = if bcx.gctx.cli_unstable().build_dir_new_layout { build_runner.files().out_dir_new_layout(&dep.unit) } else { build_runner.files().build_script_out_dir(&dep.unit) }; cmd.env("OUT_DIR", &out_dir); } // Adding output directory for each build script let is_multiple_build_scripts_enabled = unit .pkg .manifest() .unstable_features() .require(Feature::multiple_build_scripts()) .is_ok(); if is_multiple_build_scripts_enabled { for dep in deps { if dep.unit.mode.is_run_custom_build() { let out_dir = if bcx.gctx.cli_unstable().build_dir_new_layout { build_runner.files().out_dir_new_layout(&dep.unit) } else { build_runner.files().build_script_out_dir(&dep.unit) }; let target_name = dep.unit.target.name(); let out_dir_prefix = target_name .strip_prefix("build-script-") .unwrap_or(target_name); let out_dir_name = format!("{out_dir_prefix}_OUT_DIR"); cmd.env(&out_dir_name, &out_dir); } } } for arg in extern_args(build_runner, unit, &mut unstable_opts)? { cmd.arg(arg); } for (var, env) in artifact::get_env(build_runner, unit, deps)? { cmd.env(&var, env); } // This will only be set if we're already using a feature // requiring nightly rust if unstable_opts { cmd.arg("-Z").arg("unstable-options"); } Ok(()) } fn add_dep_arg<'a, 'b: 'a>( map: &mut BTreeMap<&'a Unit, PathBuf>, build_runner: &'b BuildRunner<'b, '_>, unit: &'a Unit, ) { if map.contains_key(&unit) { return; } map.insert(&unit, build_runner.files().deps_dir(&unit)); for dep in build_runner.unit_deps(unit) { add_dep_arg(map, build_runner, &dep.unit); } } /// Adds extra rustc flags and environment variables collected from the output /// of a build-script to the command to execute, include custom environment /// variables and `cfg`. fn add_custom_flags( cmd: &mut ProcessBuilder, build_script_outputs: &BuildScriptOutputs, metadata_vec: Option>, ) -> CargoResult<()> { if let Some(metadata_vec) = metadata_vec { for metadata in metadata_vec { if let Some(output) = build_script_outputs.get(metadata) { for cfg in output.cfgs.iter() { cmd.arg("--cfg").arg(cfg); } for check_cfg in &output.check_cfgs { cmd.arg("--check-cfg").arg(check_cfg); } for (name, value) in output.env.iter() { cmd.env(name, value); } } } } Ok(()) } /// Generate a list of `-L` arguments pub fn lib_search_paths( build_runner: &BuildRunner<'_, '_>, unit: &Unit, ) -> CargoResult> { let mut lib_search_paths = Vec::new(); if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout { let mut map = BTreeMap::new(); // Recursively add all dependency args to rustc process add_dep_arg(&mut map, build_runner, unit); let paths = map.into_iter().map(|(_, path)| path).sorted_unstable(); for path in paths { let mut deps = OsString::from("dependency="); deps.push(path); lib_search_paths.extend(["-L".into(), deps]); } } else { let mut deps = OsString::from("dependency="); deps.push(build_runner.files().deps_dir(unit)); lib_search_paths.extend(["-L".into(), deps]); } // Be sure that the host path is also listed. This'll ensure that proc macro // dependencies are correctly found (for reexported macros). if !unit.kind.is_host() { let mut deps = OsString::from("dependency="); deps.push(build_runner.files().host_deps(unit)); lib_search_paths.extend(["-L".into(), deps]); } Ok(lib_search_paths) } /// Generates a list of `--extern` arguments. pub fn extern_args( build_runner: &BuildRunner<'_, '_>, unit: &Unit, unstable_opts: &mut bool, ) -> CargoResult> { let mut result = Vec::new(); let deps = build_runner.unit_deps(unit); let no_embed_metadata = build_runner.bcx.gctx.cli_unstable().no_embed_metadata; // Closure to add one dependency to `result`. let mut link_to = |dep: &UnitDep, extern_crate_name: InternedString, noprelude: bool, nounused: bool| -> CargoResult<()> { let mut value = OsString::new(); let mut opts = Vec::new(); let is_public_dependency_enabled = unit .pkg .manifest() .unstable_features() .require(Feature::public_dependency()) .is_ok() || build_runner.bcx.gctx.cli_unstable().public_dependency; if !dep.public && unit.target.is_lib() && is_public_dependency_enabled { opts.push("priv"); *unstable_opts = true; } if noprelude { opts.push("noprelude"); *unstable_opts = true; } if nounused { opts.push("nounused"); *unstable_opts = true; } if !opts.is_empty() { value.push(opts.join(",")); value.push(":"); } value.push(extern_crate_name.as_str()); value.push("="); let mut pass = |file| { let mut value = value.clone(); value.push(file); result.push(OsString::from("--extern")); result.push(value); }; let outputs = build_runner.outputs(&dep.unit)?; if build_runner.only_requires_rmeta(unit, &dep.unit) || dep.unit.mode.is_check() { // Example: rlib dependency for an rlib, rmeta is all that is required. let output = outputs .iter() .find(|output| output.flavor == FileFlavor::Rmeta) .expect("failed to find rmeta dep for pipelined dep"); pass(&output.path); } else { // Example: a bin needs `rlib` for dependencies, it cannot use rmeta. for output in outputs.iter() { if output.flavor == FileFlavor::Linkable { pass(&output.path); } // If we use -Zembed-metadata=no, we also need to pass the path to the // corresponding .rmeta file to the linkable artifact, because the // normal dependency (rlib) doesn't contain the full metadata. else if no_embed_metadata && output.flavor == FileFlavor::Rmeta { pass(&output.path); } } } Ok(()) }; for dep in deps { if dep.unit.target.is_linkable() && !dep.unit.mode.is_doc() { link_to(dep, dep.extern_crate_name, dep.noprelude, dep.nounused)?; } } if unit.target.proc_macro() { // Automatically import `proc_macro`. result.push(OsString::from("--extern")); result.push(OsString::from("proc_macro")); } Ok(result) } /// Adds `-C linker=` if specified. fn add_codegen_linker( cmd: &mut ProcessBuilder, build_runner: &BuildRunner<'_, '_>, unit: &Unit, target_applies_to_host: bool, ) { let linker = if unit.target.for_host() && !target_applies_to_host { build_runner .compilation .host_linker() .map(|s| s.as_os_str()) } else { build_runner .compilation .target_linker(unit.kind) .map(|s| s.as_os_str()) }; if let Some(linker) = linker { let mut arg = OsString::from("linker="); arg.push(linker); cmd.arg("-C").arg(arg); } } /// Adds `-C incremental=`. fn add_codegen_incremental( cmd: &mut ProcessBuilder, build_runner: &BuildRunner<'_, '_>, unit: &Unit, ) { let dir = build_runner.files().incremental_dir(&unit); let mut arg = OsString::from("incremental="); arg.push(dir.as_os_str()); cmd.arg("-C").arg(arg); } fn envify(s: &str) -> String { s.chars() .flat_map(|c| c.to_uppercase()) .map(|c| if c == '-' { '_' } else { c }) .collect() } /// Configuration of the display of messages emitted by the compiler, /// e.g. diagnostics, warnings, errors, and message caching. struct OutputOptions { /// What format we're emitting from Cargo itself. format: MessageFormat, /// Where to write the JSON messages to support playback later if the unit /// is fresh. The file is created lazily so that in the normal case, lots /// of empty files are not created. If this is None, the output will not /// be cached (such as when replaying cached messages). cache_cell: Option<(PathBuf, OnceCell)>, /// If `true`, display any diagnostics. /// Other types of JSON messages are processed regardless /// of the value of this flag. /// /// This is used primarily for cache replay. If you build with `-vv`, the /// cache will be filled with diagnostics from dependencies. When the /// cache is replayed without `-vv`, we don't want to show them. show_diagnostics: bool, /// Tracks the number of warnings we've seen so far. warnings_seen: usize, /// Tracks the number of errors we've seen so far. errors_seen: usize, } impl OutputOptions { fn new(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OutputOptions { let path = build_runner.files().message_cache_path(unit); // Remove old cache, ignore ENOENT, which is the common case. drop(fs::remove_file(&path)); let cache_cell = Some((path, OnceCell::new())); let show_diagnostics = build_runner.bcx.gctx.warning_handling().unwrap_or_default() != WarningHandling::Allow; OutputOptions { format: build_runner.bcx.build_config.message_format, cache_cell, show_diagnostics, warnings_seen: 0, errors_seen: 0, } } } /// Cloned and sendable context about the manifest file. /// /// Sometimes we enrich rustc's errors with some locations in the manifest file; this /// contains a `Send`-able copy of the manifest information that we need for the /// enriched errors. struct ManifestErrorContext { /// The path to the manifest. path: PathBuf, /// The locations of various spans within the manifest. spans: Option>>, /// The raw manifest contents. contents: Option, /// A lookup for all the unambiguous renamings, mapping from the original package /// name to the renamed one. rename_table: HashMap, /// A list of targets we're compiling for, to determine which of the `[target..dependencies]` /// tables might be of interest. requested_kinds: Vec, /// A list of all the collections of cfg values, one collection for each target, to determine /// which of the `[target.'cfg(...)'.dependencies]` tables might be of interest. cfgs: Vec>, host_name: InternedString, /// Cargo's working directory (for printing out a more friendly manifest path). cwd: PathBuf, /// Terminal width for formatting diagnostics. term_width: usize, } fn on_stdout_line( state: &JobState<'_, '_>, line: &str, _package_id: PackageId, _target: &Target, ) -> CargoResult<()> { state.stdout(line.to_string())?; Ok(()) } fn on_stderr_line( state: &JobState<'_, '_>, line: &str, package_id: PackageId, manifest: &ManifestErrorContext, target: &Target, options: &mut OutputOptions, ) -> CargoResult<()> { if on_stderr_line_inner(state, line, package_id, manifest, target, options)? { // Check if caching is enabled. if let Some((path, cell)) = &mut options.cache_cell { // Cache the output, which will be replayed later when Fresh. let f = cell.try_borrow_mut_with(|| paths::create(path))?; debug_assert!(!line.contains('\n')); f.write_all(line.as_bytes())?; f.write_all(&[b'\n'])?; } } Ok(()) } /// Returns true if the line should be cached. fn on_stderr_line_inner( state: &JobState<'_, '_>, line: &str, package_id: PackageId, manifest: &ManifestErrorContext, target: &Target, options: &mut OutputOptions, ) -> CargoResult { // We primarily want to use this function to process JSON messages from // rustc. The compiler should always print one JSON message per line, and // otherwise it may have other output intermingled (think RUST_LOG or // something like that), so skip over everything that doesn't look like a // JSON message. if !line.starts_with('{') { state.stderr(line.to_string())?; return Ok(true); } let mut compiler_message: Box = match serde_json::from_str(line) { Ok(msg) => msg, // If the compiler produced a line that started with `{` but it wasn't // valid JSON, maybe it wasn't JSON in the first place! Forward it along // to stderr. Err(e) => { debug!("failed to parse json: {:?}", e); state.stderr(line.to_string())?; return Ok(true); } }; let count_diagnostic = |level, options: &mut OutputOptions| { if level == "warning" { options.warnings_seen += 1; } else if level == "error" { options.errors_seen += 1; } }; if let Ok(report) = serde_json::from_str::(compiler_message.get()) { for item in &report.future_incompat_report { count_diagnostic(&*item.diagnostic.level, options); } state.future_incompat_report(report.future_incompat_report); return Ok(true); } let res = serde_json::from_str::(compiler_message.get()); if let Ok(timing_record) = res { state.on_section_timing_emitted(timing_record); return Ok(false); } // Returns `true` if the diagnostic was modified. let add_pub_in_priv_diagnostic = |diag: &mut String| -> bool { // We are parsing the compiler diagnostic here, as this information isn't // currently exposed elsewhere. // At the time of writing this comment, rustc emits two different // "exported_private_dependencies" errors: // - type `FromPriv` from private dependency 'priv_dep' in public interface // - struct `FromPriv` from private dependency 'priv_dep' is re-exported // This regex matches them both. To see if it needs to be updated, grep the rust // source for "EXPORTED_PRIVATE_DEPENDENCIES". static PRIV_DEP_REGEX: LazyLock = LazyLock::new(|| Regex::new("from private dependency '([A-Za-z0-9-_]+)'").unwrap()); if let Some(crate_name) = PRIV_DEP_REGEX.captures(diag).and_then(|m| m.get(1)) && let Some(ref contents) = manifest.contents && let Some(span) = manifest.find_crate_span(crate_name.as_str()) { let rel_path = pathdiff::diff_paths(&manifest.path, &manifest.cwd) .unwrap_or_else(|| manifest.path.clone()) .display() .to_string(); let report = [Group::with_title(Level::NOTE.secondary_title(format!( "dependency `{}` declared here", crate_name.as_str() ))) .element( Snippet::source(contents) .path(rel_path) .annotation(AnnotationKind::Context.span(span)), )]; let rendered = Renderer::styled() .term_width(manifest.term_width) .render(&report); diag.push_str(&rendered); diag.push('\n'); return true; } false }; // Depending on what we're emitting from Cargo itself, we figure out what to // do with this JSON message. match options.format { // In the "human" output formats (human/short) or if diagnostic messages // from rustc aren't being included in the output of Cargo's JSON // messages then we extract the diagnostic (if present) here and handle // it ourselves. MessageFormat::Human | MessageFormat::Short | MessageFormat::Json { render_diagnostics: true, .. } => { #[derive(serde::Deserialize)] struct CompilerMessage<'a> { // `rendered` contains escape sequences, which can't be // zero-copy deserialized by serde_json. // See https://github.com/serde-rs/json/issues/742 rendered: String, #[serde(borrow)] message: Cow<'a, str>, #[serde(borrow)] level: Cow<'a, str>, children: Vec, code: Option, } // A partial rustfix::diagnostics::Diagnostic. We deserialize only a // subset of the fields because rustc's output can be extremely // deeply nested JSON in pathological cases involving macro // expansion. Rustfix's Diagnostic struct is recursive containing a // field `children: Vec`, and it can cause deserialization to // hit serde_json's default recursion limit, or overflow the stack // if we turn that off. Cargo only cares about the 1 field listed // here. #[derive(serde::Deserialize)] struct PartialDiagnostic { spans: Vec, } // A partial rustfix::diagnostics::DiagnosticSpan. #[derive(serde::Deserialize)] struct PartialDiagnosticSpan { suggestion_applicability: Option, } #[derive(serde::Deserialize)] struct DiagnosticCode { code: String, } if let Ok(mut msg) = serde_json::from_str::>(compiler_message.get()) { if msg.message.starts_with("aborting due to") || msg.message.ends_with("warning emitted") || msg.message.ends_with("warnings emitted") { // Skip this line; we'll print our own summary at the end. return Ok(true); } // state.stderr will add a newline if msg.rendered.ends_with('\n') { msg.rendered.pop(); } let mut rendered = msg.rendered; if options.show_diagnostics { let machine_applicable: bool = msg .children .iter() .map(|child| { child .spans .iter() .filter_map(|span| span.suggestion_applicability) .any(|app| app == Applicability::MachineApplicable) }) .any(|b| b); count_diagnostic(&msg.level, options); if msg .code .as_ref() .is_some_and(|c| c.code == "exported_private_dependencies") && options.format != MessageFormat::Short { add_pub_in_priv_diagnostic(&mut rendered); } let lint = msg.code.is_some(); state.emit_diag(&msg.level, rendered, lint, machine_applicable)?; } return Ok(true); } } MessageFormat::Json { ansi, .. } => { #[derive(serde::Deserialize, serde::Serialize)] struct CompilerMessage<'a> { rendered: String, #[serde(flatten, borrow)] other: std::collections::BTreeMap, serde_json::Value>, code: Option>, } #[derive(serde::Deserialize, serde::Serialize)] struct DiagnosticCode<'a> { code: String, #[serde(flatten, borrow)] other: std::collections::BTreeMap, serde_json::Value>, } if let Ok(mut error) = serde_json::from_str::>(compiler_message.get()) { let modified_diag = if error .code .as_ref() .is_some_and(|c| c.code == "exported_private_dependencies") { add_pub_in_priv_diagnostic(&mut error.rendered) } else { false }; // Remove color information from the rendered string if color is not // enabled. Cargo always asks for ANSI colors from rustc. This allows // cached replay to enable/disable colors without re-invoking rustc. if !ansi { error.rendered = anstream::adapter::strip_str(&error.rendered).to_string(); } if !ansi || modified_diag { let new_line = serde_json::to_string(&error)?; compiler_message = serde_json::value::RawValue::from_string(new_line)?; } } } } // We always tell rustc to emit messages about artifacts being produced. // These messages feed into pipelined compilation, as well as timing // information. // // Look for a matching directive and inform Cargo internally that a // metadata file has been produced. #[derive(serde::Deserialize)] struct ArtifactNotification<'a> { #[serde(borrow)] artifact: Cow<'a, str>, } if let Ok(artifact) = serde_json::from_str::>(compiler_message.get()) { trace!("found directive from rustc: `{}`", artifact.artifact); if artifact.artifact.ends_with(".rmeta") { debug!("looks like metadata finished early!"); state.rmeta_produced(); } return Ok(false); } // And failing all that above we should have a legitimate JSON diagnostic // from the compiler, so wrap it in an external Cargo JSON message // indicating which package it came from and then emit it. if !options.show_diagnostics { return Ok(true); } #[derive(serde::Deserialize)] struct CompilerMessage<'a> { #[serde(borrow)] message: Cow<'a, str>, #[serde(borrow)] level: Cow<'a, str>, } if let Ok(msg) = serde_json::from_str::>(compiler_message.get()) { if msg.message.starts_with("aborting due to") || msg.message.ends_with("warning emitted") || msg.message.ends_with("warnings emitted") { // Skip this line; we'll print our own summary at the end. return Ok(true); } count_diagnostic(&msg.level, options); } let msg = machine_message::FromCompiler { package_id: package_id.to_spec(), manifest_path: &manifest.path, target, message: compiler_message, } .to_json_string(); // Switch json lines from rustc/rustdoc that appear on stderr to stdout // instead. We want the stdout of Cargo to always be machine parseable as // stderr has our colorized human-readable messages. state.stdout(msg)?; Ok(true) } impl ManifestErrorContext { fn new(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> ManifestErrorContext { let mut duplicates = HashSet::new(); let mut rename_table = HashMap::new(); for dep in build_runner.unit_deps(unit) { let unrenamed_id = dep.unit.pkg.package_id().name(); if duplicates.contains(&unrenamed_id) { continue; } match rename_table.entry(unrenamed_id) { std::collections::hash_map::Entry::Occupied(occ) => { occ.remove_entry(); duplicates.insert(unrenamed_id); } std::collections::hash_map::Entry::Vacant(vac) => { vac.insert(dep.extern_crate_name); } } } let bcx = build_runner.bcx; ManifestErrorContext { path: unit.pkg.manifest_path().to_owned(), spans: unit.pkg.manifest().document().cloned(), contents: unit.pkg.manifest().contents().map(String::from), requested_kinds: bcx.target_data.requested_kinds().to_owned(), host_name: bcx.rustc().host, rename_table, cwd: path_args(build_runner.bcx.ws, unit).1, cfgs: bcx .target_data .requested_kinds() .iter() .map(|k| bcx.target_data.cfg(*k).to_owned()) .collect(), term_width: bcx .gctx .shell() .err_width() .diagnostic_terminal_width() .unwrap_or(annotate_snippets::renderer::DEFAULT_TERM_WIDTH), } } fn requested_target_names(&self) -> impl Iterator { self.requested_kinds.iter().map(|kind| match kind { CompileKind::Host => &self.host_name, CompileKind::Target(target) => target.short_name(), }) } /// Find a span for the dependency that specifies this unrenamed crate, if it's unique. /// /// rustc diagnostics (at least for public-in-private) mention the un-renamed /// crate: if you have `foo = { package = "bar" }`, the rustc diagnostic will /// say "bar". /// /// This function does its best to find a span for "bar", but it could fail if /// there are multiple candidates: /// /// ```toml /// foo = { package = "bar" } /// baz = { path = "../bar", package = "bar" } /// ``` fn find_crate_span(&self, unrenamed: &str) -> Option> { let Some(ref spans) = self.spans else { return None; }; let orig_name = self.rename_table.get(unrenamed)?.as_str(); if let Some((k, v)) = get_key_value(&spans, &["dependencies", orig_name]) { // We make some effort to find the unrenamed text: in // // ``` // foo = { package = "bar" } // ``` // // we try to find the "bar", but fall back to "foo" if we can't (which might // happen if the renaming took place in the workspace, for example). if let Some(package) = v.get_ref().as_table().and_then(|t| t.get("package")) { return Some(package.span()); } else { return Some(k.span()); } } // The dependency could also be in a target-specific table, like // [target.x86_64-unknown-linux-gnu.dependencies] or // [target.'cfg(something)'.dependencies]. We filter out target tables // that don't match a requested target or a requested cfg. if let Some(target) = spans .as_ref() .get("target") .and_then(|t| t.as_ref().as_table()) { for (platform, platform_table) in target.iter() { match platform.as_ref().parse::() { Ok(Platform::Name(name)) => { if !self.requested_target_names().any(|n| n == name) { continue; } } Ok(Platform::Cfg(cfg_expr)) => { if !self.cfgs.iter().any(|cfgs| cfg_expr.matches(cfgs)) { continue; } } Err(_) => continue, } let Some(platform_table) = platform_table.as_ref().as_table() else { continue; }; if let Some(deps) = platform_table .get("dependencies") .and_then(|d| d.as_ref().as_table()) { if let Some((k, v)) = deps.get_key_value(orig_name) { if let Some(package) = v.get_ref().as_table().and_then(|t| t.get("package")) { return Some(package.span()); } else { return Some(k.span()); } } } } } None } } /// Creates a unit of work that replays the cached compiler message. /// /// Usually used when a job is fresh and doesn't need to recompile. fn replay_output_cache( package_id: PackageId, manifest: ManifestErrorContext, target: &Target, path: PathBuf, format: MessageFormat, show_diagnostics: bool, ) -> Work { let target = target.clone(); let mut options = OutputOptions { format, cache_cell: None, show_diagnostics, warnings_seen: 0, errors_seen: 0, }; Work::new(move |state| { if !path.exists() { // No cached output, probably didn't emit anything. return Ok(()); } // We sometimes have gigabytes of output from the compiler, so avoid // loading it all into memory at once, as that can cause OOM where // otherwise there would be none. let file = paths::open(&path)?; let mut reader = std::io::BufReader::new(file); let mut line = String::new(); loop { let length = reader.read_line(&mut line)?; if length == 0 { break; } let trimmed = line.trim_end_matches(&['\n', '\r'][..]); on_stderr_line(state, trimmed, package_id, &manifest, &target, &mut options)?; line.clear(); } Ok(()) }) } /// Provides a package name with descriptive target information, /// e.g., '`foo` (bin "bar" test)', '`foo` (lib doctest)'. fn descriptive_pkg_name(name: &str, target: &Target, mode: &CompileMode) -> String { let desc_name = target.description_named(); let mode = if mode.is_rustc_test() && !(target.is_test() || target.is_bench()) { " test" } else if mode.is_doc_test() { " doctest" } else if mode.is_doc() { " doc" } else { "" }; format!("`{name}` ({desc_name}{mode})") } /// Applies environment variables from config `[env]` to [`ProcessBuilder`]. pub(crate) fn apply_env_config( gctx: &crate::GlobalContext, cmd: &mut ProcessBuilder, ) -> CargoResult<()> { for (key, value) in gctx.env_config()?.iter() { // never override a value that has already been set by cargo if cmd.get_envs().contains_key(key) { continue; } cmd.env(key, value); } Ok(()) } /// Checks if there are some scrape units waiting to be processed. fn should_include_scrape_units(bcx: &BuildContext<'_, '_>, unit: &Unit) -> bool { unit.mode.is_doc() && bcx.scrape_units.len() > 0 && bcx.ws.unit_needs_doc_scrape(unit) } /// Gets the file path of function call information output from `rustdoc`. fn scrape_output_path(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult { assert!(unit.mode.is_doc() || unit.mode.is_doc_scrape()); build_runner .outputs(unit) .map(|outputs| outputs[0].path.clone()) } /// Gets the dep-info file emitted by rustdoc. fn rustdoc_dep_info_loc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> PathBuf { let mut loc = build_runner.files().fingerprint_file_path(unit, ""); loc.set_extension("d"); loc } ================================================ FILE: src/cargo/core/compiler/output_depinfo.rs ================================================ //! dep-info files for external build system integration. //! See [`output_depinfo`] for more. use cargo_util::paths::normalize_path; use std::collections::{BTreeSet, HashSet}; use std::io::{BufWriter, Write}; use std::path::{Path, PathBuf}; use super::{BuildRunner, FileFlavor, Unit, fingerprint}; use crate::util::{CargoResult, internal}; use cargo_util::paths; use tracing::debug; /// Basically just normalizes a given path and converts it to a string. fn render_filename>(path: P, basedir: Option<&str>) -> CargoResult { fn wrap_path(path: &Path) -> CargoResult { path.to_str() .ok_or_else(|| internal(format!("path `{:?}` not utf-8", path))) .map(|f| f.replace(" ", "\\ ")) } let path = path.as_ref(); if let Some(basedir) = basedir { let norm_path = normalize_path(path); let norm_basedir = normalize_path(basedir.as_ref()); match norm_path.strip_prefix(norm_basedir) { Ok(relpath) => wrap_path(relpath), _ => wrap_path(path), } } else { wrap_path(path) } } /// Collects all dependencies of the `unit` for the output dep info file. /// /// Dependencies will be stored in `deps`, including: /// /// * dependencies from [fingerprint dep-info] /// * paths from `rerun-if-changed` build script instruction /// * ...and traverse transitive dependencies recursively /// /// [fingerprint dep-info]: super::fingerprint#fingerprint-dep-info-files fn add_deps_for_unit( deps: &mut BTreeSet, build_runner: &mut BuildRunner<'_, '_>, unit: &Unit, visited: &mut HashSet, ) -> CargoResult<()> { if !visited.insert(unit.clone()) { return Ok(()); } // units representing the execution of a build script don't actually // generate a dep info file, so we just keep on going below if !unit.mode.is_run_custom_build() { // Add dependencies from rustc dep-info output (stored in fingerprint directory) let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit); if let Some(paths) = fingerprint::parse_dep_info( unit.pkg.root(), build_runner.files().host_build_root(), &dep_info_loc, )? { for path in paths.files.into_keys() { deps.insert(path); } } else { debug!( "can't find dep_info for {:?} {}", unit.pkg.package_id(), unit.target ); return Err(internal("dep_info missing")); } } // Add rerun-if-changed dependencies if let Some(metadata_vec) = build_runner.find_build_script_metadatas(unit) { for metadata in metadata_vec { if let Some(output) = build_runner .build_script_outputs .lock() .unwrap() .get(metadata) { for path in &output.rerun_if_changed { let package_root = unit.pkg.root(); let path = if path.as_os_str().is_empty() { // Joining with an empty path causes Rust to add a trailing path separator. // On Windows, this would add an invalid trailing backslash to the .d file. package_root.to_path_buf() } else { // The paths we have saved from the unit are of arbitrary relativeness and // may be relative to the crate root of the dependency. package_root.join(path) }; deps.insert(path); } } } } // Recursively traverse all transitive dependencies let unit_deps = Vec::from(build_runner.unit_deps(unit)); // Create vec due to mutable borrow. for dep in unit_deps { if dep.unit.is_local() { add_deps_for_unit(deps, build_runner, &dep.unit, visited)?; } } Ok(()) } /// Save a `.d` dep-info file for the given unit. This is the third kind of /// dep-info mentioned in [`fingerprint`] module. /// /// Argument `unit` is expected to be the root unit, which will be uplifted. /// /// Cargo emits its own dep-info files in the output directory. This is /// only done for every "uplifted" artifact. These are intended to be used /// with external build systems so that they can detect if Cargo needs to be /// re-executed. /// /// It includes all the entries from the `rustc` dep-info file, and extends it /// with any `rerun-if-changed` entries from build scripts. It also includes /// sources from any path dependencies. Registry dependencies are not included /// under the assumption that changes to them can be detected via changes to /// `Cargo.lock`. /// /// [`fingerprint`]: super::fingerprint#dep-info-files pub fn output_depinfo(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<()> { let bcx = build_runner.bcx; let mut deps = BTreeSet::new(); let mut visited = HashSet::new(); let success = add_deps_for_unit(&mut deps, build_runner, unit, &mut visited).is_ok(); let basedir_string; let basedir = match bcx.gctx.build_config()?.dep_info_basedir.clone() { Some(value) => { basedir_string = value .resolve_path(bcx.gctx) .as_os_str() .to_str() .ok_or_else(|| anyhow::format_err!("build.dep-info-basedir path not utf-8"))? .to_string(); Some(basedir_string.as_str()) } None => None, }; let deps = deps .iter() .map(|f| render_filename(f, basedir)) .collect::>>()?; for output in build_runner.outputs(unit)?.iter().filter(|o| { !matches!( o.flavor, FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom ) }) { if let Some(ref link_dst) = output.hardlink { let output_path = link_dst.with_extension("d"); if success { let target_fn = render_filename(link_dst, basedir)?; // If nothing changed don't recreate the file which could alter // its mtime if let Ok(previous) = fingerprint::parse_rustc_dep_info(&output_path) { if previous .files .iter() .map(|(path, _checksum)| path) .eq(deps.iter().map(Path::new)) { continue; } } // Otherwise write it all out let mut outfile = BufWriter::new(paths::create(output_path)?); write!(outfile, "{}:", target_fn)?; for dep in &deps { write!(outfile, " {}", dep)?; } writeln!(outfile)?; // dep-info generation failed, so delete output file. This will // usually cause the build system to always rerun the build // rule, which is correct if inefficient. } else if output_path.exists() { paths::remove_file(output_path)?; } } } Ok(()) } ================================================ FILE: src/cargo/core/compiler/output_sbom.rs ================================================ //! cargo-sbom precursor files for external tools to create SBOM files from. //! See [`build_sbom_graph`] for more. use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::path::PathBuf; use cargo_util_schemas::core::PackageIdSpec; use itertools::Itertools; use serde::Serialize; use crate::CargoResult; use crate::core::TargetKind; use crate::util::Rustc; use crate::util::interning::InternedString; use super::{BuildRunner, CompileMode, Unit}; /// Typed version of a SBOM format version number. #[derive(Serialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] pub struct SbomFormatVersion(u32); #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize)] #[serde(rename_all = "snake_case")] enum SbomDependencyType { /// A dependency linked to the artifact produced by this unit. Normal, /// A dependency needed to run the build for this unit (e.g. a build script or proc-macro). /// The dependency is not linked to the artifact produced by this unit. Build, } #[derive(Serialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] struct SbomIndex(usize); #[derive(Serialize, Clone, Debug)] #[serde(rename_all = "snake_case")] struct SbomDependency { index: SbomIndex, kind: SbomDependencyType, } #[derive(Serialize, Clone, Debug)] #[serde(rename_all = "snake_case")] struct SbomCrate { id: PackageIdSpec, features: Vec, dependencies: Vec, kind: TargetKind, } impl SbomCrate { pub fn new(unit: &Unit) -> Self { let package_id = unit.pkg.package_id().to_spec(); let features = unit.features.iter().map(|f| f.to_string()).collect_vec(); Self { id: package_id, features, dependencies: Vec::new(), kind: unit.target.kind().clone(), } } } #[derive(Serialize, Clone)] #[serde(rename_all = "snake_case")] struct SbomRustc { version: String, wrapper: Option, workspace_wrapper: Option, commit_hash: Option, host: String, verbose_version: String, } impl From<&Rustc> for SbomRustc { fn from(rustc: &Rustc) -> Self { Self { version: rustc.version.to_string(), wrapper: rustc.wrapper.clone(), workspace_wrapper: rustc.workspace_wrapper.clone(), commit_hash: rustc.commit_hash.clone(), host: rustc.host.to_string(), verbose_version: rustc.verbose_version.clone(), } } } #[derive(Serialize)] #[serde(rename_all = "snake_case")] pub struct Sbom { version: SbomFormatVersion, root: SbomIndex, crates: Vec, rustc: SbomRustc, target: InternedString, } /// Build an [`Sbom`] for the given [`Unit`]. pub fn build_sbom(build_runner: &BuildRunner<'_, '_>, root: &Unit) -> CargoResult { let bcx = build_runner.bcx; let rustc: SbomRustc = bcx.rustc().into(); let mut crates = Vec::new(); let sbom_graph = build_sbom_graph(build_runner, root); // Build set of indices for each node in the graph for fast lookup. let indices: HashMap<&Unit, SbomIndex> = sbom_graph .keys() .enumerate() .map(|(i, dep)| (*dep, SbomIndex(i))) .collect(); // Add a item to the crates list for each node in the graph. for (unit, edges) in sbom_graph { let mut krate = SbomCrate::new(unit); for (dep, kind) in edges { krate.dependencies.push(SbomDependency { index: indices[dep], kind: kind, }); } crates.push(krate); } let target = match root.kind { super::CompileKind::Host => build_runner.bcx.host_triple(), super::CompileKind::Target(target) => target.rustc_target(), }; Ok(Sbom { version: SbomFormatVersion(1), crates, root: indices[root], rustc, target, }) } /// List all dependencies, including transitive ones. A dependency can also appear multiple times /// if it's using different settings, e.g. profile, features or crate versions. /// /// Returns a graph of dependencies. fn build_sbom_graph<'a>( build_runner: &'a BuildRunner<'_, '_>, root: &'a Unit, ) -> BTreeMap<&'a Unit, BTreeSet<(&'a Unit, SbomDependencyType)>> { tracing::trace!("building sbom graph for {}", root.pkg.package_id()); let mut queue = Vec::new(); let mut sbom_graph: BTreeMap<&Unit, BTreeSet<(&Unit, SbomDependencyType)>> = BTreeMap::new(); let mut visited = HashSet::new(); // Search to collect all dependencies of the root unit. queue.push((root, root, false)); while let Some((node, parent, is_build_dep)) = queue.pop() { let dependencies = sbom_graph.entry(parent).or_default(); for dep in build_runner.unit_deps(node) { let dep = &dep.unit; let (next_parent, next_is_build_dep) = if dep.mode == CompileMode::RunCustomBuild { // Nodes in the SBOM graph for building/running build scripts are moved on to their parent as build dependencies. (parent, true) } else { // Proc-macros and build scripts are marked as build dependencies. let dep_type = match is_build_dep || dep.target.proc_macro() { false => SbomDependencyType::Normal, true => SbomDependencyType::Build, }; dependencies.insert((dep, dep_type)); tracing::trace!( "adding sbom edge {} -> {} ({:?})", parent.pkg.package_id(), dep.pkg.package_id(), dep_type, ); (dep, false) }; if visited.insert(dep) { queue.push((dep, next_parent, next_is_build_dep)); } } } sbom_graph } ================================================ FILE: src/cargo/core/compiler/rustdoc.rs ================================================ //! Utilities for building with rustdoc. use crate::core::compiler::build_runner::BuildRunner; use crate::core::compiler::unit::Unit; use crate::core::compiler::{BuildContext, CompileKind}; use crate::sources::CRATES_IO_REGISTRY; use crate::util::errors::{CargoResult, internal}; use cargo_util::ProcessBuilder; use std::collections::HashMap; use std::collections::HashSet; use std::fmt; use std::hash; use url::Url; const DOCS_RS_URL: &'static str = "https://docs.rs/"; /// Mode used for `std`. This is for unstable feature [`-Zrustdoc-map`][1]. /// /// [1]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#rustdoc-map #[derive(Debug, Hash)] pub enum RustdocExternMode { /// Use a local `file://` URL. Local, /// Use a remote URL to (default). Remote, /// An arbitrary URL. Url(String), } impl From for RustdocExternMode { fn from(s: String) -> RustdocExternMode { match s.as_ref() { "local" => RustdocExternMode::Local, "remote" => RustdocExternMode::Remote, _ => RustdocExternMode::Url(s), } } } impl fmt::Display for RustdocExternMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { RustdocExternMode::Local => "local".fmt(f), RustdocExternMode::Remote => "remote".fmt(f), RustdocExternMode::Url(s) => s.fmt(f), } } } impl<'de> serde::de::Deserialize<'de> for RustdocExternMode { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { let s = String::deserialize(deserializer)?; Ok(s.into()) } } /// A map of registry names to URLs where documentations are hosted. /// This is for unstable feature [`-Zrustdoc-map`][1]. /// /// [1]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#rustdoc-map #[derive(serde::Deserialize, Debug)] #[serde(default)] pub struct RustdocExternMap { #[serde(deserialize_with = "default_crates_io_to_docs_rs")] /// * Key is the registry name in the configuration `[registries.]`. /// * Value is the URL where the documentation is hosted. registries: HashMap, std: Option, } impl Default for RustdocExternMap { fn default() -> Self { Self { registries: HashMap::from([(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into())]), std: None, } } } fn default_crates_io_to_docs_rs<'de, D: serde::Deserializer<'de>>( de: D, ) -> Result, D::Error> { use serde::Deserialize; let mut registries = HashMap::deserialize(de)?; if !registries.contains_key(CRATES_IO_REGISTRY) { registries.insert(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into()); } Ok(registries) } impl hash::Hash for RustdocExternMap { fn hash(&self, into: &mut H) { self.std.hash(into); for (key, value) in &self.registries { key.hash(into); value.hash(into); } } } /// Recursively generate html root url for all units and their children. /// /// This is needed because in case there is a reexport of foreign reexport, you /// need to have information about grand-children deps level (deps of your deps). fn build_all_urls( build_runner: &BuildRunner<'_, '_>, rustdoc: &mut ProcessBuilder, unit: &Unit, name2url: &HashMap<&String, Url>, map: &RustdocExternMap, unstable_opts: &mut bool, seen: &mut HashSet, ) { for dep in build_runner.unit_deps(unit) { if !seen.insert(dep.unit.clone()) { continue; } if !dep.unit.target.is_linkable() || dep.unit.mode.is_doc() { continue; } for (registry, location) in &map.registries { let sid = dep.unit.pkg.package_id().source_id(); let matches_registry = || -> bool { if !sid.is_registry() { return false; } if sid.is_crates_io() { return registry == CRATES_IO_REGISTRY; } if let Some(index_url) = name2url.get(registry) { return index_url == sid.url(); } false }; if matches_registry() { let mut url = location.clone(); if !url.contains("{pkg_name}") && !url.contains("{version}") { if !url.ends_with('/') { url.push('/'); } url.push_str("{pkg_name}/{version}/"); } let url = url .replace("{pkg_name}", &dep.unit.pkg.name()) .replace("{version}", &dep.unit.pkg.version().to_string()); rustdoc.arg("--extern-html-root-url"); rustdoc.arg(format!("{}={}", dep.unit.target.crate_name(), url)); *unstable_opts = true; } } build_all_urls( build_runner, rustdoc, &dep.unit, name2url, map, unstable_opts, seen, ); } } /// Adds unstable flag [`--extern-html-root-url`][1] to the given `rustdoc` /// invocation. This is for unstable feature [`-Zrustdoc-map`][2]. /// /// [1]: https://doc.rust-lang.org/nightly/rustdoc/unstable-features.html#--extern-html-root-url-control-how-rustdoc-links-to-non-local-crates /// [2]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#rustdoc-map pub fn add_root_urls( build_runner: &BuildRunner<'_, '_>, unit: &Unit, rustdoc: &mut ProcessBuilder, ) -> CargoResult<()> { let gctx = build_runner.bcx.gctx; if !gctx.cli_unstable().rustdoc_map { tracing::debug!("`doc.extern-map` ignored, requires -Zrustdoc-map flag"); return Ok(()); } let map = gctx.doc_extern_map()?; let mut unstable_opts = false; // Collect mapping of registry name -> index url. let name2url: HashMap<&String, Url> = map .registries .keys() .filter_map(|name| { if let Ok(index_url) = gctx.get_registry_index(name) { Some((name, index_url)) } else { tracing::warn!( "`doc.extern-map.{}` specifies a registry that is not defined", name ); None } }) .collect(); build_all_urls( build_runner, rustdoc, unit, &name2url, map, &mut unstable_opts, &mut HashSet::new(), ); let std_url = match &map.std { None | Some(RustdocExternMode::Remote) => None, Some(RustdocExternMode::Local) => { let sysroot = &build_runner.bcx.target_data.info(CompileKind::Host).sysroot; let html_root = sysroot.join("share").join("doc").join("rust").join("html"); if html_root.exists() { let url = Url::from_file_path(&html_root).map_err(|()| { internal(format!( "`{}` failed to convert to URL", html_root.display() )) })?; Some(url.to_string()) } else { tracing::warn!( "`doc.extern-map.std` is \"local\", but local docs don't appear to exist at {}", html_root.display() ); None } } Some(RustdocExternMode::Url(s)) => Some(s.to_string()), }; if let Some(url) = std_url { for name in &["std", "core", "alloc", "proc_macro"] { rustdoc.arg("--extern-html-root-url"); rustdoc.arg(format!("{}={}", name, url)); unstable_opts = true; } } if unstable_opts { rustdoc.arg("-Zunstable-options"); } Ok(()) } /// Adds unstable flag [`--output-format`][1] to the given `rustdoc` /// invocation. This is for unstable feature `-Zunstable-features`. /// /// [1]: https://doc.rust-lang.org/nightly/rustdoc/unstable-features.html?highlight=output-format#-w--output-format-output-format pub fn add_output_format( build_runner: &BuildRunner<'_, '_>, rustdoc: &mut ProcessBuilder, ) -> CargoResult<()> { let gctx = build_runner.bcx.gctx; if !gctx.cli_unstable().unstable_options { tracing::debug!("`unstable-options` is ignored, required -Zunstable-options flag"); return Ok(()); } if build_runner.bcx.build_config.intent.wants_doc_json_output() { rustdoc.arg("-Zunstable-options"); rustdoc.arg("--output-format=json"); } Ok(()) } /// Indicates whether a target should have examples scraped from it by rustdoc. /// Configured within Cargo.toml and only for unstable feature /// [`-Zrustdoc-scrape-examples`][1]. /// /// [1]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#scrape-examples #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy)] pub enum RustdocScrapeExamples { Enabled, Disabled, Unset, } impl RustdocScrapeExamples { pub fn is_enabled(&self) -> bool { matches!(self, RustdocScrapeExamples::Enabled) } pub fn is_unset(&self) -> bool { matches!(self, RustdocScrapeExamples::Unset) } } impl BuildContext<'_, '_> { /// Returns the set of [`Docscrape`] units that have a direct dependency on `unit`. /// /// [`RunCustomBuild`] units are excluded because we allow failures /// from type checks but not build script executions. /// A plain old `cargo doc` would just die if a build script execution fails, /// there is no reason for `-Zrustdoc-scrape-examples` to keep going. /// /// [`Docscrape`]: crate::core::compiler::CompileMode::Docscrape /// [`RunCustomBuild`]: crate::core::compiler::CompileMode::Docscrape pub fn scrape_units_have_dep_on<'a>(&'a self, unit: &'a Unit) -> Vec<&'a Unit> { self.scrape_units .iter() .filter(|scrape_unit| { self.unit_graph[scrape_unit] .iter() .any(|dep| &dep.unit == unit && !dep.unit.mode.is_run_custom_build()) }) .collect() } /// Returns true if this unit is needed for doing doc-scraping and is also /// allowed to fail without killing the build. pub fn unit_can_fail_for_docscraping(&self, unit: &Unit) -> bool { // If the unit is not a Docscrape unit, e.g. a Lib target that is // checked to scrape an Example target, then we need to get the doc-scrape-examples // configuration for the reverse-dependent Example target. let for_scrape_units = if unit.mode.is_doc_scrape() { vec![unit] } else { self.scrape_units_have_dep_on(unit) }; if for_scrape_units.is_empty() { false } else { // All Docscrape units must have doc-scrape-examples unset. If any are true, // then the unit is not allowed to fail. for_scrape_units .iter() .all(|unit| unit.target.doc_scrape_examples().is_unset()) } } } ================================================ FILE: src/cargo/core/compiler/standard_lib.rs ================================================ //! Code for building the standard library. use crate::core::compiler::UnitInterner; use crate::core::compiler::unit_dependencies::IsArtifact; use crate::core::compiler::{CompileKind, CompileMode, RustcTargetData, Unit}; use crate::core::profiles::{Profiles, UnitFor}; use crate::core::resolver::HasDevUnits; use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; use crate::core::{PackageId, PackageSet, Resolve, Workspace}; use crate::ops::{self, Packages}; use crate::util::errors::CargoResult; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use super::BuildConfig; fn std_crates<'a>(crates: &'a [String], default: &'static str, units: &[Unit]) -> HashSet<&'a str> { let mut crates = HashSet::from_iter(crates.iter().map(|s| s.as_str())); // This is a temporary hack until there is a more principled way to // declare dependencies in Cargo.toml. if crates.is_empty() { crates.insert(default); } if crates.contains("std") { crates.insert("core"); crates.insert("alloc"); crates.insert("proc_macro"); crates.insert("panic_unwind"); crates.insert("compiler_builtins"); // Only build libtest if it looks like it is needed (libtest depends on libstd) // If we know what units we're building, we can filter for libtest depending on the jobs. if units .iter() .any(|unit| unit.mode.is_rustc_test() && unit.target.harness()) { crates.insert("test"); } } else if crates.contains("core") { crates.insert("compiler_builtins"); } crates } /// Resolve the standard library dependencies. /// /// * `crates` is the arg value from `-Zbuild-std`. pub fn resolve_std<'gctx>( ws: &Workspace<'gctx>, target_data: &mut RustcTargetData<'gctx>, build_config: &BuildConfig, crates: &[String], kinds: &[CompileKind], ) -> CargoResult<(PackageSet<'gctx>, Resolve, ResolvedFeatures)> { let src_path = detect_sysroot_src_path(target_data)?; let std_ws_manifest_path = src_path.join("Cargo.toml"); let gctx = ws.gctx(); // TODO: Consider doing something to enforce --locked? Or to prevent the // lock file from being written, such as setting ephemeral. let mut std_ws = Workspace::new(&std_ws_manifest_path, gctx)?; // Don't require optional dependencies in this workspace, aka std's own // `[dev-dependencies]`. No need for us to generate a `Resolve` which has // those included because we'll never use them anyway. std_ws.set_require_optional_deps(false); let specs = { // If there is anything looks like needing std, resolve with it. // If not, we assume only `core` maye be needed, as `core the most fundamental crate. // // This may need a UI overhaul if `build-std` wants to fully support multi-targets. let maybe_std = kinds .iter() .any(|kind| target_data.info(*kind).maybe_support_std()); let mut crates = std_crates(crates, if maybe_std { "std" } else { "core" }, &[]); // `sysroot` is not in the default set because it is optional, but it needs // to be part of the resolve in case we do need it or `libtest`. crates.insert("sysroot"); let specs = Packages::Packages(crates.into_iter().map(Into::into).collect()); specs.to_package_id_specs(&std_ws)? }; let features = match &gctx.cli_unstable().build_std_features { Some(list) => list.clone(), None => vec![ "panic-unwind".to_string(), "backtrace".to_string(), "default".to_string(), ], }; let cli_features = CliFeatures::from_command_line( &features, /*all_features*/ false, /*uses_default_features*/ false, )?; let dry_run = false; let mut resolve = ops::resolve_ws_with_opts( &std_ws, target_data, &build_config.requested_kinds, &cli_features, &specs, HasDevUnits::No, crate::core::resolver::features::ForceAllTargets::No, dry_run, )?; debug_assert_eq!(resolve.specs_and_features.len(), 1); Ok(( resolve.pkg_set, resolve.targeted_resolve, resolve .specs_and_features .pop() .expect("resolve should have a single spec with resolved features") .resolved_features, )) } /// Generates a map of root units for the standard library for each kind requested. /// /// * `crates` is the arg value from `-Zbuild-std`. /// * `units` is the root units of the build. pub fn generate_std_roots( crates: &[String], units: &[Unit], std_resolve: &Resolve, std_features: &ResolvedFeatures, kinds: &[CompileKind], package_set: &PackageSet<'_>, interner: &UnitInterner, profiles: &Profiles, target_data: &RustcTargetData<'_>, ) -> CargoResult>> { // Generate a map of Units for each kind requested. let mut ret = HashMap::new(); let (maybe_std, maybe_core): (Vec<&CompileKind>, Vec<_>) = kinds .iter() .partition(|kind| target_data.info(**kind).maybe_support_std()); for (default_crate, kinds) in [("core", maybe_core), ("std", maybe_std)] { if kinds.is_empty() { continue; } generate_roots( &mut ret, default_crate, crates, units, std_resolve, std_features, &kinds, package_set, interner, profiles, target_data, )?; } Ok(ret) } fn generate_roots( ret: &mut HashMap>, default: &'static str, crates: &[String], units: &[Unit], std_resolve: &Resolve, std_features: &ResolvedFeatures, kinds: &[&CompileKind], package_set: &PackageSet<'_>, interner: &UnitInterner, profiles: &Profiles, target_data: &RustcTargetData<'_>, ) -> CargoResult<()> { let std_ids = std_crates(crates, default, units) .iter() .map(|crate_name| std_resolve.query(crate_name)) .collect::>>()?; let std_pkgs = package_set.get_many(std_ids)?; for pkg in std_pkgs { let lib = pkg .targets() .iter() .find(|t| t.is_lib()) .expect("std has a lib"); // I don't think we need to bother with Check here, the difference // in time is minimal, and the difference in caching is // significant. let mode = CompileMode::Build; let features = std_features.activated_features(pkg.package_id(), FeaturesFor::NormalOrDev); for kind in kinds { let kind = **kind; let list = ret.entry(kind).or_insert_with(Vec::new); let unit_for = UnitFor::new_normal(kind); let profile = profiles.get_profile( pkg.package_id(), /*is_member*/ false, /*is_local*/ false, unit_for, kind, ); list.push(interner.intern( pkg, lib, profile, kind, mode, features.clone(), target_data.info(kind).rustflags.clone(), target_data.info(kind).rustdocflags.clone(), target_data.target_config(kind).links_overrides.clone(), /*is_std*/ true, /*dep_hash*/ 0, IsArtifact::No, None, false, )); } } Ok(()) } fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult { if let Some(s) = target_data.gctx.get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") { return Ok(s.into()); } // NOTE: This is temporary until we figure out how to acquire the source. let src_path = target_data .info(CompileKind::Host) .sysroot .join("lib") .join("rustlib") .join("src") .join("rust") .join("library"); let lock = src_path.join("Cargo.lock"); if !lock.exists() { let msg = format!( "{:?} does not exist, unable to build with the standard \ library, try:\n rustup component add rust-src", lock ); match target_data.gctx.get_env("RUSTUP_TOOLCHAIN") { Ok(rustup_toolchain) => { anyhow::bail!("{} --toolchain {}", msg, rustup_toolchain); } Err(_) => { anyhow::bail!(msg); } } } Ok(src_path) } ================================================ FILE: src/cargo/core/compiler/timings/mod.rs ================================================ //! Timing tracking. //! //! This module implements some simple tracking information for timing of how //! long it takes for different units to compile. pub mod report; use super::CompileMode; use super::Unit; use super::UnitIndex; use crate::core::compiler::BuildContext; use crate::core::compiler::BuildRunner; use crate::core::compiler::job_queue::JobId; use crate::ops::cargo_report::timings::prepare_context; use crate::util::cpu::State; use crate::util::log_message::LogMessage; use crate::util::style; use crate::util::{CargoResult, GlobalContext}; use cargo_util::paths; use std::collections::HashMap; use std::io::BufWriter; use std::time::{Duration, Instant}; /// Tracking information for the entire build. /// /// Methods on this structure are generally called from the main thread of a /// running [`JobQueue`] instance (`DrainState` in specific) when the queue /// receives messages from spawned off threads. /// /// [`JobQueue`]: super::JobQueue pub struct Timings<'gctx> { gctx: &'gctx GlobalContext, /// Whether or not timings should be captured. enabled: bool, /// When Cargo started. start: Instant, /// A summary of the root units. /// /// A map from unit to index. unit_to_index: HashMap, /// Units that are in the process of being built. /// When they finished, they are moved to `unit_times`. active: HashMap, /// Last recorded state of the system's CPUs and when it happened last_cpu_state: Option, last_cpu_recording: Instant, /// Recorded CPU states, stored as tuples. First element is when the /// recording was taken and second element is percentage usage of the /// system. cpu_usage: Vec<(f64, f64)>, } /// Section of compilation (e.g. frontend, backend, linking). #[derive(Copy, Clone, serde::Serialize)] pub struct CompilationSection { /// Start of the section, as an offset in seconds from `UnitTime::start`. pub start: f64, /// End of the section, as an offset in seconds from `UnitTime::start`. pub end: Option, } /// Data for a single compilation unit, prepared for serialization to JSON. /// /// This is used by the HTML report's JavaScript to render the pipeline graph. #[derive(serde::Serialize)] pub struct UnitData { pub i: UnitIndex, pub name: String, pub version: String, pub mode: String, pub target: String, pub features: Vec, pub start: f64, pub duration: f64, pub unblocked_units: Vec, pub unblocked_rmeta_units: Vec, pub sections: Option>, } impl<'gctx> Timings<'gctx> { pub fn new(bcx: &BuildContext<'_, 'gctx>) -> Timings<'gctx> { let start = bcx.gctx.creation_time(); let enabled = bcx.logger.is_some(); if !enabled { return Timings { gctx: bcx.gctx, enabled, start, unit_to_index: HashMap::new(), active: HashMap::new(), last_cpu_state: None, last_cpu_recording: Instant::now(), cpu_usage: Vec::new(), }; } let last_cpu_state = match State::current() { Ok(state) => Some(state), Err(e) => { tracing::info!("failed to get CPU state, CPU tracking disabled: {:?}", e); None } }; Timings { gctx: bcx.gctx, enabled, start, unit_to_index: bcx.unit_to_index.clone(), active: HashMap::new(), last_cpu_state, last_cpu_recording: Instant::now(), cpu_usage: Vec::new(), } } /// Mark that a unit has started running. pub fn unit_start(&mut self, build_runner: &BuildRunner<'_, '_>, id: JobId, unit: Unit) { let Some(logger) = build_runner.bcx.logger else { return; }; let mut target = if unit.target.is_lib() && matches!(unit.mode, CompileMode::Build | CompileMode::Check { .. }) { // Special case for brevity, since most dependencies hit this path. "".to_string() } else { format!(" {}", unit.target.description_named()) }; match unit.mode { CompileMode::Test => target.push_str(" (test)"), CompileMode::Build => {} CompileMode::Check { test: true } => target.push_str(" (check-test)"), CompileMode::Check { test: false } => target.push_str(" (check)"), CompileMode::Doc { .. } => target.push_str(" (doc)"), CompileMode::Doctest => target.push_str(" (doc test)"), CompileMode::Docscrape => target.push_str(" (doc scrape)"), CompileMode::RunCustomBuild => target.push_str(" (run)"), } let start = self.start.elapsed().as_secs_f64(); logger.log(LogMessage::UnitStarted { index: self.unit_to_index[&unit], elapsed: start, }); assert!(self.active.insert(id, unit).is_none()); } /// Mark that the `.rmeta` file as generated. pub fn unit_rmeta_finished( &mut self, build_runner: &BuildRunner<'_, '_>, id: JobId, unblocked: Vec<&Unit>, ) { let Some(logger) = build_runner.bcx.logger else { return; }; // `id` may not always be active. "fresh" units unconditionally // generate `Message::Finish`, but this active map only tracks dirty // units. let Some(unit) = self.active.get(&id) else { return; }; let elapsed = self.start.elapsed().as_secs_f64(); let unblocked = unblocked.iter().map(|u| self.unit_to_index[u]).collect(); logger.log(LogMessage::UnitRmetaFinished { index: self.unit_to_index[unit], elapsed, unblocked, }); } /// Mark that a unit has finished running. pub fn unit_finished( &mut self, build_runner: &BuildRunner<'_, '_>, id: JobId, unblocked: Vec<&Unit>, ) { let Some(logger) = build_runner.bcx.logger else { return; }; // See note above in `unit_rmeta_finished`, this may not always be active. let Some(unit) = self.active.remove(&id) else { return; }; let elapsed = self.start.elapsed().as_secs_f64(); let unblocked = unblocked.iter().map(|u| self.unit_to_index[u]).collect(); logger.log(LogMessage::UnitFinished { index: self.unit_to_index[&unit], elapsed, unblocked, }); } /// Handle the start/end of a compilation section. pub fn unit_section_timing( &mut self, build_runner: &BuildRunner<'_, '_>, id: JobId, section_timing: &SectionTiming, ) { let Some(logger) = build_runner.bcx.logger else { return; }; let Some(unit) = self.active.get(&id) else { return; }; let elapsed = self.start.elapsed().as_secs_f64(); let index = self.unit_to_index[&unit]; let section = section_timing.name.clone(); logger.log(match section_timing.event { SectionTimingEvent::Start => LogMessage::UnitSectionStarted { index, elapsed, section, }, SectionTimingEvent::End => LogMessage::UnitSectionFinished { index, elapsed, section, }, }) } /// Take a sample of CPU usage pub fn record_cpu(&mut self) { if !self.enabled { return; } let Some(prev) = &mut self.last_cpu_state else { return; }; // Don't take samples too frequently, even if requested. let now = Instant::now(); if self.last_cpu_recording.elapsed() < Duration::from_millis(100) { return; } let current = match State::current() { Ok(s) => s, Err(e) => { tracing::info!("failed to get CPU state: {:?}", e); return; } }; let pct_idle = current.idle_since(prev); *prev = current; self.last_cpu_recording = now; let dur = now.duration_since(self.start).as_secs_f64(); self.cpu_usage.push((dur, 100.0 - pct_idle)); } /// Call this when all units are finished. pub fn finished( &mut self, build_runner: &BuildRunner<'_, '_>, error: &Option, ) -> CargoResult<()> { if let Some(logger) = build_runner.bcx.logger && let Some(logs) = logger.get_logs() { let timings_path = build_runner .files() .timings_dir() .expect("artifact-dir was not locked"); paths::create_dir_all(&timings_path)?; let run_id = logger.run_id(); let filename = timings_path.join(format!("cargo-timing-{run_id}.html")); let mut f = BufWriter::new(paths::create(&filename)?); let mut ctx = prepare_context(logs.into_iter(), run_id)?; ctx.error = error; ctx.cpu_usage = &self.cpu_usage; report::write_html(ctx, &mut f)?; let unstamped_filename = timings_path.join("cargo-timing.html"); paths::link_or_copy(&filename, &unstamped_filename)?; let mut shell = self.gctx.shell(); let timing_path = std::env::current_dir().unwrap_or_default().join(&filename); let link = shell.err_file_hyperlink(&timing_path); let msg = format!("report saved to {link}{}{link:#}", timing_path.display(),); shell.status_with_color("Timing", msg, &style::NOTE)?; } Ok(()) } } /// Start or end of a section timing. #[derive(serde::Deserialize, Debug)] #[serde(rename_all = "kebab-case")] pub enum SectionTimingEvent { Start, End, } /// Represents a certain section (phase) of rustc compilation. /// It is emitted by rustc when the `--json=timings` flag is used. #[derive(serde::Deserialize, Debug)] pub struct SectionTiming { pub name: String, pub event: SectionTimingEvent, } ================================================ FILE: src/cargo/core/compiler/timings/report.rs ================================================ //! Render HTML report from timing tracking data. use std::borrow::Cow; use std::collections::HashMap; use std::collections::HashSet; use std::io::Write; use indexmap::IndexMap; use itertools::Itertools as _; use crate::CargoResult; use crate::core::compiler::UnitIndex; use super::CompilationSection; use super::UnitData; /// Name of an individual compilation section. #[derive(Clone, Hash, Eq, PartialEq)] pub enum SectionName { Frontend, Codegen, Named(String), Other, } impl SectionName { /// Lower case name. fn name(&self) -> Cow<'static, str> { match self { SectionName::Frontend => "frontend".into(), SectionName::Codegen => "codegen".into(), SectionName::Named(n) => n.to_lowercase().into(), SectionName::Other => "other".into(), } } fn capitalized_name(&self) -> String { // Make the first "letter" uppercase. We could probably just assume ASCII here, but this // should be Unicode compatible. fn capitalize(s: &str) -> String { let first_char = s .chars() .next() .map(|c| c.to_uppercase().to_string()) .unwrap_or_default(); format!("{first_char}{}", s.chars().skip(1).collect::()) } capitalize(&self.name()) } } impl serde::ser::Serialize for SectionName { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.name().serialize(serializer) } } /// Postprocessed section data that has both start and an end. #[derive(Copy, Clone, serde::Serialize)] pub struct SectionData { /// Start (relative to the start of the unit) pub start: f64, /// End (relative to the start of the unit) pub end: f64, } impl SectionData { fn duration(&self) -> f64 { (self.end - self.start).max(0.0) } } /// Concurrency tracking information. #[derive(serde::Serialize)] pub struct Concurrency { /// Time as an offset in seconds from `Timings::start`. t: f64, /// Number of units currently running. active: usize, /// Number of units that could run, but are waiting for a jobserver token. waiting: usize, /// Number of units that are not yet ready, because they are waiting for /// dependencies to finish. inactive: usize, } pub struct RenderContext<'a> { /// A rendered string of when compilation started. pub start_str: String, /// A summary of the root units. /// /// Tuples of `(package_description, target_descriptions)`. pub root_units: Vec<(String, Vec)>, /// The build profile. pub profile: String, /// Total number of fresh units. pub total_fresh: u32, /// Total number of dirty units. pub total_dirty: u32, /// Time tracking for each individual unit. pub unit_data: Vec, /// Concurrency-tracking information. This is periodically updated while /// compilation progresses. pub concurrency: Vec, /// Recorded CPU states, stored as tuples. First element is when the /// recording was taken and second element is percentage usage of the /// system. pub cpu_usage: &'a [(f64, f64)], /// Compiler version info, i.e., `rustc 1.92.0-beta.2 (0a411606e 2025-10-31)`. pub rustc_version: String, /// The host triple (arch-platform-OS). pub host: String, /// The requested target platforms of compilation for this build. pub requested_targets: Vec, /// The number of jobs specified for this build. pub jobs: u32, /// Available parallelism of the compilation environment. pub num_cpus: Option, /// Fatal error during the build. pub error: &'a Option, } /// Writes an HTML report. pub fn write_html(ctx: RenderContext<'_>, f: &mut impl Write) -> CargoResult<()> { // The last concurrency record should equal to the last unit finished time. let duration = ctx.concurrency.last().map(|c| c.t).unwrap_or(0.0); let roots: Vec<&str> = ctx .root_units .iter() .map(|(name, _targets)| name.as_str()) .collect(); f.write_all(HTML_TMPL.replace("{ROOTS}", &roots.join(", ")).as_bytes())?; write_summary_table(&ctx, f, duration)?; f.write_all(HTML_CANVAS.as_bytes())?; write_unit_table(&ctx, f)?; // It helps with pixel alignment to use whole numbers. writeln!( f, "\n\ \n\ \n\ ", include_str!("timings.js") )?; Ok(()) } /// Render the summary table. fn write_summary_table( ctx: &RenderContext<'_>, f: &mut impl Write, duration: f64, ) -> CargoResult<()> { let targets = ctx .root_units .iter() .map(|(name, targets)| format!("{} ({})", name, targets.join(", "))) .collect::>() .join("
"); let total_units = ctx.total_fresh + ctx.total_dirty; let time_human = if duration > 60.0 { format!(" ({}m {:.1}s)", duration as u32 / 60, duration % 60.0) } else { "".to_string() }; let total_time = format!("{:.1}s{}", duration, time_human); let max_concurrency = ctx.concurrency.iter().map(|c| c.active).max().unwrap_or(0); let num_cpus = ctx .num_cpus .map(|x| x.to_string()) .unwrap_or_else(|| "n/a".into()); let requested_targets = ctx.requested_targets.join(", "); let error_msg = match ctx.error { Some(e) => format!(r#"Error:{e}"#), None => "".to_string(), }; let RenderContext { start_str, profile, total_fresh, total_dirty, rustc_version, host, jobs, .. } = &ctx; write!( f, r#" {error_msg}
Targets:{targets}
Profile:{profile}
Fresh units:{total_fresh}
Dirty units:{total_dirty}
Total units:{total_units}
Max concurrency:{max_concurrency} (jobs={jobs} ncpu={num_cpus})
Build start:{start_str}
Total time:{total_time}
rustc:{rustc_version}
Host: {host}
Target: {requested_targets}
"#, )?; Ok(()) } /// Write timing data in JavaScript. Primarily for `timings.js` to put data /// in a ` pub struct Bar; "#, ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = "0.0.1" [profile.dev] trim-paths = "object" "#, ) .file("src/lib.rs", "") .build(); p.cargo("doc -vv -Ztrim-paths") .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) .with_stderr_data(str![[r#" ... [WARNING] unopened HTML tag `script` --> [ROOT]/home/.cargo/registry/src/-[HASH]/bar-0.0.1/src/lib.rs:2:17 ... "#]]) .run(); } #[cargo_test(nightly, reason = "rustdoc --remap-path-prefix is unstable")] fn rustdoc_diagnostics_works() { // This is expected to work after rust-lang/rust#128736 Package::new("bar", "0.0.1") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file( "src/lib.rs", r#" /// pub struct Bar; "#, ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = "0.0.1" [profile.dev] trim-paths = "diagnostics" "#, ) .file("src/lib.rs", "") .build(); p.cargo("doc -vv -Ztrim-paths") .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) .with_stderr_data(str![[r#" ... [RUNNING] `[..]rustc [..]--remap-path-scope=diagnostics --remap-path-prefix=[ROOT]/home/.cargo/registry/src= --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..]` ... [WARNING] unopened HTML tag `script` --> -[..]/bar-0.0.1/src/lib.rs:2:17 ... "#]]) .run(); } ================================================ FILE: tests/testsuite/profiles.rs ================================================ //! Tests for profiles. use std::env; use crate::prelude::*; use cargo_test_support::registry::Package; use cargo_test_support::{project, rustc_host, str}; #[cargo_test] fn profile_overrides() { let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.0.0" edition = "2015" authors = [] [profile.dev] opt-level = 1 debug = false rpath = true "#, ) .file("src/lib.rs", "") .build(); p.cargo("build -v").with_stderr_data(str![[r#" [COMPILING] test v0.0.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name test --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..] -C opt-level=1[..] -C debug-assertions=on[..] -C metadata=[..] -C rpath --out-dir [ROOT]/foo/target/debug/deps [..] -L dependency=[ROOT]/foo/target/debug/deps` [FINISHED] `dev` profile [optimized] target(s) in [ELAPSED]s "#]]).run(); } #[cargo_test] fn opt_level_override_0() { let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.0.0" edition = "2015" authors = [] [profile.dev] opt-level = 0 "#, ) .file("src/lib.rs", "") .build(); p.cargo("build -v").with_stderr_data(str![[r#" [COMPILING] test v0.0.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name test --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C debuginfo=2 [..] -C metadata=[..] --out-dir [ROOT]/foo/target/debug/deps -L dependency=[ROOT]/foo/target/debug/deps` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]).run(); } #[cargo_test] fn debug_override_1() { let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.0.0" edition = "2015" authors = [] [profile.dev] debug = 1 "#, ) .file("src/lib.rs", "") .build(); p.cargo("build -v").with_stderr_data(str![[r#" [COMPILING] test v0.0.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name test --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C debuginfo=1 [..]-C metadata=[..] --out-dir [ROOT]/foo/target/debug/deps -L dependency=[ROOT]/foo/target/debug/deps` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]).run(); } fn check_opt_level_override(profile_level: &str, rustc_level: &str) { let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "test" version = "0.0.0" edition = "2015" authors = [] [profile.dev] opt-level = {level} "#, level = profile_level ), ) .file("src/lib.rs", "") .build(); p.cargo("build -v") .with_stderr_data(&format!( "\ [COMPILING] test v0.0.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name test --edition=2015 src/lib.rs [..]--crate-type lib \ --emit=[..]link \ -C opt-level={level}[..]\ -C debuginfo=2 [..]\ -C debug-assertions=on[..] \ -C metadata=[..] \ --out-dir [..] \ -L dependency=[ROOT]/foo/target/debug/deps` [FINISHED] `dev` profile [..]+ debuginfo] target(s) in [ELAPSED]s ", level = rustc_level )) .run(); } #[cargo_test] fn opt_level_overrides() { for &(profile_level, rustc_level) in &[ ("1", "1"), ("2", "2"), ("3", "3"), ("\"s\"", "s"), ("\"z\"", "z"), ] { check_opt_level_override(profile_level, rustc_level) } } #[cargo_test] fn top_level_overrides_deps() { let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.0.0" edition = "2015" authors = [] [profile.release] opt-level = 1 debug = true [dependencies.foo] path = "foo" "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.0" edition = "2015" authors = [] [profile.release] opt-level = 0 debug = false [lib] name = "foo" crate-type = ["dylib", "rlib"] "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("build -v --release") .with_stderr_data(&format!( "\ [LOCKING] 1 package to latest compatible version [COMPILING] foo v0.0.0 ([ROOT]/foo/foo) [RUNNING] `rustc --crate-name foo --edition=2015 foo/src/lib.rs [..]\ --crate-type dylib --crate-type rlib \ --emit=[..]link \ -C prefer-dynamic \ -C opt-level=1[..]\ -C debuginfo=2 [..]\ -C metadata=[..] \ --out-dir [ROOT]/foo/target/release/deps \ -L dependency=[ROOT]/foo/target/release/deps` [COMPILING] test v0.0.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name test --edition=2015 src/lib.rs [..]--crate-type lib \ --emit=[..]link \ -C opt-level=1[..]\ -C debuginfo=2 [..]\ -C metadata=[..] \ --out-dir [..] \ -L dependency=[ROOT]/foo/target/release/deps \ --extern foo=[ROOT]/foo/target/release/deps/\ {prefix}foo[..]{suffix} \ --extern foo=[ROOT]/foo/target/release/deps/libfoo.rlib` [FINISHED] `release` profile [optimized + debuginfo] target(s) in [ELAPSED]s ", prefix = env::consts::DLL_PREFIX, suffix = env::consts::DLL_SUFFIX )) .run(); } #[cargo_test] fn profile_in_non_root_manifest_triggers_a_warning() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] [profile.dev] debug = false "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = ".." [profile.dev] opt-level = 1 "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); p.cargo("build -v") .cwd("bar") .with_stderr_data(str![[r#" [WARNING] profiles for the non root package will be ignored, specify profiles at the workspace root: package: [ROOT]/foo/bar/Cargo.toml workspace: [ROOT]/foo/Cargo.toml [COMPILING] bar v0.1.0 ([ROOT]/foo/bar) [RUNNING] `rustc [..]` [FINISHED] `dev` profile [unoptimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn profile_in_virtual_manifest_works() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] [profile.dev] opt-level = 1 debug = false "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = ".." "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); p.cargo("build -v") .cwd("bar") .with_stderr_data(str![[r#" [COMPILING] bar v0.1.0 ([ROOT]/foo/bar) [RUNNING] `rustc [..]` [FINISHED] `dev` profile [optimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn profile_lto_string_bool_dev() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [profile.dev] lto = "true" "#, ) .file("src/lib.rs", "") .build(); p.cargo("build") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: `lto` setting of string `"true"` for `dev` profile is not a valid setting, must be a boolean (`true`/`false`) or a string (`"thin"`/`"fat"`/`"off"`) or omitted. "#]]) .run(); } #[cargo_test] fn profile_panic_test_bench() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [profile.test] panic = "abort" [profile.bench] panic = "abort" "#, ) .file("src/lib.rs", "") .build(); p.cargo("build") .with_stderr_data(str![[r#" [WARNING] `panic` setting is ignored for `bench` profile [WARNING] `panic` setting is ignored for `test` profile [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn profile_doc_deprecated() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [profile.doc] opt-level = 0 "#, ) .file("src/lib.rs", "") .build(); p.cargo("build") .with_stderr_data(str![[r#" [WARNING] profile `doc` is deprecated and has no effect ... "#]]) .run(); } #[cargo_test] fn panic_unwind_does_not_build_twice() { // Check for a bug where `lib` was built twice, once with panic set and // once without. Since "unwind" is the default, they are the same and // should only be built once. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [profile.dev] panic = "unwind" "#, ) .file("src/lib.rs", "") .file("src/main.rs", "fn main() {}") .file("tests/t1.rs", "") .build(); p.cargo("test -v --tests --no-run") .with_stderr_data( str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib [..]` [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..] --test [..]` [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]--crate-type bin [..]` [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..] --test [..]` [RUNNING] `rustc --crate-name t1 --edition=2015 tests/t1.rs [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/t1-[HASH][EXE]` "#]] .unordered(), ) .run(); } #[cargo_test] fn debug_0_report() { // The finished line handles 0 correctly. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [profile.dev] debug = 0 "#, ) .file("src/lib.rs", "") .build(); p.cargo("build -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]` [FINISHED] `dev` profile [unoptimized] target(s) in [ELAPSED]s "#]]) .with_stderr_does_not_contain("-C debuginfo") .run(); } #[cargo_test] fn thin_lto_works() { let p = project() .file( "Cargo.toml", r#" [package] name = "top" version = "0.5.0" edition = "2015" authors = [] [profile.release] lto = 'thin' "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build --release -v") .with_stderr_data(str![[r#" [COMPILING] top v0.5.0 ([ROOT]/foo) [RUNNING] `rustc [..] -C lto=thin [..]` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn strip_works() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [profile.release] strip = 'symbols' "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build --release -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] -C strip=symbols [..]` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn strip_passes_unknown_option_to_rustc() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [profile.release] strip = 'unknown' "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build --release -v") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] -C strip=unknown [..]` [ERROR] incorrect value `unknown` for [..] `strip` [..] was expected ... "#]]) .run(); } #[cargo_test] fn strip_accepts_true_to_strip_symbols() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [profile.release] strip = true "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build --release -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] -C strip=symbols [..]` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn strip_accepts_false_to_disable_strip() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [profile.release] strip = false "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build --release -v") .with_stderr_does_not_contain("[RUNNING] `rustc [..] -C strip[..]`") .run(); } #[cargo_test] fn strip_debuginfo_in_release() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build --release -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] -C strip=debuginfo[..]` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("build --release -v --target") .arg(rustc_host()) .with_stderr_data(str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] -C strip=debuginfo[..]` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn strip_debuginfo_without_debug() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [profile.dev] debug = 0 "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] -C strip=debuginfo[..]` [FINISHED] `dev` profile [unoptimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn do_not_strip_debuginfo_with_requested_debug() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = { path = "bar" } [profile.release.package.bar] debug = 1 "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("build --release -v") .with_stderr_does_not_contain("[RUNNING] `rustc [..] -C strip=debuginfo[..]`") .run(); } #[cargo_test] fn rustflags_works() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["profile-rustflags"] [profile.dev] rustflags = ["-C", "link-dead-code=yes"] [package] name = "foo" version = "0.0.1" edition = "2015" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v") .masquerade_as_nightly_cargo(&["profile-rustflags"]) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] -C link-dead-code=yes [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn rustflags_works_with_env() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["profile-rustflags"] [package] name = "foo" version = "0.0.1" edition = "2015" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v") .env("CARGO_PROFILE_DEV_RUSTFLAGS", "-C link-dead-code=yes") .masquerade_as_nightly_cargo(&["profile-rustflags"]) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] -C link-dead-code=yes [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn rustflags_requires_cargo_feature() { let p = project() .file( "Cargo.toml", r#" [profile.dev] rustflags = ["-C", "link-dead-code=yes"] [package] name = "foo" version = "0.0.1" edition = "2015" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v") .masquerade_as_nightly_cargo(&["profile-rustflags"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: feature `profile-rustflags` is required The package requires the Cargo feature called `profile-rustflags`, but that feature is not stabilized in this version of Cargo (1.[..]). Consider adding `cargo-features = ["profile-rustflags"]` to the top of Cargo.toml (above the [package] table) to tell Cargo you are opting in to use this unstable feature. See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-rustflags-option for more information about the status of this feature. "#]]) .run(); Package::new("bar", "1.0.0").publish(); p.change_file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = "1.0" [profile.dev.package.bar] rustflags = ["-C", "link-dead-code=yes"] "#, ); p.cargo("check") .masquerade_as_nightly_cargo(&["profile-rustflags"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: feature `profile-rustflags` is required The package requires the Cargo feature called `profile-rustflags`, but that feature is not stabilized in this version of Cargo (1.[..]). Consider adding `cargo-features = ["profile-rustflags"]` to the top of Cargo.toml (above the [package] table) to tell Cargo you are opting in to use this unstable feature. See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-rustflags-option for more information about the status of this feature. "#]]) .run(); } #[cargo_test] fn debug_options_valid() { let build = |option| { let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" authors = [] version = "0.0.0" edition = "2015" [profile.dev] debug = "{option}" "# ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v") }; for (option, cli) in [ ("line-directives-only", "line-directives-only"), ("line-tables-only", "line-tables-only"), ("limited", "1"), ("full", "2"), ] { build(option) .with_stderr_data(&format!( "\ ... [RUNNING] `rustc [..]-C debuginfo={cli} [..]` ... " )) .run(); } build("none") .with_stderr_does_not_contain("[..]-C debuginfo[..]") .run(); } #[cargo_test] fn profile_hint_mostly_unused_warn_without_gate() { Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = "1.0" [profile.dev.package.bar] hint-mostly-unused = true "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -v") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [WARNING] bar@1.0.0: ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_stderr_does_not_contain("-Zhint-mostly-unused") .run(); } #[cargo_test(nightly, reason = "-Zhint-mostly-unused is unstable")] fn profile_hint_mostly_unused_nightly() { Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = "1.0" [profile.dev.package.bar] hint-mostly-unused = true "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -Zprofile-hint-mostly-unused -v") .masquerade_as_nightly_cargo(&["profile-hint-mostly-unused"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..] -Zhint-mostly-unused [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_stderr_does_not_contain( "[RUNNING] `rustc --crate-name foo [..] -Zhint-mostly-unused [..]", ) .run(); } ================================================ FILE: tests/testsuite/progress.rs ================================================ //! Tests for progress bar. use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::registry::Package; use cargo_test_support::str; #[cargo_test] fn bad_progress_config_unknown_when() { let p = project() .file( ".cargo/config.toml", r#" [term] progress = { when = 'unknown' } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] error in [ROOT]/foo/.cargo/config.toml: could not load config key `term.progress.when` Caused by: unknown variant `unknown`, expected one of `auto`, `never`, `always` "#]]) .run(); } #[cargo_test] fn bad_progress_config_missing_width() { let p = project() .file( ".cargo/config.toml", r#" [term] progress = { when = 'always' } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] "always" progress requires a `width` key "#]]) .run(); } #[cargo_test] fn default_progress_is_auto() { let p = project() .file( ".cargo/config.toml", r#" [term] progress = { width = 1000 } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); } #[cargo_test] fn always_shows_progress() { const N: usize = 3; let mut deps = String::new(); for i in 1..=N { Package::new(&format!("dep{}", i), "1.0.0").publish(); deps.push_str(&format!("dep{} = \"1.0\"\n", i)); } let p = project() .file( ".cargo/config.toml", r#" [term] progress = { when = 'always', width = 100 } "#, ) .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] {} "#, deps ), ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_stderr_data( str![[r#" [DOWNLOADING] [..] crate [..] [DOWNLOADED] 3 crates ([..]) in [..]s [BUILDING] [..] [..]/4: [..] [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s ... "#]] .unordered(), ) .run(); } #[cargo_test] fn never_progress() { const N: usize = 3; let mut deps = String::new(); for i in 1..=N { Package::new(&format!("dep{}", i), "1.0.0").publish(); deps.push_str(&format!("dep{} = \"1.0\"\n", i)); } let p = project() .file( ".cargo/config.toml", r#" [term] progress = { when = 'never' } "#, ) .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] {} "#, deps ), ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_stderr_does_not_contain("[DOWNLOADING] [..] crates [..]") .with_stderr_does_not_contain("[..][DOWNLOADED] 3 crates ([..]) in [..]") .with_stderr_does_not_contain("[BUILDING] [..] [..]/4: [..]") .run(); } #[cargo_test] fn plain_string_when_doesnt_work() { let p = project() .file( ".cargo/config.toml", r#" [term] progress = "never" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] invalid configuration for key `term.progress` expected a table, but found a string for `term.progress` in [ROOT]/foo/.cargo/config.toml "#]]) .run(); } ================================================ FILE: tests/testsuite/pub_priv.rs ================================================ //! Tests for public/private dependencies. use crate::prelude::*; use cargo_test_support::registry::{Dependency, Package}; use cargo_test_support::{git, str}; use cargo_test_support::{project, registry}; #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn exported_priv_warning() { Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] priv_dep = "0.1.0" "#, ) .file( "src/lib.rs", " extern crate priv_dep; pub fn use_priv(_: priv_dep::FromPriv) {} ", ) .build(); p.cargo("check --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" ... src/lib.rs:3:13: [WARNING] type `FromPriv` from private dependency 'priv_dep' in public interface ... "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn exported_pub_dep() { Package::new("pub_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPub;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] pub_dep = {version = "0.1.0", public = true} "#, ) .file( "src/lib.rs", " extern crate pub_dep; pub fn use_pub(_: pub_dep::FromPub) {} ", ) .build(); p.cargo("check --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] pub_dep v0.1.0 (registry `dummy-registry`) [CHECKING] pub_dep v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] pub fn requires_nightly_cargo() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] "#, ) .file("src/lib.rs", "") .build(); p.cargo("check --message-format=short") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: the cargo feature `public-dependency` requires a nightly version of Cargo, but this is the `stable` channel See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information about Rust release channels. See https://doc.rust-lang.org/[..]cargo/reference/unstable.html#public-dependency for more information about using this feature. "#]]) .run(); } #[cargo_test] fn requires_feature() { Package::new("pub_dep", "0.1.0") .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] pub_dep = { version = "0.1.0", public = true } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [WARNING] ignoring `public` on dependency pub_dep, pass `-Zpublic-dependency` to enable support for it [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] pub_dep v0.1.0 (registry `dummy-registry`) [CHECKING] pub_dep v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn pub_dev_dependency() { Package::new("pub_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPub;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dev-dependencies] pub_dep = {version = "0.1.0", public = true} "#, ) .file( "src/lib.rs", " extern crate pub_dep; pub fn use_pub(_: pub_dep::FromPub) {} ", ) .build(); p.cargo("check --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: 'public' specifier can only be used on regular dependencies, not dev-dependencies "#]]) .run(); } #[cargo_test] fn pub_dev_dependency_without_feature() { Package::new("pub_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPub;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dev-dependencies] pub_dep = {version = "0.1.0", public = true} "#, ) .file( "tests/mod.rs", " extern crate pub_dep; pub fn use_pub(_: pub_dep::FromPub) {} ", ) .build(); p.cargo("check --message-format=short") .with_stderr_data(str![[r#" [WARNING] 'public' specifier can only be used on regular dependencies, not dev-dependencies [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn workspace_pub_disallowed() { Package::new("foo1", "0.1.0") .file("src/lib.rs", "pub struct FromFoo;") .publish(); Package::new("foo2", "0.1.0") .file("src/lib.rs", "pub struct FromFoo;") .publish(); Package::new("foo3", "0.1.0") .file("src/lib.rs", "pub struct FromFoo;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [workspace.dependencies] foo1 = "0.1.0" foo2 = { version = "0.1.0", public = true } foo3 = { version = "0.1.0", public = false } [dependencies] foo1 = { workspace = true, public = true } foo2 = { workspace = true } foo3 = { workspace = true, public = true } "#, ) .file( "src/lib.rs", " #![deny(exported_private_dependencies)] pub fn use_priv1(_: foo1::FromFoo) {} pub fn use_priv2(_: foo2::FromFoo) {} pub fn use_priv3(_: foo3::FromFoo) {} ", ) .build(); p.cargo("check") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: foo2 is public, but workspace dependencies cannot be public "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn allow_priv_in_tests() { Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] priv_dep = {version = "0.1.0", public = false} "#, ) .file( "tests/mod.rs", " extern crate priv_dep; pub fn use_priv(_: priv_dep::FromPriv) {} ", ) .build(); p.cargo("check --tests --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] priv_dep v0.1.0 (registry `dummy-registry`) [CHECKING] priv_dep v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn allow_priv_in_benches() { Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] priv_dep = {version = "0.1.0", public = false} "#, ) .file( "benches/mod.rs", " extern crate priv_dep; pub fn use_priv(_: priv_dep::FromPriv) {} ", ) .build(); p.cargo("check --benches --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] priv_dep v0.1.0 (registry `dummy-registry`) [CHECKING] priv_dep v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn allow_priv_in_bins() { Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] priv_dep = {version = "0.1.0", public = false} "#, ) .file( "src/main.rs", " extern crate priv_dep; pub fn use_priv(_: priv_dep::FromPriv) {} fn main() {} ", ) .build(); p.cargo("check --bins --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] priv_dep v0.1.0 (registry `dummy-registry`) [CHECKING] priv_dep v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn allow_priv_in_examples() { Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] priv_dep = {version = "0.1.0", public = false} "#, ) .file( "examples/lib.rs", " extern crate priv_dep; pub fn use_priv(_: priv_dep::FromPriv) {} fn main() {} ", ) .build(); p.cargo("check --examples --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] priv_dep v0.1.0 (registry `dummy-registry`) [CHECKING] priv_dep v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn allow_priv_in_custom_build() { Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [build-dependencies] priv_dep = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .file( "build.rs", " extern crate priv_dep; pub fn use_priv(_: priv_dep::FromPriv) {} fn main() {} ", ) .build(); p.cargo("check --all-targets --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] priv_dep v0.1.0 (registry `dummy-registry`) [COMPILING] priv_dep v0.1.0 [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn publish_package_with_public_dependency() { Package::new("pub_bar", "0.1.0") .file("src/lib.rs", "pub struct FromPub;") .publish(); Package::new("bar", "0.1.0") .cargo_feature("public-dependency") .add_dep(Dependency::new("pub_bar", "0.1.0").public(true)) .file( "src/lib.rs", " extern crate pub_bar; pub use pub_bar::FromPub as BarFromPub; ", ) .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = {version = "0.1.0", public = true} "#, ) .file( "src/lib.rs", " extern crate bar; pub fn use_pub(_: bar::BarFromPub) {} ", ) .build(); p.cargo("check --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] pub_bar v0.1.0 (registry `dummy-registry`) [DOWNLOADED] bar v0.1.0 (registry `dummy-registry`) [CHECKING] pub_bar v0.1.0 [CHECKING] bar v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn verify_mix_cargo_feature_z() { Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); Package::new("pub_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPub;") .publish(); let p = project() .file( "Cargo.toml", r#" cargo-features = ["public-dependency"] [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] dep = "0.1.0" priv_dep = {version = "0.1.0", public = false} pub_dep = {version = "0.1.0", public = true} "#, ) .file( "src/lib.rs", " extern crate dep; extern crate priv_dep; extern crate pub_dep; pub fn use_dep(_: dep::FromDep) {} pub fn use_priv(_: priv_dep::FromPriv) {} pub fn use_pub(_: pub_dep::FromPub) {} ", ) .build(); p.cargo("check -Zpublic-dependency --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" ... src/lib.rs:5:13: [WARNING] type `FromDep` from private dependency 'dep' in public interface src/lib.rs:6:13: [WARNING] type `FromPriv` from private dependency 'priv_dep' in public interface ... "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn verify_z_public_dependency() { Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); Package::new("pub_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPub;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] dep = "0.1.0" priv_dep = {version = "0.1.0", public = false} pub_dep = {version = "0.1.0", public = true} "#, ) .file( "src/lib.rs", " extern crate dep; extern crate priv_dep; extern crate pub_dep; pub fn use_dep(_: dep::FromDep) {} pub fn use_priv(_: priv_dep::FromPriv) {} pub fn use_pub(_: pub_dep::FromPub) {} ", ) .build(); p.cargo("check -Zpublic-dependency --message-format=short") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... src/lib.rs:5:13: [WARNING] type `FromDep` from private dependency 'dep' in public interface src/lib.rs:6:13: [WARNING] type `FromPriv` from private dependency 'priv_dep' in public interface ... "#]] .unordered(), ) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn manifest_location() { Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] dep = "0.1.0" priv_dep = "0.1.0" "#, ) .file( "src/lib.rs", " extern crate dep; extern crate priv_dep; pub fn use_dep(_: dep::FromDep) {} pub use priv_dep::FromPriv; ", ) .build(); p.cargo("check -Zpublic-dependency") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... [WARNING] struct `FromPriv` from private dependency 'priv_dep' is re-exported --> src/lib.rs:5:21 | 5 | pub use priv_dep::FromPriv; | ^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [NOTE] dependency `priv_dep` declared here --> Cargo.toml:9:17 | 9 | priv_dep = "0.1.0" | -------- [WARNING] type `FromDep` from private dependency 'dep' in public interface --> src/lib.rs:4:13 | 4 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [NOTE] dependency `dep` declared here --> Cargo.toml:8:17 | 8 | dep = "0.1.0" | --- ... "#]] .unordered(), ) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn renamed_dependency() { Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] dep = { version = "0.1.0", package = "dep" } renamed_dep = {version = "0.1.0", package = "priv_dep" } "#, ) .file( "src/lib.rs", " extern crate dep; extern crate renamed_dep; pub fn use_dep(_: dep::FromDep) {} pub fn use_priv(_: renamed_dep::FromPriv) {} ", ) .build(); p.cargo("check -Zpublic-dependency") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... [WARNING] type `FromDep` from private dependency 'dep' in public interface --> src/lib.rs:4:13 | 4 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [NOTE] dependency `dep` declared here --> Cargo.toml:8:54 | 8 | dep = { version = "0.1.0", package = "dep" } | ----- [WARNING] type `FromPriv` from private dependency 'priv_dep' in public interface --> src/lib.rs:5:13 | 5 | pub fn use_priv(_: renamed_dep::FromPriv) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [NOTE] dependency `priv_dep` declared here --> Cargo.toml:9:61 | 9 | renamed_dep = {version = "0.1.0", package = "priv_dep" } | ---------- ... "#]] .unordered(), ) .run(); } // We don't point to the toml locations if the crate is ambiguous. #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn duplicate_renamed_dependency() { registry::alt_init(); Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .alternative(true) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] dep = { version = "0.1.0", package = "dep" } renamed_dep = {version = "0.1.0", package = "dep", registry = "alternative" } "#, ) .file( "src/lib.rs", " extern crate dep; extern crate renamed_dep; pub fn use_dep(_: dep::FromDep) {} pub fn use_priv(_: renamed_dep::FromPriv) {} ", ) .build(); p.cargo("check -Zpublic-dependency") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... [WARNING] type `FromDep` from private dependency 'dep' in public interface --> src/lib.rs:4:13 | 4 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [WARNING] type `FromPriv` from private dependency 'dep' in public interface --> src/lib.rs:5:13 | 5 | pub fn use_priv(_: renamed_dep::FromPriv) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... "#]] .unordered(), ) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn dependency_location_in_target_table() { if crate::utils::cross_compile::disabled() { return; } let native = cargo_test_support::cross_compile::native(); let alt = cargo_test_support::cross_compile::alternate(); Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); Package::new("native_priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); Package::new("alt_priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" [target.{native}.dependencies] dep = {{ version = "0.1.0" }} renamed_dep = {{ version = "0.1.0", package = "native_priv_dep" }} [target.{alt}.dependencies] dep = {{ version = "0.1.0" }} renamed_dep = {{ version = "0.1.0", package = "alt_priv_dep" }} "# ), ) .file( "src/lib.rs", " extern crate dep; extern crate renamed_dep; pub fn use_dep(_: dep::FromDep) {} pub fn use_priv(_: renamed_dep::FromPriv) {} ", ) .build(); p.cargo("check -Zpublic-dependency") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... [WARNING] type `FromDep` from private dependency 'dep' in public interface --> src/lib.rs:4:13 | 4 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [NOTE] dependency `dep` declared here --> Cargo.toml:8:17 | 8 | dep = { version = "0.1.0" } | --- [WARNING] type `FromPriv` from private dependency 'native_priv_dep' in public interface --> src/lib.rs:5:13 | 5 | pub fn use_priv(_: renamed_dep::FromPriv) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [NOTE] dependency `native_priv_dep` declared here --> Cargo.toml:9:62 | 9 | renamed_dep = { version = "0.1.0", package = "native_priv_dep" } | ----------------- ... "#]] .unordered(), ) .run(); p.cargo(&format!("check -Zpublic-dependency --target={alt}")) .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... [WARNING] type `FromDep` from private dependency 'dep' in public interface --> src/lib.rs:4:13 | 4 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [NOTE] dependency `dep` declared here --> Cargo.toml:12:17 | 12 | dep = { version = "0.1.0" } | --- [WARNING] type `FromPriv` from private dependency 'alt_priv_dep' in public interface --> src/lib.rs:5:13 | 5 | pub fn use_priv(_: renamed_dep::FromPriv) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [NOTE] dependency `alt_priv_dep` declared here --> Cargo.toml:13:62 | 13 | renamed_dep = { version = "0.1.0", package = "alt_priv_dep" } | -------------- ... "#]] .unordered(), ) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn dependency_location_in_target_table_with_cfg() { if crate::utils::cross_compile::disabled() { return; } let native_arch = cargo_test_support::cross_compile::native_arch(); let alt = cargo_test_support::cross_compile::alternate(); Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); Package::new("native_priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); Package::new("alt_priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" [target.'cfg(target_arch = "{native_arch}")'.dependencies] dep = {{ version = "0.1.0" }} renamed_dep = {{ version = "0.1.0", package = "native_priv_dep" }} [target.'cfg(not(target_arch = "{native_arch}"))'.dependencies] dep = {{ version = "0.1.0" }} renamed_dep = {{ version = "0.1.0", package = "alt_priv_dep" }} "# ), ) .file( "src/lib.rs", " extern crate dep; extern crate renamed_dep; pub fn use_dep(_: dep::FromDep) {} pub fn use_priv(_: renamed_dep::FromPriv) {} ", ) .build(); p.cargo("check -Zpublic-dependency") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... [WARNING] type `FromDep` from private dependency 'dep' in public interface --> src/lib.rs:4:13 | 4 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [NOTE] dependency `dep` declared here --> Cargo.toml:8:17 | 8 | dep = { version = "0.1.0" } | --- [WARNING] type `FromPriv` from private dependency 'native_priv_dep' in public interface --> src/lib.rs:5:13 | 5 | pub fn use_priv(_: renamed_dep::FromPriv) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [NOTE] dependency `native_priv_dep` declared here --> Cargo.toml:9:62 | 9 | renamed_dep = { version = "0.1.0", package = "native_priv_dep" } | ----------------- ... "#]] .unordered(), ) .run(); p.cargo(&format!("check -Zpublic-dependency --target={alt}")) .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data( str![[r#" ... [WARNING] type `FromDep` from private dependency 'dep' in public interface --> src/lib.rs:4:13 | 4 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [NOTE] dependency `dep` declared here --> Cargo.toml:12:17 | 12 | dep = { version = "0.1.0" } | --- [WARNING] type `FromPriv` from private dependency 'alt_priv_dep' in public interface --> src/lib.rs:5:13 | 5 | pub fn use_priv(_: renamed_dep::FromPriv) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [NOTE] dependency `alt_priv_dep` declared here --> Cargo.toml:13:62 | 13 | renamed_dep = { version = "0.1.0", package = "alt_priv_dep" } | -------------- ... "#]] .unordered(), ) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn dependency_location_in_workspace() { Package::new("dep", "0.1.0") .file("src/lib.rs", "pub struct FromDep;") .publish(); let (p, repo) = git::new_repo("foo", |p| { p.file( "Cargo.toml", r#" [workspace] members = ["pkg"] [workspace.package] edition = "2015" [workspace.dependencies] dep = "0.1.0" "#, ) .file( "pkg/Cargo.toml", r#" [package] name = "pkg" edition.workspace = true [dependencies] dep.workspace = true "#, ) .file( "pkg/src/lib.rs", " extern crate dep; pub fn use_dep(_: dep::FromDep) {} ", ) }); git::commit(&repo); p.cargo(&format!("check -Zpublic-dependency")) .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" ... [WARNING] type `FromDep` from private dependency 'dep' in public interface --> pkg/src/lib.rs:3:13 | 3 | pub fn use_dep(_: dep::FromDep) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default ... "#]]) .run(); } #[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] fn relative_display_path() { Package::new("priv_dep", "0.1.0") .file("src/lib.rs", "pub struct FromPriv;") .publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo"] "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] priv_dep = "0.1.0" "#, ) .file( "foo/src/lib.rs", " extern crate priv_dep; pub use priv_dep::FromPriv; ", ) .build(); p.cargo("check -Zpublic-dependency") .cwd("foo") .masquerade_as_nightly_cargo(&["public-dependency"]) .with_stderr_data(str![[r#" ... [WARNING] struct `FromPriv` from private dependency 'priv_dep' is re-exported --> foo/src/lib.rs:3:21 | 3 | pub use priv_dep::FromPriv; | ^^^^^^^^^^^^^^^^^^ | = [NOTE] `#[warn(exported_private_dependencies)]` on by default [NOTE] dependency `priv_dep` declared here --> foo/Cargo.toml:8:17 | 8 | priv_dep = "0.1.0" | -------- ... "#]]) .run(); } ================================================ FILE: tests/testsuite/publish.rs ================================================ //! Tests for the `cargo publish` command. use std::fs; use std::sync::{Arc, Mutex}; use crate::prelude::*; use cargo_test_support::git::{self, repo}; use cargo_test_support::registry::{self, Package, RegistryBuilder, Response}; use cargo_test_support::{Project, paths}; use cargo_test_support::{basic_manifest, project, publish, str}; const CLEAN_FOO_JSON: &str = r#" { "authors": [], "badges": {}, "categories": [], "deps": [], "description": "foo", "documentation": "foo", "features": {}, "homepage": "foo", "keywords": [], "license": "MIT", "license_file": null, "links": null, "name": "foo", "readme": null, "readme_file": null, "repository": "foo", "rust_version": null, "vers": "0.0.1" } "#; fn validate_upload_foo() { publish::validate_upload( r#" { "authors": [], "badges": {}, "categories": [], "deps": [], "description": "foo", "documentation": null, "features": {}, "homepage": null, "keywords": [], "license": "MIT", "license_file": null, "links": null, "name": "foo", "readme": null, "readme_file": null, "repository": null, "rust_version": null, "vers": "0.0.1" } "#, "foo-0.0.1.crate", &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], ); } fn validate_upload_li() { publish::validate_upload( r#" { "authors": [], "badges": {}, "categories": [], "deps": [], "description": "li", "documentation": null, "features": {}, "homepage": null, "keywords": [], "license": "MIT", "license_file": null, "links": null, "name": "li", "readme": null, "readme_file": null, "repository": null, "rust_version": "1.69", "vers": "0.0.1" } "#, "li-0.0.1.crate", &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], ); } #[cargo_test] fn simple() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); validate_upload_foo(); } #[cargo_test] fn duplicate_version() { let registry_dupl = RegistryBuilder::new().http_api().http_index().build(); Package::new("foo", "0.0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --dry-run") .replace_crates_io(registry_dupl.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] crate foo@0.0.1 already exists on crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [WARNING] aborting upload due to dry run "#]]) .run(); p.cargo("publish") .replace_crates_io(registry_dupl.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] crate foo@0.0.1 already exists on crates.io index "#]]) .run(); } // Check that the `token` key works at the root instead of under a // `[registry]` table. #[cargo_test] fn simple_publish_with_http() { let _reg = registry::RegistryBuilder::new() .http_api() .token(registry::Token::Plaintext("sekrit".to_string())) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify --token sekrit --registry dummy-registry") .with_stderr_data(str![[r#" [WARNING] `cargo publish --token` is deprecated in favor of using `cargo login` and environment variables [UPDATING] `dummy-registry` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `dummy-registry` [NOTE] waiting for foo v0.0.1 to be available at registry `dummy-registry` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `dummy-registry` "#]]) .run(); } #[cargo_test] fn simple_publish_with_asymmetric() { let _reg = registry::RegistryBuilder::new() .http_api() .http_index() .alternative_named("dummy-registry") .token(registry::Token::rfc_key()) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify -Zasymmetric-token --registry dummy-registry") .masquerade_as_nightly_cargo(&["asymmetric-token"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `dummy-registry` [NOTE] waiting for foo v0.0.1 to be available at registry `dummy-registry` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `dummy-registry` "#]]) .run(); } #[cargo_test] fn old_token_location() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); let credentials = paths::home().join(".cargo/credentials.toml"); fs::remove_file(&credentials).unwrap(); // Verify can't publish without a token. p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] no token found, please run `cargo login` or use environment variable CARGO_REGISTRY_TOKEN "#]]) .run(); fs::write(&credentials, format!(r#"token = "{}""#, registry.token())).unwrap(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); // Skip `validate_upload_foo` as we just cared we got far enough for verify the token behavior. // Other tests will verify the endpoint gets the right payload. } #[cargo_test] fn simple_with_index() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify") .arg("--token") .arg(registry.token()) .arg("--index") .arg(registry.index_url().as_str()) .with_stderr_data(str![[r#" [WARNING] `cargo publish --token` is deprecated in favor of using `cargo login` and environment variables [UPDATING] `[ROOT]/registry` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `[ROOT]/registry` [NOTE] waiting for foo v0.0.1 to be available at registry `[ROOT]/registry` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `[ROOT]/registry` "#]]) .run(); // Skip `validate_upload_foo` as we just cared we got far enough for verify the VCS behavior. // Other tests will verify the endpoint gets the right payload. } #[cargo_test] fn git_deps() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies.foo] git = "git://path/to/nowhere" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] failed to verify manifest at `[ROOT]/foo/Cargo.toml` Caused by: all dependencies must have a version requirement specified when publishing. dependency `foo` does not specify a version Note: The published dependency will use the version from crates.io, the `git` specification will be removed from the dependency declaration. "#]]) .run(); } #[cargo_test] fn path_dependency_no_version() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies.bar] path = "bar" "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) .file("bar/src/lib.rs", "") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] failed to verify manifest at `[ROOT]/foo/Cargo.toml` Caused by: all dependencies must have a version requirement specified when publishing. dependency `bar` does not specify a version Note: The published dependency will use the version from crates.io, the `path` specification will be removed from the dependency declaration. "#]]) .run(); } #[cargo_test] fn unpublishable_crate() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" publish = false "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --index") .arg(registry.index_url().as_str()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] `foo` cannot be published. `package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. "#]]) .run(); } #[cargo_test] fn dont_publish_dirty() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project().file("bar", "").build(); let _ = git::repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] 1 files in the working directory contain changes that were not yet committed into git: bar to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag "#]]) .run(); } #[cargo_test] fn publish_clean() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project().build(); let _ = repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); // Skip `validate_upload_foo_clean` as we just cared we got far enough for verify the VCS behavior. // Other tests will verify the endpoint gets the right payload. } #[cargo_test] fn publish_in_sub_repo() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project().no_manifest().file("baz", "").build(); let _ = repo(&paths::root().join("foo")) .file( "bar/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .cwd("bar") .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.0.1 ([ROOT]/foo/bar) [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo/bar) [COMPILING] foo v0.0.1 ([ROOT]/foo/bar/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo/bar) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); // Skip `validate_upload_foo_clean` as we just cared we got far enough for verify the VCS behavior. // Other tests will verify the endpoint gets the right payload. } #[cargo_test] fn publish_when_ignored() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project().file("baz", "").build(); let _ = repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/main.rs", "fn main() {}") .file(".gitignore", "baz") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 6 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); // Skip `validate_upload` as we just cared we got far enough for verify the VCS behavior. // Other tests will verify the endpoint gets the right payload. } #[cargo_test] fn ignore_when_crate_ignored() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project().no_manifest().file("bar/baz", "").build(); let _ = repo(&paths::root().join("foo")) .file(".gitignore", "bar") .nocommit_file( "bar/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .nocommit_file("bar/src/main.rs", "fn main() {}"); p.cargo("publish") .replace_crates_io(registry.index_url()) .cwd("bar") .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.0.1 ([ROOT]/foo/bar) [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo/bar) [COMPILING] foo v0.0.1 ([ROOT]/foo/bar/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo/bar) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); // Skip `validate_upload` as we just cared we got far enough for verify the VCS behavior. // Other tests will verify the endpoint gets the right payload. } #[cargo_test] fn new_crate_rejected() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project().file("baz", "").build(); let _ = repo(&paths::root().join("foo")) .nocommit_file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .nocommit_file("src/main.rs", "fn main() {}"); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] 3 files in the working directory contain changes that were not yet committed into git: ... "#]]) .run(); } #[cargo_test] fn dry_run() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --dry-run --index") .arg(registry.index_url().as_str()) .with_stderr_data(str![[r#" [UPDATING] `[ROOT]/registry` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [WARNING] aborting upload due to dry run "#]]) .run(); // Ensure the API request wasn't actually made assert!(registry::api_path().join("api/v1/crates").exists()); assert!(!registry::api_path().join("api/v1/crates/new").exists()); } #[cargo_test] fn registry_not_in_publish_list() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" publish = [ "test" ] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .arg("--registry") .arg("alternative") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `foo` cannot be published. The registry `alternative` is not listed in the `package.publish` value in Cargo.toml. "#]]) .run(); } #[cargo_test] fn publish_empty_list() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" publish = [] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `foo` cannot be published. `package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. "#]]) .run(); } #[cargo_test] fn publish_allowed_registry() { let _registry = RegistryBuilder::new() .http_api() .http_index() .alternative() .build(); let p = project().build(); let _ = repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" publish = ["alternative"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --registry alternative") .with_stderr_data(str![[r#" [UPDATING] `alternative` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `alternative` [NOTE] waiting for foo v0.0.1 to be available at registry `alternative` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `alternative` "#]]) .run(); publish::validate_alt_upload( CLEAN_FOO_JSON, "foo-0.0.1.crate", &[ "Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs", ".cargo_vcs_info.json", ], ); } #[cargo_test] fn publish_implicitly_to_only_allowed_registry() { let _registry = RegistryBuilder::new() .http_api() .http_index() .alternative() .build(); let p = project().build(); let _ = repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" publish = ["alternative"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .with_stderr_data(str![[r#" [NOTE] found `alternative` as only allowed registry. Publishing to it automatically. [UPDATING] `alternative` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `alternative` [NOTE] waiting for foo v0.0.1 to be available at registry `alternative` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `alternative` "#]]) .run(); publish::validate_alt_upload( CLEAN_FOO_JSON, "foo-0.0.1.crate", &[ "Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs", ".cargo_vcs_info.json", ], ); } #[cargo_test] fn publish_when_both_publish_and_index_specified() { let registry = RegistryBuilder::new() .http_api() .http_index() .alternative() .build(); let p = project().build(); let _ = repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" publish = ["registry"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .arg("--index") .arg(registry.index_url().as_str()) .arg("--token") .arg(registry.token()) .with_stderr_data(str![[r#" [WARNING] `cargo publish --token` is deprecated in favor of using `cargo login` and environment variables [WARNING] `--index` will ignore registries set by `package.publish` in Cargo.toml, and may cause unexpected push to prohibited registry [HELP] use `--registry` instead or set `publish = true` in Cargo.toml to suppress this warning [UPDATING] [..] index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry [..] [NOTE] waiting for foo v0.0.1 to be available at registry [..] [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry [..] "#]]) .run(); } #[cargo_test] fn publish_failed_with_index_and_only_allowed_registry() { let registry = RegistryBuilder::new() .http_api() .http_index() .alternative() .build(); let p = project().build(); let _ = repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" publish = ["alternative"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .arg("--index") .arg(registry.index_url().as_str()) .with_status(101) .with_stderr_data(str![[r#" ... [ERROR] command-line argument --index requires --token to be specified "#]]) .run(); } #[cargo_test] fn publish_fail_with_no_registry_specified() { let p = project().build(); let _ = repo(&paths::root().join("foo")) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" publish = ["alternative", "test"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .with_status(101) .with_stderr_data(str![[r#" [ERROR] --registry is required to disambiguate between "alternative" or "test" registries "#]]) .run(); } #[cargo_test] fn block_publish_no_registry() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" publish = [] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `foo` cannot be published. `package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. "#]]) .run(); } // Explicitly setting `crates-io` in the publish list. #[cargo_test] fn publish_with_crates_io_explicit() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" publish = ["crates-io"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `foo` cannot be published. The registry `alternative` is not listed in the `package.publish` value in Cargo.toml. "#]]) .run(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn publish_with_select_features() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [features] required = [] optional = [] "#, ) .file( "src/main.rs", "#[cfg(not(feature = \"required\"))] compile_error!(\"This crate requires `required` feature!\"); fn main() {}", ) .build(); p.cargo("publish --features required") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn publish_with_all_features() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [features] required = [] optional = [] "#, ) .file( "src/main.rs", "#[cfg(not(feature = \"required\"))] compile_error!(\"This crate requires `required` feature!\"); fn main() {}", ) .build(); p.cargo("publish --all-features") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn publish_with_no_default_features() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [features] default = ["required"] required = [] "#, ) .file( "src/main.rs", "#[cfg(not(feature = \"required\"))] compile_error!(\"This crate requires `required` feature!\"); fn main() {}", ) .build(); p.cargo("publish --no-default-features") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" ... [ERROR] This crate requires `required` feature! ... "#]]) .run(); } #[cargo_test] fn publish_with_patch() { let registry = RegistryBuilder::new().http_api().http_index().build(); Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies] bar = "1.0" [patch.crates-io] bar = { path = "bar" } "#, ) .file( "src/main.rs", "extern crate bar; fn main() { bar::newfunc(); }", ) .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0")) .file("bar/src/lib.rs", "pub fn newfunc() {}") .build(); // Check that it works with the patched crate. p.cargo("build").run(); // Check that verify fails with patched crate which has new functionality. p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" ... error[E0425]: cannot find function `newfunc` in crate `bar` ... "#]]) .run(); // Remove the usage of new functionality and try again. p.change_file("src/main.rs", "extern crate bar; pub fn main() {}"); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); publish::validate_upload( r#" { "authors": [], "badges": {}, "categories": [], "deps": [ { "default_features": true, "features": [], "kind": "normal", "name": "bar", "optional": false, "target": null, "version_req": "^1.0" } ], "description": "foo", "documentation": null, "features": {}, "homepage": null, "keywords": [], "license": "MIT", "license_file": null, "links": null, "name": "foo", "readme": null, "readme_file": null, "repository": null, "rust_version": null, "vers": "0.0.1" } "#, "foo-0.0.1.crate", &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], ); } #[cargo_test] fn publish_checks_for_token_before_verify() { let registry = registry::RegistryBuilder::new() .no_configure_token() .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); // Assert upload token error before the package is verified p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] no token found, please run `cargo login` or use environment variable CARGO_REGISTRY_TOKEN "#]]) .with_stderr_does_not_contain("[VERIFYING] foo v0.0.1 ([CWD])") .run(); // Assert package verified successfully on dry run p.cargo("publish --dry-run") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [WARNING] aborting upload due to dry run "#]]) .run(); } #[cargo_test] fn publish_with_bad_source() { let p = project() .file( ".cargo/config.toml", r#" [source.crates-io] replace-with = 'local-registry' [source.local-registry] local-registry = 'registry' "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish") .with_status(101) .with_stderr_data(str![[r#" [ERROR] crates-io is replaced with non-remote-registry source registry `[ROOT]/foo/registry`; include `--registry crates-io` to use crates.io "#]]) .run(); p.change_file( ".cargo/config.toml", r#" [source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "vendor" "#, ); p.cargo("publish") .with_status(101) .with_stderr_data(str![[r#" [ERROR] crates-io is replaced with non-remote-registry source dir [ROOT]/foo/vendor; include `--registry crates-io` to use crates.io "#]]) .run(); } // A dependency with both `git` and `version`. #[cargo_test] fn publish_git_with_version() { let registry = RegistryBuilder::new().http_api().http_index().build(); Package::new("dep1", "1.0.1") .file("src/lib.rs", "pub fn f() -> i32 {1}") .publish(); let git_project = git::new("dep1", |project| { project .file("Cargo.toml", &basic_manifest("dep1", "1.0.0")) .file("src/lib.rs", "pub fn f() -> i32 {2}") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" authors = [] edition = "2018" license = "MIT" description = "foo" [dependencies] dep1 = {{version = "1.0", git="{}"}} "#, git_project.url() ), ) .file( "src/main.rs", r#" pub fn main() { println!("{}", dep1::f()); } "#, ) .build(); p.cargo("run") .with_stdout_data(str![[r#" 2 "#]]) .run(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.1.0 ([ROOT]/foo) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.1.0 ([ROOT]/foo) [UPLOADED] foo v0.1.0 to registry `crates-io` [NOTE] waiting for foo v0.1.0 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.1.0 at registry `crates-io` "#]]) .run(); publish::validate_upload_with_contents( r#" { "authors": [], "badges": {}, "categories": [], "deps": [ { "default_features": true, "features": [], "kind": "normal", "name": "dep1", "optional": false, "target": null, "version_req": "^1.0" } ], "description": "foo", "documentation": null, "features": {}, "homepage": null, "keywords": [], "license": "MIT", "license_file": null, "links": null, "name": "foo", "readme": null, "readme_file": null, "repository": null, "rust_version": null, "vers": "0.1.0" } "#, "foo-0.1.0.crate", &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], [ ( "Cargo.toml", // Check that only `version` is included in Cargo.toml. str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "foo" version = "0.1.0" authors = [] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" readme = false license = "MIT" [[bin]] name = "foo" path = "src/main.rs" [dependencies.dep1] version = "1.0" "##]], ), ( "Cargo.lock", // The important check here is that it is 1.0.1 in the registry. str![[r##" # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "dep1" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "[..]" [[package]] name = "foo" version = "0.1.0" dependencies = [ "dep1", ] "##]], ), ], ); } #[cargo_test] fn publish_dev_dep_stripping() { let registry = RegistryBuilder::new().http_api().http_index().build(); Package::new("normal-only", "1.0.0") .feature("cat", &[]) .publish(); Package::new("optional-dep-feature", "1.0.0") .feature("cat", &[]) .publish(); Package::new("optional-namespaced", "1.0.0") .feature("cat", &[]) .publish(); Package::new("optional-renamed-dep-feature", "1.0.0") .feature("cat", &[]) .publish(); Package::new("optional-renamed-namespaced", "1.0.0") .feature("cat", &[]) .publish(); Package::new("build-only", "1.0.0") .feature("cat", &[]) .publish(); Package::new("normal-and-dev", "1.0.0") .feature("cat", &[]) .publish(); Package::new("target-normal-only", "1.0.0") .feature("cat", &[]) .publish(); Package::new("target-build-only", "1.0.0") .feature("cat", &[]) .publish(); Package::new("target-normal-and-dev", "1.0.0") .feature("cat", &[]) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" [features] foo_feature = [ "normal-only/cat", "build-only/cat", "dev-only/cat", "renamed-dev-only01/cat", "normal-and-dev/cat", "target-normal-only/cat", "target-build-only/cat", "target-dev-only/cat", "target-normal-and-dev/cat", "optional-dep-feature/cat", "dep:optional-namespaced", "optional-renamed-dep-feature10/cat", "dep:optional-renamed-namespaced10", ] [dependencies] normal-only = { version = "1.0", features = ["cat"] } normal-and-dev = { version = "1.0", features = ["cat"] } optional-dep-feature = { version = "1.0", features = ["cat"], optional = true } optional-namespaced = { version = "1.0", features = ["cat"], optional = true } optional-renamed-dep-feature10 = { version = "1.0", features = ["cat"], optional = true, package = "optional-renamed-dep-feature" } optional-renamed-namespaced10 = { version = "1.0", features = ["cat"], optional = true, package = "optional-renamed-namespaced" } [build-dependencies] build-only = { version = "1.0", features = ["cat"] } [dev-dependencies] dev-only = { path = "../dev-only", features = ["cat"] } renamed-dev-only01 = { path = "../renamed-dev-only", features = ["cat"], package = "renamed-dev-only" } normal-and-dev = { version = "1.0", features = ["cat"] } [target.'cfg(unix)'.dependencies] target-normal-only = { version = "1.0", features = ["cat"] } target-normal-and-dev = { version = "1.0", features = ["cat"] } [target.'cfg(unix)'.build-dependencies] target-build-only = { version = "1.0", features = ["cat"] } [target.'cfg(unix)'.dev-dependencies] target-dev-only = { path = "../dev-only", features = ["cat"] } target-normal-and-dev = { version = "1.0", features = ["cat"] } "#, ) .file("src/main.rs", "") .file( "dev-only/Cargo.toml", r#" [package] name = "dev-only" version = "0.1.0" edition = "2015" authors = [] [features] cat = [] "#, ) .file( "dev-only/src/lib.rs", r#" #[cfg(feature = "cat")] pub fn cat() {} "#, ) .file( "renamed-dev-only/Cargo.toml", r#" [package] name = "renamed-dev-only" version = "0.1.0" edition = "2015" authors = [] [features] cat = [] "#, ) .file( "renamed-dev-only/src/lib.rs", r#" #[cfg(feature = "cat")] pub fn cat() {} "#, ) .build(); p.cargo("publish --no-verify") .env("RUSTFLAGS", "--cfg unix") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.1.0 ([ROOT]/foo) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.1.0 ([ROOT]/foo) [UPLOADED] foo v0.1.0 to registry `crates-io` [NOTE] waiting for foo v0.1.0 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.1.0 at registry `crates-io` "#]]) .run(); publish::validate_upload_with_contents( r#" { "authors": [], "badges": {}, "categories": [], "deps": [ { "default_features": true, "features": [ "cat" ], "kind": "normal", "name": "normal-and-dev", "optional": false, "target": null, "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "normal", "name": "normal-only", "optional": false, "target": null, "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "normal", "name": "optional-dep-feature", "optional": true, "target": null, "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "normal", "name": "optional-namespaced", "optional": true, "target": null, "version_req": "^1.0" }, { "default_features": true, "explicit_name_in_toml": "optional-renamed-dep-feature10", "features": [ "cat" ], "kind": "normal", "name": "optional-renamed-dep-feature", "optional": true, "target": null, "version_req": "^1.0" }, { "default_features": true, "explicit_name_in_toml": "optional-renamed-namespaced10", "features": [ "cat" ], "kind": "normal", "name": "optional-renamed-namespaced", "optional": true, "target": null, "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "dev", "name": "normal-and-dev", "optional": false, "target": null, "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "build", "name": "build-only", "optional": false, "target": null, "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "normal", "name": "target-normal-and-dev", "optional": false, "target": "cfg(unix)", "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "normal", "name": "target-normal-only", "optional": false, "target": "cfg(unix)", "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "build", "name": "target-build-only", "optional": false, "target": "cfg(unix)", "version_req": "^1.0" }, { "default_features": true, "features": [ "cat" ], "kind": "dev", "name": "target-normal-and-dev", "optional": false, "target": "cfg(unix)", "version_req": "^1.0" } ], "description": "foo", "documentation": "foo", "features": { "foo_feature": [ "normal-only/cat", "build-only/cat", "normal-and-dev/cat", "target-normal-only/cat", "target-build-only/cat", "target-normal-and-dev/cat", "optional-dep-feature/cat", "dep:optional-namespaced", "optional-renamed-dep-feature10/cat", "dep:optional-renamed-namespaced10" ] }, "homepage": "foo", "keywords": [], "license": "MIT", "license_file": null, "links": null, "name": "foo", "readme": null, "readme_file": null, "repository": "foo", "rust_version": null, "vers": "0.1.0" } "#, "foo-0.1.0.crate", &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], [( "Cargo.toml", str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "foo" version = "0.1.0" authors = [] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" homepage = "foo" documentation = "foo" readme = false license = "MIT" repository = "foo" [features] foo_feature = [ "normal-only/cat", "build-only/cat", "normal-and-dev/cat", "target-normal-only/cat", "target-build-only/cat", "target-normal-and-dev/cat", "optional-dep-feature/cat", "dep:optional-namespaced", "optional-renamed-dep-feature10/cat", "dep:optional-renamed-namespaced10", ] [[bin]] name = "foo" path = "src/main.rs" [dependencies.normal-and-dev] version = "1.0" features = ["cat"] [dependencies.normal-only] version = "1.0" features = ["cat"] [dependencies.optional-dep-feature] version = "1.0" features = ["cat"] optional = true [dependencies.optional-namespaced] version = "1.0" features = ["cat"] optional = true [dependencies.optional-renamed-dep-feature10] version = "1.0" features = ["cat"] optional = true package = "optional-renamed-dep-feature" [dependencies.optional-renamed-namespaced10] version = "1.0" features = ["cat"] optional = true package = "optional-renamed-namespaced" [dev-dependencies.normal-and-dev] version = "1.0" features = ["cat"] [build-dependencies.build-only] version = "1.0" features = ["cat"] [target."cfg(unix)".dependencies.target-normal-and-dev] version = "1.0" features = ["cat"] [target."cfg(unix)".dependencies.target-normal-only] version = "1.0" features = ["cat"] [target."cfg(unix)".build-dependencies.target-build-only] version = "1.0" features = ["cat"] [target."cfg(unix)".dev-dependencies.target-normal-and-dev] version = "1.0" features = ["cat"] "##]], )], ); } #[cargo_test] fn credentials_ambiguous_filename() { // `publish` generally requires a remote registry let registry = registry::RegistryBuilder::new().http_api().build(); // Make token in `credentials.toml` incorrect to ensure it is not read. let credentials_toml = paths::home().join(".cargo/credentials.toml"); fs::write(credentials_toml, r#"token = "wrong-token""#).unwrap(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" ... Unauthorized message from server. "#]]) .run(); // Favor `credentials` if exists. let credentials = paths::home().join(".cargo/credentials"); fs::write(credentials, r#"token = "sekrit""#).unwrap(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] both `[ROOT]/home/.cargo/credentials` and `[ROOT]/home/.cargo/credentials.toml` exist. Using `[ROOT]/home/.cargo/credentials` [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); } // --index will not load registry.token to avoid possibly leaking // crates.io token to another server. #[cargo_test] fn index_requires_token() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let credentials = paths::home().join(".cargo/credentials.toml"); fs::remove_file(&credentials).unwrap(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify --index") .arg(registry.index_url().as_str()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] command-line argument --index requires --token to be specified "#]]) .run(); } // publish with source replacement without --registry #[cargo_test] fn cratesio_source_replacement() { registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify") .with_status(101) .with_stderr_data(str![[r#" [ERROR] crates-io is replaced with remote registry dummy-registry; include `--registry dummy-registry` or `--registry crates-io` "#]]) .run(); } // Registry returns an API error. #[cargo_test] fn api_error_json() { let _registry = registry::RegistryBuilder::new() .alternative() .http_api() .add_responder("/api/v1/crates/new", |_, _| Response { body: br#"{"errors": [{"detail": "you must be logged in"}]}"#.to_vec(), code: 403, headers: vec![], }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/ Caused by: the remote server responded with an error (status 403 Forbidden): you must be logged in "#]]) .run(); } // Registry returns an API error with a 200 status code. #[cargo_test] fn api_error_200() { let _registry = registry::RegistryBuilder::new() .alternative() .http_api() .add_responder("/api/v1/crates/new", |_, _| Response { body: br#"{"errors": [{"detail": "max upload size is 123"}]}"#.to_vec(), code: 200, headers: vec![], }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/ Caused by: the remote server responded with an [ERROR] max upload size is 123 "#]]) .run(); } // Registry returns an error code without a JSON message. #[cargo_test] fn api_error_code() { let _registry = registry::RegistryBuilder::new() .alternative() .http_api() .add_responder("/api/v1/crates/new", |_, _| Response { body: br#"go away"#.to_vec(), code: 400, headers: vec![], }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/ Caused by: failed to get a 200 OK response, got 400 headers: HTTP/1.1 400 Content-Length: 7 Connection: close body: go away "#]]) .run(); } // Registry has a network error. #[cargo_test] fn api_curl_error() { let _registry = registry::RegistryBuilder::new() .alternative() .http_api() .add_responder("/api/v1/crates/new", |_, _| { panic!("broke"); }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/lib.rs", "") .build(); // This doesn't check for the exact text of the error in the remote // possibility that cargo is linked with a weird version of libcurl, or // curl changes the text of the message. Currently the message 52 // (CURLE_GOT_NOTHING) is: // Server returned nothing (no headers, no data) (Empty reply from server) p.cargo("publish --no-verify --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/ Caused by: [52] Server returned nothing (no headers, no data) (Empty reply from server) "#]]) .run(); } // Registry returns an invalid response. #[cargo_test] fn api_other_error() { let _registry = registry::RegistryBuilder::new() .alternative() .http_api() .add_responder("/api/v1/crates/new", |_, _| Response { body: b"\xff".to_vec(), code: 200, headers: vec![], }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify --registry alternative") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/ Caused by: invalid response body from server Caused by: invalid utf-8 sequence of 1 bytes from index 0 "#]]) .run(); } #[cargo_test] fn in_package_workspace() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2021" [workspace] members = ["li"] "#, ) .file("src/main.rs", "fn main() {}") .file( "li/Cargo.toml", r#" [package] name = "li" version = "0.0.1" edition = "2015" rust-version = "1.69" description = "li" license = "MIT" "#, ) .file("li/src/main.rs", "fn main() {}") .build(); p.cargo("publish -p li --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] li v0.0.1 ([ROOT]/foo/li) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] li v0.0.1 ([ROOT]/foo/li) [UPLOADED] li v0.0.1 to registry `crates-io` [NOTE] waiting for li v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] li v0.0.1 at registry `crates-io` "#]]) .run(); validate_upload_li(); } #[cargo_test] fn with_duplicate_spec_in_members() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] resolver = "2" members = ["li","bar"] default-members = ["li","bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "li/Cargo.toml", r#" [package] name = "li" version = "0.0.1" edition = "2015" description = "li" license = "MIT" "#, ) .file("li/src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" description = "bar" license = "MIT" "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] bar v0.0.1 ([ROOT]/foo/bar) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] li v0.0.1 ([ROOT]/foo/li) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] bar v0.0.1 ([ROOT]/foo/bar) [UPLOADED] bar v0.0.1 to registry `crates-io` [UPLOADING] li v0.0.1 ([ROOT]/foo/li) [UPLOADED] li v0.0.1 to registry `crates-io` [NOTE] waiting for bar v0.0.1 or li v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crates should be available shortly [PUBLISHED] bar v0.0.1 and li v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn in_package_workspace_with_members_with_features_old() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["li"] "#, ) .file("src/main.rs", "fn main() {}") .file( "li/Cargo.toml", r#" [package] name = "li" version = "0.0.1" edition = "2015" rust-version = "1.69" description = "li" license = "MIT" "#, ) .file("li/src/main.rs", "fn main() {}") .build(); p.cargo("publish -p li --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] li v0.0.1 ([ROOT]/foo/li) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] li v0.0.1 ([ROOT]/foo/li) [UPLOADED] li v0.0.1 to registry `crates-io` [NOTE] waiting for li v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] li v0.0.1 at registry `crates-io` "#]]) .run(); validate_upload_li(); } #[cargo_test] fn in_virtual_workspace() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo"] "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("foo/src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn in_virtual_workspace_with_p() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo","li"] "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("foo/src/main.rs", "fn main() {}") .file( "li/Cargo.toml", r#" [package] name = "li" version = "0.0.1" edition = "2015" rust-version = "1.69" description = "li" license = "MIT" "#, ) .file("li/src/main.rs", "fn main() {}") .build(); p.cargo("publish -p li --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] li v0.0.1 ([ROOT]/foo/li) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] li v0.0.1 ([ROOT]/foo/li) [UPLOADED] li v0.0.1 to registry `crates-io` [NOTE] waiting for li v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] li v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn in_package_workspace_not_found() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2021" [workspace] "#, ) .file("src/main.rs", "fn main() {}") .file( "li/Cargo.toml", r#" [package] name = "li" version = "0.0.1" edition = "2021" authors = [] license = "MIT" description = "li" "#, ) .file("li/src/main.rs", "fn main() {}") .build(); p.cargo("publish -p li --no-verify") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification `li` did not match any packages [HELP] a package with a similar name exists: `foo` "#]]) .run(); } #[cargo_test] fn in_package_workspace_found_multiple() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2021" [workspace] members = ["li","lii"] "#, ) .file("src/main.rs", "fn main() {}") .file( "li/Cargo.toml", r#" [package] name = "li" version = "0.0.1" edition = "2021" authors = [] license = "MIT" description = "li" "#, ) .file("li/src/main.rs", "fn main() {}") .file( "lii/Cargo.toml", r#" [package] name = "lii" version = "0.0.1" edition = "2021" authors = [] license = "MIT" description = "lii" "#, ) .file("lii/src/main.rs", "fn main() {}") .build(); p.cargo("publish -p li* --no-verify") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] li v0.0.1 ([ROOT]/foo/li) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] lii v0.0.1 ([ROOT]/foo/lii) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] li v0.0.1 ([ROOT]/foo/li) [UPLOADED] li v0.0.1 to registry `crates-io` [UPLOADING] lii v0.0.1 ([ROOT]/foo/lii) [UPLOADED] lii v0.0.1 to registry `crates-io` [NOTE] waiting for li v0.0.1 or lii v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crates should be available shortly [PUBLISHED] li v0.0.1 and lii v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn publish_path_dependency_without_workspace() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2021" [dependencies.bar] path = "bar" "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2021" authors = [] license = "MIT" description = "bar" "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); p.cargo("publish -p bar --no-verify") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification `bar` did not match any packages [HELP] a package with a similar name exists: `foo` "#]]) .run(); } #[cargo_test] fn http_api_not_noop() { let registry = registry::RegistryBuilder::new().http_api().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); let p = project() .file( "Cargo.toml", r#" [project] name = "bar" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies] foo = "0.0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build").run(); } #[cargo_test] fn wait_for_first_publish() { // Counter for number of tries before the package is "published" let arc: Arc> = Arc::new(Mutex::new(0)); let arc2 = arc.clone(); // Registry returns an invalid response. let registry = registry::RegistryBuilder::new() .http_index() .http_api() .add_responder("/index/de/la/delay", move |req, server| { let mut lock = arc.lock().unwrap(); *lock += 1; if *lock <= 1 { server.not_found(req) } else { server.index(req) } }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "delay" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(0) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] delay v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] delay v0.0.1 ([ROOT]/foo) [UPLOADED] delay v0.0.1 to registry `crates-io` [NOTE] waiting for delay v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] delay v0.0.1 at registry `crates-io` "#]]) .run(); // Verify the responder has been pinged let lock = arc2.lock().unwrap(); assert_eq!(*lock, 2); drop(lock); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] delay = "0.0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build").with_status(0).run(); } /// A separate test is needed for package names with - or _ as they hit /// the responder twice per cargo invocation. If that ever gets changed /// this test will need to be changed accordingly. #[cargo_test] fn wait_for_first_publish_underscore() { // Counter for number of tries before the package is "published" let arc: Arc> = Arc::new(Mutex::new(0)); let arc2 = arc.clone(); let misses = Arc::new(Mutex::new(Vec::new())); let misses2 = misses.clone(); // Registry returns an invalid response. let registry = registry::RegistryBuilder::new() .http_index() .http_api() .add_responder("/index/de/la/delay_with_underscore", move |req, server| { let mut lock = arc.lock().unwrap(); *lock += 1; if *lock <= 1 { server.not_found(req) } else { server.index(req) } }) .not_found_handler(move |req, _| { misses.lock().unwrap().push(req.url.to_string()); Response { body: b"not found".to_vec(), code: 404, headers: vec![], } }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "delay_with_underscore" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(0) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] delay_with_underscore v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] delay_with_underscore v0.0.1 ([ROOT]/foo) [UPLOADED] delay_with_underscore v0.0.1 to registry `crates-io` [NOTE] waiting for delay_with_underscore v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] delay_with_underscore v0.0.1 at registry `crates-io` "#]]) .run(); // Verify the repsponder has been pinged let lock = arc2.lock().unwrap(); assert_eq!(*lock, 2); drop(lock); { let misses = misses2.lock().unwrap(); assert!( misses.len() == 1, "should only have 1 not found URL; instead found {misses:?}" ); } let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] delay_with_underscore = "0.0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build").with_status(0).run(); } #[cargo_test] fn wait_for_subsequent_publish() { // Counter for number of tries before the package is "published" let arc: Arc> = Arc::new(Mutex::new(0)); let arc2 = arc.clone(); let publish_req = Arc::new(Mutex::new(None)); let publish_req2 = publish_req.clone(); let registry = registry::RegistryBuilder::new() .http_index() .http_api() .add_responder("/api/v1/crates/new", move |req, server| { // Capture the publish request, but defer publishing *publish_req.lock().unwrap() = Some(req.clone()); server.ok(req) }) .add_responder("/index/de/la/delay", move |req, server| { let mut lock = arc.lock().unwrap(); *lock += 1; if *lock == 3 { // Run the publish on the 3rd attempt let rep = server .check_authorized_publish(&publish_req2.lock().unwrap().as_ref().unwrap()); assert_eq!(rep.code, 200); } server.index(req) }) .build(); // Publish an earlier version Package::new("delay", "0.0.1") .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "delay" version = "0.0.2" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(0) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] delay v0.0.2 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] delay v0.0.2 ([ROOT]/foo) [UPLOADED] delay v0.0.2 to registry `crates-io` [NOTE] waiting for delay v0.0.2 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] delay v0.0.2 at registry `crates-io` "#]]) .run(); // Verify the responder has been pinged let lock = arc2.lock().unwrap(); assert_eq!(*lock, 3); drop(lock); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] delay = "0.0.2" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check").with_status(0).run(); } #[cargo_test] fn skip_wait_for_publish() { // Intentionally using local registry so the crate never makes it to the index let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .file( ".cargo/config.toml", " [publish] timeout = 0 ", ) .build(); p.cargo("publish --no-verify -Zpublish-timeout") .replace_crates_io(registry.index_url()) .masquerade_as_nightly_cargo(&["publish-timeout"]) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` "#]]) .run(); } #[cargo_test] fn timeout_waiting_for_publish() { // Publish doesn't happen within the timeout window. let registry = registry::RegistryBuilder::new() .http_api() .delayed_index_update(20) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "delay" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [publish] timeout = 2 "#, ) .build(); p.cargo("publish --no-verify -Zpublish-timeout") .replace_crates_io(registry.index_url()) .masquerade_as_nightly_cargo(&["publish-timeout"]) .with_status(0) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] delay v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] delay v0.0.1 ([ROOT]/foo) [UPLOADED] delay v0.0.1 to registry `crates-io` [NOTE] waiting for delay v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [WARNING] timed out waiting for delay v0.0.1 to be available in registry `crates-io` | = [NOTE] the registry may have a backlog that is delaying making the crate available. The crate should be available soon. "#]]) .run(); } #[cargo_test] fn timeout_waiting_for_dependency_publish() { // Publish doesn't happen within the timeout window. let registry = registry::RegistryBuilder::new() .http_api() .delayed_index_update(20) .build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["main", "other", "dep"] "#, ) .file( "main/Cargo.toml", r#" [package] name = "main" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies] dep = { version = "0.0.1", path = "../dep" } "#, ) .file("main/src/main.rs", "fn main() {}") .file( "other/Cargo.toml", r#" [package] name = "other" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies] dep = { version = "0.0.1", path = "../dep" } "#, ) .file("other/src/main.rs", "fn main() {}") .file( "dep/Cargo.toml", r#" [package] name = "dep" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("dep/src/lib.rs", "") .file( ".cargo/config.toml", r#" [publish] timeout = 2 "#, ) .build(); p.cargo("publish --no-verify -Zpublish-timeout") .replace_crates_io(registry.index_url()) .masquerade_as_nightly_cargo(&["publish-timeout"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] dep v0.0.1 ([ROOT]/foo/dep) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] main v0.0.1 ([ROOT]/foo/main) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] other v0.0.1 ([ROOT]/foo/other) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] dep v0.0.1 ([ROOT]/foo/dep) [UPLOADED] dep v0.0.1 to registry `crates-io` [NOTE] waiting for dep v0.0.1 to be available at registry `crates-io`. 2 remaining crates to be published [WARNING] timed out waiting for dep v0.0.1 to be available in registry `crates-io` | = [NOTE] the registry may have a backlog that is delaying making the crate available. The crate should be available soon. [ERROR] unable to publish main v0.0.1 and other v0.0.1 due to a timeout while waiting for published dependencies to be available. "#]]) .run(); } #[cargo_test] fn package_selection() { let registry = registry::RegistryBuilder::new().http_api().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "#[test] fn a() {}") .file("b/Cargo.toml", &basic_manifest("b", "0.1.0")) .file("b/src/lib.rs", "#[test] fn b() {}") .build(); p.cargo("publish --no-verify --dry-run --workspace") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no description, license, license-file, documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] a v0.1.0 ([ROOT]/foo/a) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [WARNING] manifest has no description, license, license-file, documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] b v0.1.0 ([ROOT]/foo/b) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] a v0.1.0 ([ROOT]/foo/a) [WARNING] aborting upload due to dry run [UPLOADING] b v0.1.0 ([ROOT]/foo/b) [WARNING] aborting upload due to dry run "#]]) .with_stdout_data(str![[r#""#]]) .run(); p.cargo("publish --no-verify --dry-run --package a --package b") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no description, license, license-file, documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] a v0.1.0 ([ROOT]/foo/a) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [WARNING] manifest has no description, license, license-file, documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] b v0.1.0 ([ROOT]/foo/b) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] a v0.1.0 ([ROOT]/foo/a) [WARNING] aborting upload due to dry run [UPLOADING] b v0.1.0 ([ROOT]/foo/b) [WARNING] aborting upload due to dry run "#]]) .with_stdout_data(str![[r#""#]]) .run(); p.cargo("publish --no-verify --dry-run --workspace --exclude b") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no description, license, license-file, documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] a v0.1.0 ([ROOT]/foo/a) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] a v0.1.0 ([ROOT]/foo/a) [WARNING] aborting upload due to dry run "#]]) .with_stdout_data(str![[r#""#]]) .run(); } #[cargo_test] fn wait_for_git_publish() { // Slow publish to an index with a git index. let registry = registry::RegistryBuilder::new() .http_api() .delayed_index_update(5) .build(); // Publish an earlier version Package::new("delay", "0.0.1") .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "delay" version = "0.0.2" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(0) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] delay v0.0.2 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] delay v0.0.2 ([ROOT]/foo) [UPLOADED] delay v0.0.2 to registry `crates-io` [NOTE] waiting for delay v0.0.2 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] delay v0.0.2 at registry `crates-io` "#]]) .run(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] delay = "0.0.2" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check").with_status(0).run(); } #[cargo_test] fn invalid_token() { // Checks publish behavior with an invalid token. let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .env("CARGO_REGISTRY_TOKEN", "\x16") .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/ Caused by: token contains invalid characters. Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header. "#]]) .with_status(101) .run(); } #[cargo_test] fn versionless_package() { // Use local registry for faster test times since no publish will occur let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" description = "foo" "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] `foo` cannot be published. `package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. "#]]) .run(); } // A workspace with three projects that depend on one another (level1 -> level2 -> level3). // level1 is a binary package, to test lockfile generation. fn workspace_with_local_deps_project() -> Project { project() .file( "Cargo.toml", r#" [workspace] members = ["level1", "level2", "level3"] [workspace.dependencies] level2 = { path = "level2", version = "0.0.1" } "# ) .file( "level1/Cargo.toml", r#" [package] name = "level1" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "level1" repository = "bar" [dependencies] # Let one dependency also specify features, for the added test coverage when generating package files. level2 = { workspace = true, features = ["foo"] } "#, ) .file("level1/src/main.rs", "fn main() {}") .file( "level2/Cargo.toml", r#" [package] name = "level2" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "level2" repository = "bar" [features] foo = [] [dependencies] level3 = { path = "../level3", version = "0.0.1" } "# ) .file("level2/src/lib.rs", "") .file( "level3/Cargo.toml", r#" [package] name = "level3" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "level3" repository = "bar" "#, ) .file("level3/src/lib.rs", "") .build() } #[cargo_test] fn workspace_with_local_deps() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = workspace_with_local_deps_project(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] level3 v0.0.1 ([ROOT]/foo/level3) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] level2 v0.0.1 ([ROOT]/foo/level2) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] level1 v0.0.1 ([ROOT]/foo/level1) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] level3 v0.0.1 ([ROOT]/foo/level3) [COMPILING] level3 v0.0.1 ([ROOT]/foo/target/package/level3-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [VERIFYING] level2 v0.0.1 ([ROOT]/foo/level2) [UPDATING] crates.io index [UNPACKING] level3 v0.0.1 (registry `[ROOT]/foo/target/package/tmp-registry`) [COMPILING] level3 v0.0.1 [COMPILING] level2 v0.0.1 ([ROOT]/foo/target/package/level2-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [VERIFYING] level1 v0.0.1 ([ROOT]/foo/level1) [UPDATING] crates.io index [UNPACKING] level2 v0.0.1 (registry `[ROOT]/foo/target/package/tmp-registry`) [COMPILING] level2 v0.0.1 [COMPILING] level1 v0.0.1 ([ROOT]/foo/target/package/level1-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] level3 v0.0.1 ([ROOT]/foo/level3) [UPLOADED] level3 v0.0.1 to registry `crates-io` [NOTE] waiting for level3 v0.0.1 to be available at registry `crates-io`. 2 remaining crates to be published [PUBLISHED] level3 v0.0.1 at registry `crates-io` [UPLOADING] level2 v0.0.1 ([ROOT]/foo/level2) [UPLOADED] level2 v0.0.1 to registry `crates-io` [NOTE] waiting for level2 v0.0.1 to be available at registry `crates-io`. 1 remaining crate to be published [PUBLISHED] level2 v0.0.1 at registry `crates-io` [UPLOADING] level1 v0.0.1 ([ROOT]/foo/level1) [UPLOADED] level1 v0.0.1 to registry `crates-io` [NOTE] waiting for level1 v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] level1 v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn workspace_parallel() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b", "c"] "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "a" repository = "bar" "#, ) .file("a/src/lib.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "b" repository = "bar" "#, ) .file("b/src/lib.rs", "") .file( "c/Cargo.toml", r#" [package] name = "c" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "c" repository = "bar" [dependencies] a = { path = "../a", version = "0.0.1" } b = { path = "../b", version = "0.0.1" } "#, ) .file("c/src/lib.rs", "") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data( str![[r#" [UPDATING] crates.io index [PACKAGING] a v0.0.1 ([ROOT]/foo/a) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] b v0.0.1 ([ROOT]/foo/b) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] c v0.0.1 ([ROOT]/foo/c) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] a v0.0.1 ([ROOT]/foo/a) [COMPILING] a v0.0.1 ([ROOT]/foo/target/package/a-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [VERIFYING] b v0.0.1 ([ROOT]/foo/b) [COMPILING] b v0.0.1 ([ROOT]/foo/target/package/b-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [VERIFYING] c v0.0.1 ([ROOT]/foo/c) [UPDATING] crates.io index [UNPACKING] a v0.0.1 (registry `[ROOT]/foo/target/package/tmp-registry`) [UNPACKING] b v0.0.1 (registry `[ROOT]/foo/target/package/tmp-registry`) [COMPILING] a v0.0.1 [COMPILING] b v0.0.1 [COMPILING] c v0.0.1 ([ROOT]/foo/target/package/c-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADED] b v0.0.1 to registry `crates-io` [UPLOADED] a v0.0.1 to registry `crates-io` [NOTE] waiting for a v0.0.1 or b v0.0.1 to be available at registry `crates-io`. 1 remaining crate to be published [PUBLISHED] a v0.0.1 and b v0.0.1 at registry `crates-io` [UPLOADING] c v0.0.1 ([ROOT]/foo/c) [UPLOADED] c v0.0.1 to registry `crates-io` [NOTE] waiting for c v0.0.1 to be available at registry `crates-io` [PUBLISHED] c v0.0.1 at registry `crates-io` [UPLOADING] a v0.0.1 ([ROOT]/foo/a) [UPLOADING] b v0.0.1 ([ROOT]/foo/b) [UPDATING] crates.io index [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly "#]] .unordered(), ) .run(); } #[cargo_test] fn workspace_missing_dependency() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "a" repository = "bar" "#, ) .file("a/src/lib.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "b" repository = "bar" [dependencies] a = { path = "../a", version = "0.0.1" } "#, ) .file("b/src/lib.rs", "") .build(); p.cargo("publish -p b") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] b v0.0.1 ([ROOT]/foo/b) [UPDATING] crates.io index [ERROR] failed to prepare local package for uploading Caused by: no matching package named `a` found location searched: crates.io index required by package `b v0.0.1 ([ROOT]/foo/b)` "#]]) .run(); p.cargo("publish -p a") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] a v0.0.1 ([ROOT]/foo/a) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] a v0.0.1 ([ROOT]/foo/a) [COMPILING] a v0.0.1 ([ROOT]/foo/target/package/a-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] a v0.0.1 ([ROOT]/foo/a) [UPLOADED] a v0.0.1 to registry `crates-io` [NOTE] waiting for a v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] a v0.0.1 at registry `crates-io` "#]]) .run(); // Publishing the whole workspace now will fail, as `a` is already published. p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] crate a@0.0.1 already exists on crates.io index "#]]) .run(); } #[cargo_test] fn one_unpublishable_package() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["dep", "main"] "#, ) .file( "main/Cargo.toml", r#" [package] name = "main" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "main" repository = "bar" publish = false [dependencies] dep = { path = "../dep", version = "0.1.0" } "#, ) .file("main/src/main.rs", "fn main() {}") .file( "dep/Cargo.toml", r#" [package] name = "dep" version = "0.1.0" edition = "2015" authors = [] license = "MIT" description = "dep" repository = "bar" "#, ) .file("dep/src/lib.rs", "") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] dep v0.1.0 ([ROOT]/foo/dep) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] dep v0.1.0 ([ROOT]/foo/dep) [COMPILING] dep v0.1.0 ([ROOT]/foo/target/package/dep-0.1.0) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] dep v0.1.0 ([ROOT]/foo/dep) [UPLOADED] dep v0.1.0 to registry `crates-io` [NOTE] waiting for dep v0.1.0 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] dep v0.1.0 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn virtual_ws_with_multiple_unpublishable_package() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["dep", "main", "publishable"] "#, ) .file( "main/Cargo.toml", r#" [package] name = "main" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "main" repository = "bar" publish = false [dependencies] dep = { path = "../dep", version = "0.1.0" } "#, ) .file("main/src/main.rs", "fn main() {}") .file( "dep/Cargo.toml", r#" [package] name = "dep" version = "0.1.0" edition = "2015" authors = [] license = "MIT" description = "dep" repository = "bar" publish = false "#, ) .file("dep/src/lib.rs", "") .file( "publishable/Cargo.toml", r#" [package] name = "publishable" version = "0.1.0" edition = "2015" license = "MIT" description = "foo" repository = "foo" "#, ) .file("publishable/src/lib.rs", "") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] publishable v0.1.0 ([ROOT]/foo/publishable) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] publishable v0.1.0 ([ROOT]/foo/publishable) [COMPILING] publishable v0.1.0 ([ROOT]/foo/target/package/publishable-0.1.0) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] publishable v0.1.0 ([ROOT]/foo/publishable) [UPLOADED] publishable v0.1.0 to registry `crates-io` [NOTE] waiting for publishable v0.1.0 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] publishable v0.1.0 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn workspace_flag_with_unpublishable_packages() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["publishable", "non-publishable"] [package] name = "cwd" version = "0.0.0" edition = "2015" license = "MIT" description = "foo" repository = "foo" publish = false "#, ) .file("src/lib.rs", "") .file( "publishable/Cargo.toml", r#" [package] name = "publishable" version = "0.0.0" edition = "2015" license = "MIT" description = "foo" repository = "foo" publish = true "#, ) .file("publishable/src/lib.rs", "") .file( "non-publishable/Cargo.toml", r#" [package] name = "non-publishable" version = "0.0.0" edition = "2015" license = "MIT" description = "foo" repository = "foo" publish = false "#, ) .file("non-publishable/src/lib.rs", "") .build(); p.cargo("publish --workspace") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] publishable v0.0.0 ([ROOT]/foo/publishable) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] publishable v0.0.0 ([ROOT]/foo/publishable) [COMPILING] publishable v0.0.0 ([ROOT]/foo/target/package/publishable-0.0.0) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] publishable v0.0.0 ([ROOT]/foo/publishable) [UPLOADED] publishable v0.0.0 to registry `crates-io` [NOTE] waiting for publishable v0.0.0 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] publishable v0.0.0 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn unpublishable_package_as_versioned_dev_dep() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["dep", "main"] "#, ) .file( "main/Cargo.toml", r#" [package] name = "main" version = "0.0.1" edition = "2015" license = "MIT" description = "main" repository = "bar" [dev-dependencies] dep = { path = "../dep", version = "0.1.0" } "#, ) .file("main/src/main.rs", "fn main() {}") .file( "dep/Cargo.toml", r#" [package] name = "dep" version = "0.1.0" edition = "2015" license = "MIT" description = "dep" repository = "bar" publish = false "#, ) .file("dep/src/lib.rs", "") .build(); // It is expected to find the versioned dev dep not being published, // regardless with `--dry-run` or `--no-verify`. p.cargo("publish") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] main v0.0.1 ([ROOT]/foo/main) [UPDATING] crates.io index [ERROR] failed to prepare local package for uploading Caused by: no matching package named `dep` found location searched: crates.io index required by package `main v0.0.1 ([ROOT]/foo/main)` "#]]) .run(); p.cargo("publish --dry-run") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] main v0.0.1 ([ROOT]/foo/main) [UPDATING] crates.io index [ERROR] failed to prepare local package for uploading Caused by: no matching package named `dep` found location searched: crates.io index required by package `main v0.0.1 ([ROOT]/foo/main)` "#]]) .run(); p.cargo("publish --no-verify") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] main v0.0.1 ([ROOT]/foo/main) [UPDATING] crates.io index [ERROR] failed to prepare local package for uploading Caused by: no matching package named `dep` found location searched: crates.io index required by package `main v0.0.1 ([ROOT]/foo/main)` "#]]) .run(); } #[cargo_test] fn all_unpublishable_packages() { let registry = RegistryBuilder::new().http_api().http_index().build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["non-publishable1", "non-publishable2"] "#, ) .file( "non-publishable1/Cargo.toml", r#" [package] name = "non-publishable1" version = "0.0.0" edition = "2015" license = "MIT" description = "foo" repository = "foo" publish = false "#, ) .file("non-publishable1/src/lib.rs", "") .file( "non-publishable2/Cargo.toml", r#" [package] name = "non-publishable2" version = "0.0.0" edition = "2015" license = "MIT" description = "foo" repository = "foo" publish = false "#, ) .file("non-publishable2/src/lib.rs", "") .build(); p.cargo("publish --workspace") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [WARNING] nothing to publish, but found 2 unpublishable packages | = [HELP] to publish packages, set `package.publish` to `true` or a non-empty list "#]]) .run(); } #[cargo_test] fn checksum_changed() { let registry = RegistryBuilder::new().http_api().http_index().build(); Package::new("dep", "1.0.0").publish(); Package::new("transitive", "1.0.0") .dep("dep", "1.0.0") .publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["dep"] [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" [dependencies] dep = { path = "./dep", version = "1.0.0" } transitive = "1.0.0" "#, ) .file("src/lib.rs", "") .file( "dep/Cargo.toml", r#" [package] name = "dep" version = "1.0.0" edition = "2015" "#, ) .file("dep/src/lib.rs", "") .build(); p.cargo("check").run(); p.cargo("publish --dry-run --workspace") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] crate dep@1.0.0 already exists on crates.io index [WARNING] manifest has no description, license, license-file, documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] dep v1.0.0 ([ROOT]/foo/dep) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] dep v1.0.0 ([ROOT]/foo/dep) [COMPILING] dep v1.0.0 ([ROOT]/foo/target/package/dep-1.0.0) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [VERIFYING] foo v0.0.1 ([ROOT]/foo) [UNPACKING] dep v1.0.0 (registry `[ROOT]/foo/target/package/tmp-registry`) [COMPILING] dep v1.0.0 [COMPILING] transitive v1.0.0 [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] dep v1.0.0 ([ROOT]/foo/dep) [WARNING] aborting upload due to dry run [UPLOADING] foo v0.0.1 ([ROOT]/foo) [WARNING] aborting upload due to dry run "#]]) .run(); } #[cargo_test] fn workspace_publish_rate_limit_error() { let registry = registry::RegistryBuilder::new() .http_api() .http_index() .add_responder("/api/v1/crates/new", |_req, _| { // For simplicity, let's just return rate limit error for all requests // This simulates hitting rate limit during workspace publish Response { code: 429, headers: vec!["Retry-After: 3600".to_string()], body: format!( "You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email help@crates.io to have your limit increased." ).into_bytes(), } }) .build(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["package_a", "package_b", "package_c"] "#, ) .file("src/lib.rs", "") .file( "package_a/Cargo.toml", r#" [package] name = "package_a" version = "0.1.0" edition = "2015" license = "MIT" description = "package a" repository = "https://github.com/test/package_a" "#, ) .file("package_a/src/lib.rs", "") .file( "package_b/Cargo.toml", r#" [package] name = "package_b" version = "0.1.0" edition = "2015" license = "MIT" description = "package b" repository = "https://github.com/test/package_b" "#, ) .file("package_b/src/lib.rs", "") .file( "package_c/Cargo.toml", r#" [package] name = "package_c" version = "0.1.0" edition = "2015" license = "MIT" description = "package c" repository = "https://github.com/test/package_c" [dependencies] package_a = { version = "0.1.0", path = "../package_a" } "#, ) .file("package_c/src/lib.rs", "") .build(); // This demonstrates the improved error message after the fix // The user now knows which package failed and what packages remain to be published p.cargo("publish --workspace --no-verify") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] package_a v0.1.0 ([ROOT]/foo/package_a) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] package_b v0.1.0 ([ROOT]/foo/package_b) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] package_c v0.1.0 ([ROOT]/foo/package_c) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [UPLOADING] package_a v0.1.0 ([ROOT]/foo/package_a) [ERROR] failed to publish package_a v0.1.0 to registry at http://127.0.0.1:[..]/ [NOTE] the following crates have not been published yet: package_b v0.1.0 ([ROOT]/foo/package_b) package_c v0.1.0 ([ROOT]/foo/package_c) Caused by: failed to get a 200 OK response, got 429 headers: HTTP/1.1 429 Content-Length: 172 Connection: close Retry-After: 3600 body: You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email help@crates.io to have your limit increased. "#]]) .run(); } #[cargo_test] fn workspace_circular_publish_dependency() { // Verify detection and reporting of workspace circular dependencies. let registry = registry::RegistryBuilder::new() .http_api() .http_index() .build(); cargo_test_support::registry::Package::new("foo", "0.1.0").publish(); cargo_test_support::registry::Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "bar"] "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.1" edition = "2015" license = "MIT" description = "foo" repository = "foo" [dependencies] bar = { version = "0.1", path = "../bar" } "#, ) .file("foo/src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.1" edition = "2015" license = "MIT" description = "bar" repository = "bar" [dev-dependencies] foo = { version = "0.1", path = "../foo" } "#, ) .file("bar/src/lib.rs", "") .build(); // Ensure the circular dependency is caught and reported clearly. p.cargo("publish --workspace --no-verify -Zpublish-timeout --config publish.timeout=1") .masquerade_as_nightly_cargo(&["publish-timeout"]) .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.1.1 ([ROOT]/foo/foo) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] bar v0.1.1 ([ROOT]/foo/bar) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [ERROR] circular dependency detected while publishing bar v0.1.1 and foo v0.1.1 [HELP] to break a cycle between dev-dependencies and other dependencies, remove the version field on the dev-dependency so it will be implicitly stripped on publish "#]]) .run(); } #[cargo_test] fn workspace_circular_publish_dependency_with_non_cycle_package() { // Verify that circular dependency errors only report packages actively involved in the cycle. // Package 'c' is independent and should be excluded from the error message, // even though the cycle between 'a' and 'b' blocks the overall workspace publish. let registry = registry::RegistryBuilder::new() .http_api() .http_index() .build(); cargo_test_support::registry::Package::new("a", "1.0.0").publish(); cargo_test_support::registry::Package::new("b", "1.0.0").publish(); cargo_test_support::registry::Package::new("c", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b", "c"] "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "1.0.1" edition = "2015" license = "MIT" description = "a" repository = "a" [dependencies] b = { version = "1.0", path = "../b" } c = { version = "1.0", path = "../c" } "#, ) .file("a/src/lib.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "1.0.1" edition = "2015" license = "MIT" description = "b" repository = "b" [dependencies] c = { version = "1.0", path = "../c" } [dev-dependencies] a = { version = "1.0", path = "../a" } "#, ) .file("b/src/lib.rs", "") .file( "c/Cargo.toml", r#" [package] name = "c" version = "1.0.1" edition = "2015" license = "MIT" description = "c" repository = "c" "#, ) .file("c/src/lib.rs", "") .build(); p.cargo("publish --workspace --no-verify -Zpublish-timeout --config publish.timeout=1") .masquerade_as_nightly_cargo(&["publish-timeout"]) .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] c v1.0.1 ([ROOT]/foo/c) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] b v1.0.1 ([ROOT]/foo/b) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [PACKAGING] a v1.0.1 ([ROOT]/foo/a) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [ERROR] circular dependency detected while publishing a v1.0.1 and b v1.0.1 [HELP] to break a cycle between dev-dependencies and other dependencies, remove the version field on the dev-dependency so it will be implicitly stripped on publish "#]]) .run(); } ================================================ FILE: tests/testsuite/publish_lockfile.rs ================================================ //! Tests for including `Cargo.lock` when publishing/packaging. use std::fs::File; use crate::prelude::*; use crate::utils::cargo_process; use cargo_test_support::registry::Package; use cargo_test_support::{ basic_manifest, git, paths, project, publish::validate_crate_contents, str, }; fn pl_manifest(name: &str, version: &str, extra: &str) -> String { format!( r#" [package] name = "{}" version = "{}" edition = "2015" authors = [] license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" {} "#, name, version, extra ) } #[cargo_test] fn removed() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["publish-lockfile"] [package] name = "foo" version = "0.1.0" edition = "2015" publish-lockfile = true license = "MIT" description = "foo" documentation = "foo" homepage = "foo" repository = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("package") .masquerade_as_nightly_cargo(&["publish-lockfile"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: the cargo feature `publish-lockfile` has been removed in the 1.37 release Remove the feature from Cargo.toml to remove this error. See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#publish-lockfile for more information about using this feature. "#]]) .run(); } #[cargo_test] fn package_lockfile() { let p = project() .file("Cargo.toml", &pl_manifest("foo", "0.0.1", "")) .file("src/main.rs", "fn main() {}") .build(); p.cargo("package") .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); p.cargo("package -l") .with_stdout_data(str![[r#" Cargo.lock Cargo.toml Cargo.toml.orig src/main.rs "#]]) .run(); p.cargo("package") .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); validate_crate_contents( f, "foo-0.0.1.crate", &["Cargo.toml", "Cargo.toml.orig", "Cargo.lock", "src/main.rs"], (), ); } #[cargo_test] fn package_lockfile_git_repo() { // Create a Git repository containing a minimal Rust project. let g = git::repo(&paths::root().join("foo")) .file("Cargo.toml", &pl_manifest("foo", "0.0.1", "")) .file("src/main.rs", "fn main() {}") .build(); cargo_process("package -l") .cwd(g.root()) .with_stdout_data(str![[r#" .cargo_vcs_info.json Cargo.lock Cargo.toml Cargo.toml.orig src/main.rs "#]]) .run(); cargo_process("package -v") .cwd(g.root()) .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [ARCHIVING] .cargo_vcs_info.json [ARCHIVING] Cargo.lock [ARCHIVING] Cargo.toml [ARCHIVING] Cargo.toml.orig [ARCHIVING] src/main.rs [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn lock_file_with_library() { let p = project() .file("Cargo.toml", &pl_manifest("foo", "0.0.1", "")) .file("src/lib.rs", "") .build(); p.cargo("package").run(); let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); validate_crate_contents( f, "foo-0.0.1.crate", &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs", "Cargo.lock"], (), ); } #[cargo_test] fn lock_file_and_workspace() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo"] "#, ) .file("foo/Cargo.toml", &pl_manifest("foo", "0.0.1", "")) .file("foo/src/main.rs", "fn main() {}") .build(); p.cargo("package").cwd("foo").run(); let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); validate_crate_contents( f, "foo-0.0.1.crate", &["Cargo.toml", "Cargo.toml.orig", "src/main.rs", "Cargo.lock"], (), ); } #[cargo_test] fn note_resolve_changes() { // `multi` has multiple sources (path and registry). Package::new("multi", "0.1.0").publish(); // `updated` is always from registry, but should not change. Package::new("updated", "1.0.0").publish(); // `patched` is [patch]ed. Package::new("patched", "1.0.0").publish(); let p = project() .file( "Cargo.toml", &pl_manifest( "foo", "0.0.1", r#" [dependencies] multi = { path = "multi", version = "0.1" } updated = "1.0" patched = "1.0" [patch.crates-io] patched = { path = "patched" } "#, ), ) .file("src/main.rs", "fn main() {}") .file("multi/Cargo.toml", &basic_manifest("multi", "0.1.0")) .file("multi/src/lib.rs", "") .file("patched/Cargo.toml", &basic_manifest("patched", "1.0.0")) .file("patched/src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); // Make sure this does not change or warn. Package::new("updated", "1.0.1").publish(); p.cargo("package --no-verify -v --allow-dirty") .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [ARCHIVING] Cargo.lock [UPDATING] `dummy-registry` index [NOTE] package `multi v0.1.0` added to the packaged Cargo.lock file, was originally sourced from `[ROOT]/foo/multi` [NOTE] package `patched v1.0.0` added to the packaged Cargo.lock file, was originally sourced from `[ROOT]/foo/patched` [ARCHIVING] Cargo.toml [ARCHIVING] Cargo.toml.orig [ARCHIVING] src/main.rs [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) ... "#]].unordered()) .run(); } #[cargo_test] fn outdated_lock_version_change_does_not_warn() { // If the version of the package being packaged changes, but Cargo.lock is // not updated, don't bother warning about it. let p = project() .file("Cargo.toml", &pl_manifest("foo", "0.1.0", "")) .file("src/main.rs", "fn main() {}") .build(); p.cargo("generate-lockfile").run(); p.change_file("Cargo.toml", &pl_manifest("foo", "0.2.0", "")); p.cargo("package --no-verify") .with_stderr_data(str![[r#" [PACKAGING] foo v0.2.0 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) "#]]) .run(); } #[cargo_test] fn no_warn_workspace_extras() { // Other entries in workspace lock file should be ignored. Package::new("dep1", "1.0.0").publish(); Package::new("dep2", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file( "a/Cargo.toml", &pl_manifest( "a", "0.1.0", r#" [dependencies] dep1 = "1.0" "#, ), ) .file("a/src/main.rs", "fn main() {}") .file( "b/Cargo.toml", &pl_manifest( "b", "0.1.0", r#" [dependencies] dep2 = "1.0" "#, ), ) .file("b/src/main.rs", "fn main() {}") .build(); p.cargo("generate-lockfile").run(); p.cargo("package --no-verify") .cwd("a") .with_stderr_data(str![[r#" [PACKAGING] a v0.1.0 ([ROOT]/foo/a) [UPDATING] `dummy-registry` index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) "#]]) .run(); } #[cargo_test] fn warn_package_with_yanked() { Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", &pl_manifest( "foo", "0.0.1", r#" [dependencies] bar = "0.1" "#, ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("generate-lockfile").run(); Package::new("bar", "0.1.0").yanked(true).publish(); // Make sure it sticks with the locked (yanked) version. Package::new("bar", "0.1.1").publish(); p.cargo("package --no-verify") .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] `dummy-registry` index [WARNING] package `bar v0.1.0` in Cargo.lock is yanked in registry `crates-io`, consider updating to a version that is not yanked [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) "#]]) .run(); } #[cargo_test] fn warn_install_with_yanked() { Package::new("bar", "0.1.0").yanked(true).publish(); Package::new("bar", "0.1.1").publish(); Package::new("foo", "0.1.0") .dep("bar", "0.1") .file("src/main.rs", "fn main() {}") .file( "Cargo.lock", r#" [[package]] name = "bar" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "foo" version = "0.1.0" dependencies = [ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] "#, ) .publish(); cargo_process("install --locked foo") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] foo v0.1.0 (registry `dummy-registry`) [INSTALLING] foo v0.1.0 [WARNING] package `bar v0.1.0` in Cargo.lock is yanked in registry `crates-io`, consider running without --locked [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0 (registry `dummy-registry`) [COMPILING] bar v0.1.0 [COMPILING] foo v0.1.0 [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE] [INSTALLED] package `foo v0.1.0` (executable `foo[EXE]`) [WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries "#]]) .run(); // Try again without --locked, make sure it uses 0.1.1 and does not warn. cargo_process("install --force foo") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [INSTALLING] foo v0.1.0 [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.1 (registry `dummy-registry`) [COMPILING] bar v0.1.1 [COMPILING] foo v0.1.0 [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [REPLACING] [ROOT]/home/.cargo/bin/foo[EXE] [REPLACED] package `foo v0.1.0` with `foo v0.1.0` (executable `foo[EXE]`) [WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries "#]]) .run(); } #[cargo_test] fn ignore_lockfile() { // With an explicit `include` list, but Cargo.lock in .gitignore, don't // complain about `Cargo.lock` being ignored. Note that it is still // included in the packaged regardless. let p = git::new("foo", |p| { p.file( "Cargo.toml", &pl_manifest( "foo", "0.0.1", r#" include = [ "src/main.rs" ] "#, ), ) .file("src/main.rs", "fn main() {}") .file(".gitignore", "Cargo.lock") }); p.cargo("package -l") .with_stdout_data(str![[r#" .cargo_vcs_info.json Cargo.lock Cargo.toml Cargo.toml.orig src/main.rs "#]]) .run(); p.cargo("generate-lockfile").run(); p.cargo("package -v") .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [ARCHIVING] .cargo_vcs_info.json [ARCHIVING] Cargo.lock [ARCHIVING] Cargo.toml [ARCHIVING] Cargo.toml.orig [ARCHIVING] src/main.rs [PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn ignore_lockfile_inner() { // Ignore `Cargo.lock` if in .gitignore in a git subdirectory. let p = git::new("foo", |p| { p.no_manifest() .file("bar/Cargo.toml", &pl_manifest("bar", "0.0.1", "")) .file("bar/src/main.rs", "fn main() {}") .file("bar/.gitignore", "Cargo.lock") }); p.cargo("generate-lockfile").cwd("bar").run(); p.cargo("package -v --no-verify") .cwd("bar") .with_stderr_data(str![[r#" [PACKAGING] bar v0.0.1 ([ROOT]/foo/bar) [ARCHIVING] .cargo_vcs_info.json [ARCHIVING] .gitignore [ARCHIVING] Cargo.lock [ARCHIVING] Cargo.toml [ARCHIVING] Cargo.toml.orig [ARCHIVING] src/main.rs [PACKAGED] 6 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) "#]]) .run(); } #[cargo_test] fn use_workspace_root_lockfile() { // Issue #11148 // Workspace members should use `Cargo.lock` at workspace root Package::new("serde", "0.2.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies] serde = "0.2" [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "bar" workspace = ".." [dependencies] serde = "0.2" "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); // Create `Cargo.lock` in the workspace root. p.cargo("generate-lockfile").run(); // Now, add a newer version of `serde`. Package::new("serde", "0.2.1").publish(); // Expect: package `bar` uses `serde v0.2.0` as required by workspace `Cargo.lock`. p.cargo("package --workspace") .with_stderr_data(str![[r#" [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] bar v0.0.1 ([ROOT]/foo/bar) [UPDATING] `dummy-registry` index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] bar v0.0.1 ([ROOT]/foo/bar) [DOWNLOADING] crates ... [DOWNLOADED] serde v0.2.0 (registry `dummy-registry`) [COMPILING] serde v0.2.0 [COMPILING] bar v0.0.1 ([ROOT]/foo/target/package/bar-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); let package_path = p.root().join("target/package/foo-0.0.1.crate"); assert!(package_path.is_file()); let f = File::open(&package_path).unwrap(); validate_crate_contents( f, "foo-0.0.1.crate", &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], (), ); let package_path = p.root().join("target/package/bar-0.0.1.crate"); assert!(package_path.is_file()); let f = File::open(&package_path).unwrap(); validate_crate_contents( f, "bar-0.0.1.crate", &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], (), ); } ================================================ FILE: tests/testsuite/read_manifest.rs ================================================ //! Tests for the `cargo read-manifest` command. use crate::prelude::*; use cargo_test_support::{basic_bin_manifest, main_file, project, str}; pub fn basic_bin_manifest_with_readme(name: &str, readme_filename: &str) -> String { format!( r#" [package] name = "{}" version = "0.5.0" authors = ["wycats@example.com"] readme = {} [[bin]] name = "{}" "#, name, readme_filename, name ) } #[cargo_test] fn cargo_read_manifest_path_to_cargo_toml_relative() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest --manifest-path foo/Cargo.toml") .cwd(p.root().parent().unwrap()) .with_stdout_data( str![[r#" { "readme": null, "...": "{...}" } "#]] .is_json(), ) .run(); } #[cargo_test] fn cargo_read_manifest_path_to_cargo_toml_absolute() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest --manifest-path") .arg(p.root().join("Cargo.toml")) .cwd(p.root().parent().unwrap()) .with_stdout_data( str![[r#" { "readme": null, "...": "{...}" } "#]] .is_json(), ) .run(); } #[cargo_test] fn cargo_read_manifest_path_to_cargo_toml_parent_relative() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest --manifest-path foo") .cwd(p.root().parent().unwrap()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] manifest path `foo` is a directory but expected a file [HELP] [ROOT]/foo/Cargo.toml exists "#]]) .run(); } #[cargo_test] fn cargo_read_manifest_path_to_cargo_toml_parent_absolute() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest --manifest-path") .arg(p.root()) .cwd(p.root().parent().unwrap()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] manifest path `[ROOT]/foo` is a directory but expected a file [HELP] [ROOT]/foo/Cargo.toml exists "#]]) .run(); } #[cargo_test] fn cargo_read_manifest_cwd() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest") .with_stdout_data( str![[r#" { "readme": null, "...": "{...}" } "#]] .is_json(), ) .run(); } #[cargo_test] fn cargo_read_manifest_with_specified_readme() { let p = project() .file( "Cargo.toml", &basic_bin_manifest_with_readme("foo", r#""SomeReadme.txt""#), ) .file("SomeReadme.txt", "Sample Project") .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest") .with_stdout_data( str![[r#" { "readme": "SomeReadme.txt", "...": "{...}" } "#]] .is_json(), ) .run(); } #[cargo_test] fn cargo_read_manifest_default_readme() { let assert_output = |readme, expected| { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file(readme, "Sample project") .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest").with_stdout_data(expected).run(); }; assert_output( "README.md", str![[r#" { "readme": "README.md", "...": "{...}" } "#]] .is_json(), ); assert_output( "README.txt", str![[r#" { "readme": "README.txt", "...": "{...}" } "#]] .is_json(), ); assert_output( "README", str![[r#" { "readme": "README", "...": "{...}" } "#]] .is_json(), ); } #[cargo_test] fn cargo_read_manifest_suppress_default_readme() { let p = project() .file( "Cargo.toml", &basic_bin_manifest_with_readme("foo", "false"), ) .file("README.txt", "Sample project") .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest") .with_stdout_data( str![[r#" { "readme": null, "...": "{...}" } "#]] .is_json(), ) .run(); } // If a file named README.md exists, and `readme = true`, the value `README.md` should be defaulted in. #[cargo_test] fn cargo_read_manifest_defaults_readme_if_true() { let p = project() .file("Cargo.toml", &basic_bin_manifest_with_readme("foo", "true")) .file("README.md", "Sample project") .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("read-manifest") .with_stdout_data( str![[r#" { "readme": "README.md", "...": "{...}" } "#]] .is_json(), ) .run(); } ================================================ FILE: tests/testsuite/registry.rs ================================================ //! Tests for normal registry dependencies. use std::fmt::Write; use std::fs::{self, File}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::Mutex; use crate::prelude::*; use crate::utils::cargo_process; use cargo::core::SourceId; use cargo_test_support::assert_deterministic_mtime; use cargo_test_support::paths; use cargo_test_support::registry::{ self, Dependency, Package, RegistryBuilder, Response, TestRegistry, registry_path, }; use cargo_test_support::{basic_manifest, project, str}; use cargo_test_support::{git, t}; use cargo_util::paths::remove_dir_all; fn setup_http() -> TestRegistry { RegistryBuilder::new().http_index().build() } #[cargo_test] fn test_server_stops() { let server = setup_http(); server.join(); // ensure the server fully shuts down } #[cargo_test] fn simple_http() { let _server = setup_http(); simple( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn simple_git() { simple( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn simple(pre_clean_expected: impl IntoData, post_clean_expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check").with_stderr_data(pre_clean_expected).run(); p.cargo("clean").run(); assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file()); // Don't download a second time p.cargo("check").with_stderr_data(post_clean_expected).run(); } #[cargo_test] fn deps_http() { let _server = setup_http(); deps(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn deps_git() { deps(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn deps(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1").publish(); Package::new("bar", "0.0.1").dep("baz", "*").publish(); p.cargo("check").with_stderr_data(expected).run(); assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file()); } #[cargo_test] fn nonexistent_http() { let _server = setup_http(); nonexistent(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `nonexistent` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]]); } #[cargo_test] fn nonexistent_git() { nonexistent(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `nonexistent` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]]); } fn nonexistent(expected: impl IntoData) { Package::new("init", "0.0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] nonexistent = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check") .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn wrong_case_http() { let _server = setup_http(); wrong_case(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package found searched package name: `Init` perhaps you meant: init location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]]); } #[cargo_test] fn wrong_case_git() { wrong_case(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package found searched package name: `Init` perhaps you meant: init location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]]); } fn wrong_case(expected: impl IntoData) { Package::new("init", "0.0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] Init = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); // #5678 to make this work p.cargo("check") .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn mis_hyphenated_http() { let _server = setup_http(); mis_hyphenated(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package found searched package name: `mis_hyphenated` perhaps you meant: mis-hyphenated location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]]); } #[cargo_test] fn mis_hyphenated_git() { mis_hyphenated(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package found searched package name: `mis_hyphenated` perhaps you meant: mis-hyphenated location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]]); } fn mis_hyphenated(expected: impl IntoData) { Package::new("mis-hyphenated", "0.0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] mis_hyphenated = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); // #2775 to make this work p.cargo("check") .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn wrong_version_http() { let _server = setup_http(); wrong_version( str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `foo = ">=1.0.0"` candidate versions found which didn't match: 0.0.2, 0.0.1 location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` perhaps a crate was updated and forgotten to be re-vendored? "#]], str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `foo = ">=1.0.0"` candidate versions found which didn't match: 0.0.4, 0.0.3, 0.0.2, ... location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` perhaps a crate was updated and forgotten to be re-vendored? "#]], ); } #[cargo_test] fn wrong_version_git() { wrong_version( str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `foo = ">=1.0.0"` candidate versions found which didn't match: 0.0.2, 0.0.1 location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` perhaps a crate was updated and forgotten to be re-vendored? "#]], str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `foo = ">=1.0.0"` candidate versions found which didn't match: 0.0.4, 0.0.3, 0.0.2, ... location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` perhaps a crate was updated and forgotten to be re-vendored? "#]], ); } fn wrong_version(pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] foo = ">= 1.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("foo", "0.0.1").publish(); Package::new("foo", "0.0.2").publish(); p.cargo("check") .with_status(101) .with_stderr_data(pre_publish_expected) .run(); Package::new("foo", "0.0.3").publish(); Package::new("foo", "0.0.4").publish(); p.cargo("check") .with_status(101) .with_stderr_data(post_publish_expected) .run(); } #[cargo_test] fn bad_cksum_http() { let _server = setup_http(); bad_cksum(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bad-cksum v0.0.1 (registry `dummy-registry`) [ERROR] failed to download replaced source registry `crates-io` Caused by: failed to verify the checksum of `bad-cksum v0.0.1 (registry `dummy-registry`)` "#]]); } #[cargo_test] fn bad_cksum_git() { bad_cksum(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bad-cksum v0.0.1 (registry `dummy-registry`) [ERROR] failed to download replaced source registry `crates-io` Caused by: failed to verify the checksum of `bad-cksum v0.0.1 (registry `dummy-registry`)` "#]]); } fn bad_cksum(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bad-cksum = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); let pkg = Package::new("bad-cksum", "0.0.1"); pkg.publish(); t!(File::create(&pkg.archive_dst())); p.cargo("check -v") .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn update_registry_http() { let _server = setup_http(); update_registry( str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `notyet` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`) [CHECKING] notyet v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn update_registry_git() { update_registry( str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `notyet` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`) [CHECKING] notyet v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn update_registry(pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData) { Package::new("init", "0.0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] notyet = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check") .with_status(101) .with_stderr_data(pre_publish_expected) .run(); Package::new("notyet", "0.0.1").publish(); p.cargo("check") .with_stderr_data(post_publish_expected) .run(); } #[cargo_test] fn package_with_path_deps_http() { let _server = setup_http(); package_with_path_deps( str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] `dummy-registry` index [ERROR] failed to prepare local package for uploading Caused by: no matching package named `notyet` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] `dummy-registry` index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [DOWNLOADING] crates ... [DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`) [COMPILING] notyet v0.0.1 [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn package_with_path_deps_git() { package_with_path_deps( str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] `dummy-registry` index [ERROR] failed to prepare local package for uploading Caused by: no matching package named `notyet` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] `dummy-registry` index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [DOWNLOADING] crates ... [DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`) [COMPILING] notyet v0.0.1 [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn package_with_path_deps( pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData, ) { Package::new("init", "0.0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" repository = "bar" [dependencies.notyet] version = "0.0.1" path = "notyet" "#, ) .file("src/main.rs", "fn main() {}") .file("notyet/Cargo.toml", &basic_manifest("notyet", "0.0.1")) .file("notyet/src/lib.rs", "") .build(); p.cargo("package") .with_status(101) .with_stderr_data(pre_publish_expected) .run(); Package::new("notyet", "0.0.1").publish(); p.cargo("package") .with_stderr_data(post_publish_expected) .run(); } #[cargo_test] fn lockfile_locks_http() { let _server = setup_http(); lockfile_locks( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn lockfile_locks_git() { lockfile_locks( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn lockfile_locks(pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check") .with_stderr_data(pre_publish_expected) .run(); p.root().move_into_the_past(); Package::new("bar", "0.0.2").publish(); p.cargo("check") .with_stderr_data(post_publish_expected) .run(); } #[cargo_test] fn lockfile_locks_transitively_http() { let _server = setup_http(); lockfile_locks_transitively( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn lockfile_locks_transitively_git() { lockfile_locks_transitively( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn lockfile_locks_transitively( pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData, ) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1").publish(); Package::new("bar", "0.0.1").dep("baz", "*").publish(); p.cargo("check") .with_stderr_data(pre_publish_expected) .run(); p.root().move_into_the_past(); Package::new("baz", "0.0.2").publish(); Package::new("bar", "0.0.2").dep("baz", "*").publish(); p.cargo("check") .with_stderr_data(post_publish_expected) .run(); } #[cargo_test] fn yanks_are_not_used_http() { let _server = setup_http(); yanks_are_not_used(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn yanks_are_not_used_git() { yanks_are_not_used(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn yanks_are_not_used(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1").publish(); Package::new("baz", "0.0.2").yanked(true).publish(); Package::new("bar", "0.0.1").dep("baz", "*").publish(); Package::new("bar", "0.0.2") .dep("baz", "*") .yanked(true) .publish(); p.cargo("check").with_stderr_data(expected).run(); } #[cargo_test] fn relying_on_a_yank_is_bad_http() { let _server = setup_http(); relying_on_a_yank_is_bad(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `baz = "=0.0.2"` version 0.0.2 is yanked location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `bar v0.0.1` ... which satisfies dependency `bar = "*"` of package `foo v0.0.1 ([ROOT]/foo)` "#]]); } #[cargo_test] fn relying_on_a_yank_is_bad_git() { relying_on_a_yank_is_bad(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `baz = "=0.0.2"` version 0.0.2 is yanked location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `bar v0.0.1` ... which satisfies dependency `bar = "*"` of package `foo v0.0.1 ([ROOT]/foo)` "#]]); } fn relying_on_a_yank_is_bad(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1").publish(); Package::new("baz", "0.0.2").yanked(true).publish(); Package::new("bar", "0.0.1").dep("baz", "=0.0.2").publish(); p.cargo("check") .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn yanks_in_lockfiles_are_ok_http() { let _server = setup_http(); yanks_in_lockfiles_are_ok( str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `bar = "*"` version 0.0.1 is yanked location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], ); } #[cargo_test] fn yanks_in_lockfiles_are_ok_git() { yanks_in_lockfiles_are_ok( str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `bar = "*"` version 0.0.1 is yanked location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], ); } fn yanks_in_lockfiles_are_ok(expected_check: impl IntoData, expected_update: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check").run(); registry_path().join("3").rm_rf(); Package::new("bar", "0.0.1").yanked(true).publish(); p.cargo("check").with_stderr_data(expected_check).run(); p.cargo("update") .with_status(101) .with_stderr_data(expected_update) .run(); } #[cargo_test] fn yanks_in_lockfiles_are_ok_for_other_update_http() { let _server = setup_http(); yanks_in_lockfiles_are_ok_for_other_update( str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `bar = "*"` version 0.0.1 is yanked location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] baz v0.0.1 -> v0.0.2 "#]], ); } #[cargo_test] fn yanks_in_lockfiles_are_ok_for_other_update_git() { yanks_in_lockfiles_are_ok_for_other_update( str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `bar = "*"` version 0.0.1 is yanked location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] baz v0.0.1 -> v0.0.2 "#]], ); } fn yanks_in_lockfiles_are_ok_for_other_update( expected_check: impl IntoData, expected_update: impl IntoData, expected_other_update: impl IntoData, ) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" baz = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); Package::new("baz", "0.0.1").publish(); p.cargo("check").run(); registry_path().join("3").rm_rf(); Package::new("bar", "0.0.1").yanked(true).publish(); Package::new("baz", "0.0.1").publish(); p.cargo("check").with_stderr_data(expected_check).run(); Package::new("baz", "0.0.2").publish(); p.cargo("update") .with_status(101) .with_stderr_data(expected_update) .run(); p.cargo("update baz") .with_stderr_data(expected_other_update) .run(); } #[cargo_test] fn yanks_in_lockfiles_are_ok_with_new_dep_http() { let _server = setup_http(); yanks_in_lockfiles_are_ok_with_new_dep(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [ADDING] baz v0.0.1 [DOWNLOADING] crates ... [DOWNLOADED] baz v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn yanks_in_lockfiles_are_ok_with_new_dep_git() { yanks_in_lockfiles_are_ok_with_new_dep(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [ADDING] baz v0.0.1 [DOWNLOADING] crates ... [DOWNLOADED] baz v0.0.1 (registry `dummy-registry`) [CHECKING] baz v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn yanks_in_lockfiles_are_ok_with_new_dep(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check").run(); registry_path().join("3").rm_rf(); Package::new("bar", "0.0.1").yanked(true).publish(); Package::new("baz", "0.0.1").publish(); p.change_file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" baz = "*" "#, ); p.cargo("check").with_stderr_data(expected).run(); } #[cargo_test] fn update_with_lockfile_if_packages_missing_http() { let _server = setup_http(); update_with_lockfile_if_packages_missing(str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn update_with_lockfile_if_packages_missing_git() { update_with_lockfile_if_packages_missing(str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn update_with_lockfile_if_packages_missing(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check").run(); p.root().move_into_the_past(); paths::home().join(".cargo/registry").rm_rf(); p.cargo("check").with_stderr_data(expected).run(); } #[cargo_test] fn update_lockfile_http() { let _server = setup_http(); update_lockfile( str![[r#" [UPDATING] `dummy-registry` index [UPDATING] bar v0.0.1 -> v0.0.2 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.2 (registry `dummy-registry`) [CHECKING] bar v0.0.2 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v0.0.2 -> v0.0.3 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.3 (registry `dummy-registry`) [CHECKING] bar v0.0.3 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] bar v0.0.3 -> v0.0.4 [ADDING] spam v0.2.5 "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v0.0.4 -> v0.0.5 [REMOVING] spam v0.2.5 "#]], ); } #[cargo_test] fn update_lockfile_git() { update_lockfile( str![[r#" [UPDATING] `dummy-registry` index [UPDATING] bar v0.0.1 -> v0.0.2 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.2 (registry `dummy-registry`) [CHECKING] bar v0.0.2 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v0.0.2 -> v0.0.3 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.3 (registry `dummy-registry`) [CHECKING] bar v0.0.3 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] bar v0.0.3 -> v0.0.4 [ADDING] spam v0.2.5 "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v0.0.4 -> v0.0.5 [REMOVING] spam v0.2.5 "#]], ); } fn update_lockfile( expected_update: impl IntoData, expected_check: impl IntoData, expected_other_update: impl IntoData, expected_other_check: impl IntoData, expected_new_update: impl IntoData, expected_new_check: impl IntoData, ) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); println!("0.0.1"); Package::new("bar", "0.0.1").publish(); p.cargo("check").run(); Package::new("bar", "0.0.2").publish(); Package::new("bar", "0.0.3").publish(); paths::home().join(".cargo/registry").rm_rf(); println!("0.0.2 update"); p.cargo("update bar --precise 0.0.2") .with_stderr_data(expected_update) .run(); println!("0.0.2 build"); p.cargo("check").with_stderr_data(expected_check).run(); println!("0.0.3 update"); p.cargo("update bar") .with_stderr_data(expected_other_update) .run(); println!("0.0.3 build"); p.cargo("check") .with_stderr_data(expected_other_check) .run(); println!("new dependencies update"); Package::new("bar", "0.0.4").dep("spam", "0.2.5").publish(); Package::new("spam", "0.2.5").publish(); p.cargo("update bar") .with_stderr_data(expected_new_update) .run(); println!("new dependencies update"); Package::new("bar", "0.0.5").publish(); p.cargo("update bar") .with_stderr_data(expected_new_check) .run(); } #[cargo_test] fn dev_dependency_not_used_http() { let _server = setup_http(); dev_dependency_not_used(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn dev_dependency_not_used_git() { dev_dependency_not_used(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn dev_dependency_not_used(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1").publish(); Package::new("bar", "0.0.1").dev_dep("baz", "*").publish(); p.cargo("check").with_stderr_data(expected).run(); } #[cargo_test] fn bad_license_file_http() { let registry = setup_http(); bad_license_file( ®istry, str![[r#" ... [ERROR] license-file `foo` does not appear to exist (relative to `[ROOT]/foo`). ... "#]], ); } #[cargo_test] fn bad_license_file_git() { let registry = registry::init(); bad_license_file( ®istry, str![[r#" ... [ERROR] license-file `foo` does not appear to exist (relative to `[ROOT]/foo`). ... "#]], ); } fn bad_license_file(registry: &TestRegistry, expected: impl IntoData) { Package::new("foo", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license-file = "foo" description = "bar" repository = "baz" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("publish -v") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn updating_a_dep_http() { let _server = setup_http(); updating_a_dep( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] a v0.0.1 ([ROOT]/foo/a) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v0.0.1 -> v0.1.0 [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0 (registry `dummy-registry`) [CHECKING] bar v0.1.0 [CHECKING] a v0.0.1 ([ROOT]/foo/a) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn updating_a_dep_git() { updating_a_dep( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] a v0.0.1 ([ROOT]/foo/a) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v0.0.1 -> v0.1.0 [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0 (registry `dummy-registry`) [CHECKING] bar v0.1.0 [CHECKING] a v0.0.1 ([ROOT]/foo/a) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn updating_a_dep(pre_update_expected: impl IntoData, post_update_expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.a] path = "a" "#, ) .file("src/main.rs", "fn main() {}") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("a/src/lib.rs", "") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check").with_stderr_data(pre_update_expected).run(); assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file()); // Now delete the CACHEDIR.TAG file: this is the situation we'll be in after // upgrading from a version of Cargo that doesn't mark this directory, to one that // does. It should be recreated. fs::remove_file(paths::home().join(".cargo/registry/CACHEDIR.TAG")) .expect("remove CACHEDIR.TAG"); p.change_file( "a/Cargo.toml", r#" [package] name = "a" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" "#, ); Package::new("bar", "0.1.0").publish(); println!("second"); p.cargo("check") .with_stderr_data(post_update_expected) .run(); assert!( paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file(), "CACHEDIR.TAG recreated in existing registry" ); } #[cargo_test] fn git_and_registry_dep_http() { let _server = setup_http(); git_and_registry_dep( str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/b` [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] a v0.0.1 (registry `dummy-registry`) [CHECKING] a v0.0.1 [CHECKING] b v0.0.1 ([ROOTURL]/b#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn git_and_registry_dep_git() { git_and_registry_dep( str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/b` [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] a v0.0.1 (registry `dummy-registry`) [CHECKING] a v0.0.1 [CHECKING] b v0.0.1 ([ROOTURL]/b#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn git_and_registry_dep(pre_move_expected: impl IntoData, post_move_expected: impl IntoData) { let b = git::repo(&paths::root().join("b")) .file( "Cargo.toml", r#" [package] name = "b" version = "0.0.1" edition = "2015" authors = [] [dependencies] a = "0.0.1" "#, ) .file("src/lib.rs", "") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] a = "0.0.1" [dependencies.b] git = '{}' "#, b.url() ), ) .file("src/main.rs", "fn main() {}") .build(); Package::new("a", "0.0.1").publish(); p.root().move_into_the_past(); p.cargo("check").with_stderr_data(pre_move_expected).run(); p.root().move_into_the_past(); println!("second"); p.cargo("check").with_stderr_data(post_move_expected).run(); } #[cargo_test] fn update_publish_then_update_http() { let _server = setup_http(); update_publish_then_update(str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] a v0.1.1 (registry `dummy-registry`) [COMPILING] a v0.1.1 [COMPILING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn update_publish_then_update_git() { update_publish_then_update(str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] a v0.1.1 (registry `dummy-registry`) [COMPILING] a v0.1.1 [COMPILING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn update_publish_then_update(expected: impl IntoData) { // First generate a Cargo.lock and a clone of the registry index at the // "head" of the current registry. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] a = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("a", "0.1.0").publish(); p.cargo("build").run(); // Next, publish a new package and back up the copy of the registry we just // created. Package::new("a", "0.1.1").publish(); let registry = paths::home().join(".cargo/registry"); let backup = paths::root().join("registry-backup"); t!(fs::rename(®istry, &backup)); // Generate a Cargo.lock with the newer version, and then move the old copy // of the registry back into place. let p2 = project() .at("foo2") .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] a = "0.1.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); p2.cargo("build").run(); registry.rm_rf(); t!(fs::rename(&backup, ®istry)); t!(fs::rename( p2.root().join("Cargo.lock"), p.root().join("Cargo.lock") )); // Finally, build the first project again (with our newer Cargo.lock) which // should force an update of the old registry, download the new crate, and // then build everything again. p.cargo("build").with_stderr_data(expected).run(); } #[cargo_test] fn fetch_downloads_http() { let _server = setup_http(); fetch_downloads(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] a v0.1.0 (registry `dummy-registry`) "#]]); } #[cargo_test] fn fetch_downloads_git() { fetch_downloads(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] a v0.1.0 (registry `dummy-registry`) "#]]); } fn fetch_downloads(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] a = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("a", "0.1.0").publish(); p.cargo("fetch").with_stderr_data(expected).run(); } #[cargo_test] fn update_transitive_dependency_http() { let _server = setup_http(); update_transitive_dependency( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] b v0.1.0 -> v0.1.1 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] b v0.1.1 (registry `dummy-registry`) [CHECKING] b v0.1.1 [CHECKING] a v0.1.0 [CHECKING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn update_transitive_dependency_git() { update_transitive_dependency( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] b v0.1.0 -> v0.1.1 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] b v0.1.1 (registry `dummy-registry`) [CHECKING] b v0.1.1 [CHECKING] a v0.1.0 [CHECKING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn update_transitive_dependency(expected_update: impl IntoData, expected_check: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] a = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("a", "0.1.0").dep("b", "*").publish(); Package::new("b", "0.1.0").publish(); p.cargo("fetch").run(); Package::new("b", "0.1.1").publish(); p.cargo("update b").with_stderr_data(expected_update).run(); p.cargo("check").with_stderr_data(expected_check).run(); } #[cargo_test] fn update_backtracking_ok_http() { let _server = setup_http(); update_backtracking_ok(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] hyper v0.6.5 -> v0.6.6 [UPDATING] openssl v0.1.0 -> v0.1.1 "#]]); } #[cargo_test] fn update_backtracking_ok_git() { update_backtracking_ok(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] hyper v0.6.5 -> v0.6.6 [UPDATING] openssl v0.1.0 -> v0.1.1 "#]]); } fn update_backtracking_ok(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] webdriver = "0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("webdriver", "0.1.0") .dep("hyper", "0.6") .publish(); Package::new("hyper", "0.6.5") .dep("openssl", "0.1") .dep("cookie", "0.1") .publish(); Package::new("cookie", "0.1.0") .dep("openssl", "0.1") .publish(); Package::new("openssl", "0.1.0").publish(); p.cargo("generate-lockfile").run(); Package::new("openssl", "0.1.1").publish(); Package::new("hyper", "0.6.6") .dep("openssl", "0.1.1") .dep("cookie", "0.1.0") .publish(); p.cargo("update hyper").with_stderr_data(expected).run(); } #[cargo_test] fn update_multiple_packages_http() { let _server = setup_http(); update_multiple_packages( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] a v0.1.0 -> v0.1.1 [UPDATING] b v0.1.0 -> v0.1.1 [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] c v0.1.0 -> v0.1.1 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] a v0.1.1 (registry `dummy-registry`) [DOWNLOADED] b v0.1.1 (registry `dummy-registry`) [DOWNLOADED] c v0.1.1 (registry `dummy-registry`) [CHECKING] a v0.1.1 [CHECKING] c v0.1.1 [CHECKING] b v0.1.1 [CHECKING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } #[cargo_test] fn update_multiple_packages_git() { update_multiple_packages( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] a v0.1.0 -> v0.1.1 [UPDATING] b v0.1.0 -> v0.1.1 [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest "#]], str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] c v0.1.0 -> v0.1.1 "#]], str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] c v0.1.1 (registry `dummy-registry`) [DOWNLOADED] b v0.1.1 (registry `dummy-registry`) [DOWNLOADED] a v0.1.1 (registry `dummy-registry`) [CHECKING] b v0.1.1 [CHECKING] a v0.1.1 [CHECKING] c v0.1.1 [CHECKING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]], ); } fn update_multiple_packages( expected_update: impl IntoData, expected_other_update: impl IntoData, expected_check: impl IntoData, ) { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] a = "*" b = "*" c = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("a", "0.1.0").publish(); Package::new("b", "0.1.0").publish(); Package::new("c", "0.1.0").publish(); p.cargo("fetch").run(); Package::new("a", "0.1.1").publish(); Package::new("b", "0.1.1").publish(); Package::new("c", "0.1.1").publish(); p.cargo("update a b") .with_stderr_data(expected_update) .run(); p.cargo("update b c") .with_stderr_data(expected_other_update) .run(); p.cargo("check") .with_stderr_data(IntoData::unordered(expected_check)) .run(); } #[cargo_test] fn bundled_crate_in_registry_http() { let _server = setup_http(); bundled_crate_in_registry(); } #[cargo_test] fn bundled_crate_in_registry_git() { bundled_crate_in_registry(); } fn bundled_crate_in_registry() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] bar = "0.1" baz = "0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.1.0").publish(); Package::new("baz", "0.1.0") .dep("bar", "0.1.0") .file( "Cargo.toml", r#" [package] name = "baz" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "bar", version = "0.1.0" } "#, ) .file("src/lib.rs", "") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "") .publish(); p.cargo("run").run(); } #[cargo_test] fn update_same_prefix_oh_my_how_was_this_a_bug_http() { let _server = setup_http(); update_same_prefix_oh_my_how_was_this_a_bug(); } #[cargo_test] fn update_same_prefix_oh_my_how_was_this_a_bug_git() { update_same_prefix_oh_my_how_was_this_a_bug(); } fn update_same_prefix_oh_my_how_was_this_a_bug() { let p = project() .file( "Cargo.toml", r#" [package] name = "ugh" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("foobar", "0.2.0").publish(); Package::new("foo", "0.1.0") .dep("foobar", "0.2.0") .publish(); p.cargo("generate-lockfile").run(); p.cargo("update foobar --precise=0.2.0").run(); } #[cargo_test] fn use_semver_http() { let _server = setup_http(); use_semver(); } #[cargo_test] fn use_semver_git() { use_semver(); } fn use_semver() { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "1.2.3-alpha.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("foo", "1.2.3-alpha.0").publish(); p.cargo("check").run(); } #[cargo_test] fn use_semver_package_incorrectly_http() { let _server = setup_http(); use_semver_package_incorrectly(str![[r#" [ERROR] failed to select a version for the requirement `a = "^0.1"` candidate versions found which didn't match: 0.1.1-alpha.0 location searched: [ROOT]/foo/a required by package `b v0.1.0 ([ROOT]/foo/b)` if you are looking for the prerelease package it needs to be specified explicitly a = { version = "0.1.1-alpha.0" } "#]]); } #[cargo_test] fn use_semver_package_incorrectly_git() { use_semver_package_incorrectly(str![[r#" [ERROR] failed to select a version for the requirement `a = "^0.1"` candidate versions found which didn't match: 0.1.1-alpha.0 location searched: [ROOT]/foo/a required by package `b v0.1.0 ([ROOT]/foo/b)` if you are looking for the prerelease package it needs to be specified explicitly a = { version = "0.1.1-alpha.0" } "#]]); } fn use_semver_package_incorrectly(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.1.1-alpha.0" edition = "2015" authors = [] "#, ) .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.1.0" edition = "2015" authors = [] [dependencies] a = { version = "^0.1", path = "../a" } "#, ) .file("a/src/main.rs", "fn main() {}") .file("b/src/main.rs", "fn main() {}") .build(); p.cargo("check") .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn only_download_relevant_http() { let _server = setup_http(); only_download_relevant(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 3 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] baz v0.1.0 (registry `dummy-registry`) [CHECKING] baz v0.1.0 [CHECKING] bar v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn only_download_relevant_git() { only_download_relevant(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 3 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] baz v0.1.0 (registry `dummy-registry`) [CHECKING] baz v0.1.0 [CHECKING] bar v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn only_download_relevant(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [target.foo.dependencies] foo = "*" [dev-dependencies] bar = "*" [dependencies] baz = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("foo", "0.1.0").publish(); Package::new("bar", "0.1.0").publish(); Package::new("baz", "0.1.0").publish(); p.cargo("check").with_stderr_data(expected).run(); } #[cargo_test] fn resolve_and_backtracking_http() { let _server = setup_http(); resolve_and_backtracking(); } #[cargo_test] fn resolve_and_backtracking_git() { resolve_and_backtracking(); } fn resolve_and_backtracking() { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("foo", "0.1.1") .feature_dep("bar", "0.1", &["a", "b"]) .publish(); Package::new("foo", "0.1.0").publish(); p.cargo("check").run(); } #[cargo_test] fn upstream_warnings_on_extra_verbose_http() { let _server = setup_http(); upstream_warnings_on_extra_verbose(str![[r#" ... [WARNING] function `unused` is never used ... "#]]); } #[cargo_test] fn upstream_warnings_on_extra_verbose_git() { upstream_warnings_on_extra_verbose(str![[r#" ... [WARNING] function `unused` is never used ... "#]]); } fn upstream_warnings_on_extra_verbose(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("foo", "0.1.0") .file("src/lib.rs", "fn unused() {}") .publish(); p.cargo("check -vv").with_stderr_data(expected).run(); } #[cargo_test] fn disallow_network_http() { let _server = setup_http(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check --frozen") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no matching package named `foo` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `bar v0.5.0 ([ROOT]/foo)` As a reminder, you're using offline mode (--frozen) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--frozen`. "#]]) .run(); } #[cargo_test] fn disallow_network_git() { let _server = RegistryBuilder::new().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check --frozen") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no matching package named `foo` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `bar v0.5.0 ([ROOT]/foo)` As a reminder, you're using offline mode (--frozen) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--frozen`. "#]]) .run(); } #[cargo_test] fn add_dep_dont_update_registry_http() { let _server = setup_http(); add_dep_dont_update_registry(str![[r#" [CHECKING] bar v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn add_dep_dont_update_registry_git() { add_dep_dont_update_registry(str![[r#" [CHECKING] bar v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn add_dep_dont_update_registry(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] baz = { path = "baz" } "#, ) .file("src/main.rs", "fn main() {}") .file( "baz/Cargo.toml", r#" [package] name = "baz" version = "0.5.0" edition = "2015" authors = [] [dependencies] remote = "0.3" "#, ) .file("baz/src/lib.rs", "") .build(); Package::new("remote", "0.3.4").publish(); p.cargo("check").run(); p.change_file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] baz = { path = "baz" } remote = "0.3" "#, ); p.cargo("check").with_stderr_data(expected).run(); } #[cargo_test] fn bump_version_dont_update_registry_http() { let _server = setup_http(); bump_version_dont_update_registry(str![[r#" [CHECKING] bar v0.6.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } #[cargo_test] fn bump_version_dont_update_registry_git() { bump_version_dont_update_registry(str![[r#" [CHECKING] bar v0.6.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]); } fn bump_version_dont_update_registry(expected: impl IntoData) { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] baz = { path = "baz" } "#, ) .file("src/main.rs", "fn main() {}") .file( "baz/Cargo.toml", r#" [package] name = "baz" version = "0.5.0" edition = "2015" authors = [] [dependencies] remote = "0.3" "#, ) .file("baz/src/lib.rs", "") .build(); Package::new("remote", "0.3.4").publish(); p.cargo("check").run(); p.change_file( "Cargo.toml", r#" [package] name = "bar" version = "0.6.0" edition = "2015" authors = [] [dependencies] baz = { path = "baz" } "#, ); p.cargo("check").with_stderr_data(expected).run(); } #[cargo_test] fn toml_lies_but_index_is_truth_http() { let _server = setup_http(); toml_lies_but_index_is_truth(); } #[cargo_test] fn toml_lies_but_index_is_truth_git() { toml_lies_but_index_is_truth(); } fn toml_lies_but_index_is_truth() { Package::new("foo", "0.2.0").publish(); Package::new("bar", "0.3.0") .dep("foo", "0.2.0") .file( "Cargo.toml", r#" [package] name = "bar" version = "0.3.0" edition = "2015" authors = [] [dependencies] foo = "0.1.0" "#, ) .file("src/lib.rs", "extern crate foo;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = [] [dependencies] bar = "0.3" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -v").run(); } #[cargo_test] fn vv_prints_warnings_http() { let _server = setup_http(); vv_prints_warnings(); } #[cargo_test] fn vv_prints_warnings_git() { vv_prints_warnings(); } fn vv_prints_warnings() { Package::new("foo", "0.2.0") .file( "src/lib.rs", "#![deny(warnings)] fn foo() {} // unused function", ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "fo" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "0.2" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -vv").run(); } #[cargo_test] fn bad_and_or_malicious_packages_rejected_http() { let _server = setup_http(); bad_and_or_malicious_packages_rejected(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] foo v0.2.0 (registry `dummy-registry`) [ERROR] failed to download replaced source registry `crates-io` Caused by: failed to unpack package `foo v0.2.0 (registry `dummy-registry`)` Caused by: invalid tarball downloaded, contains a file at "foo-0.1.0/src/lib.rs" which isn't under "foo-0.2.0" "#]]); } #[cargo_test] fn bad_and_or_malicious_packages_rejected_git() { bad_and_or_malicious_packages_rejected(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] foo v0.2.0 (registry `dummy-registry`) [ERROR] failed to download replaced source registry `crates-io` Caused by: failed to unpack package `foo v0.2.0 (registry `dummy-registry`)` Caused by: invalid tarball downloaded, contains a file at "foo-0.1.0/src/lib.rs" which isn't under "foo-0.2.0" "#]]); } fn bad_and_or_malicious_packages_rejected(expected: impl IntoData) { Package::new("foo", "0.2.0") .extra_file("foo-0.1.0/src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "fo" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "0.2" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -vv") .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test] fn git_init_templatedir_missing_http() { let _server = setup_http(); git_init_templatedir_missing(); } #[cargo_test] fn git_init_templatedir_missing_git() { git_init_templatedir_missing(); } fn git_init_templatedir_missing() { Package::new("foo", "0.2.0").dep("bar", "*").publish(); Package::new("bar", "0.2.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "fo" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "0.2" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check").run(); remove_dir_all(paths::home().join(".cargo/registry")).unwrap(); fs::write( paths::home().join(".gitconfig"), r#" [init] templatedir = nowhere "#, ) .unwrap(); p.cargo("check").run(); p.cargo("check").run(); } #[cargo_test] fn rename_deps_and_features_http() { let _server = setup_http(); rename_deps_and_features(); } #[cargo_test] fn rename_deps_and_features_git() { rename_deps_and_features(); } fn rename_deps_and_features() { Package::new("foo", "0.1.0") .file("src/lib.rs", "pub fn f1() {}") .publish(); Package::new("foo", "0.2.0") .file("src/lib.rs", "pub fn f2() {}") .publish(); Package::new("bar", "0.2.0") .add_dep( Dependency::new("foo01", "0.1.0") .package("foo") .optional(true), ) .add_dep(Dependency::new("foo02", "0.2.0").package("foo")) .feature("another", &["foo01"]) .file( "src/lib.rs", r#" extern crate foo02; #[cfg(feature = "foo01")] extern crate foo01; pub fn foo() { foo02::f2(); #[cfg(feature = "foo01")] foo01::f1(); } "#, ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.5.0" edition = "2015" authors = [] [dependencies] bar = "0.2" "#, ) .file( "src/main.rs", " extern crate bar; fn main() { bar::foo(); } ", ) .build(); p.cargo("check").run(); p.cargo("check --features bar/foo01").run(); p.cargo("check --features bar/another").run(); } #[cargo_test] fn ignore_invalid_json_lines_http() { let _server = setup_http(); ignore_invalid_json_lines(); } #[cargo_test] fn ignore_invalid_json_lines_git() { ignore_invalid_json_lines(); } fn ignore_invalid_json_lines() { Package::new("foo", "0.1.0").publish(); Package::new("foo", "0.1.1") .invalid_index_line(true) .publish(); Package::new("foo", "0.2.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = '0.1.0' foo02 = { version = '0.2.0', package = 'foo' } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); } #[cargo_test] fn invalid_json_lines_error() { Package::new("foo", "0.1.0") .rust_version("1.0") .schema_version(2) .publish(); Package::new("foo", "0.1.1") // Bad name field, too corrupt to use .invalid_index_line(true) .publish(); Package::new("foo", "0.1.2") // Bad version field, too corrupt to use .index_line( r#"{"cksum":"7ca5fc2301ad96ade45356faf53225aea36437d99930bbfa951155c01faecf79","deps":[],"features":{},"links":null,"name":"foo","vers":"bad","yanked":false,"rust_version":"1.2345","v":1000000000}"#, ) .publish(); Package::new("foo", "0.1.3") // Bad field, report rust version .index_line( r#"{"cksum":"7ca5fc2301ad96ade45356faf53225aea36437d99930bbfa951155c01faecf79","deps":[],"features":"bad","links":null,"name":"foo","vers":"0.1.3","yanked":false,"rust_version":"1.2345","v":1000000000}"#, ) .publish(); Package::new("foo", "0.1.4") // Bad field, report schema .index_line( r#"{"cksum":"7ca5fc2301ad96ade45356faf53225aea36437d99930bbfa951155c01faecf79","deps":[],"features":"bad","links":null,"name":"foo","vers":"0.1.4","yanked":false,"v":1000000000}"#, ) .publish(); Package::new("foo", "0.1.5") // Bad field, report error .index_line( r#"{"cksum":"7ca5fc2301ad96ade45356faf53225aea36437d99930bbfa951155c01faecf79","deps":[],"features":"bad","links":null,"name":"foo","vers":"0.1.5","yanked":false}"#, ) .publish(); Package::new("foo", "0.1.6") // Bad field with bad rust version, report schema .index_line( r#"{"cksum":"7ca5fc2301ad96ade45356faf53225aea36437d99930bbfa951155c01faecf79","deps":[],"features":"bad","links":null,"name":"foo","vers":"0.1.6","yanked":false,"rust_version":"bad","v":1000000000}"#, ) .publish(); Package::new("foo", "0.1.7") // Bad field with bad rust version and schema, report error .index_line( r#"{"cksum":"7ca5fc2301ad96ade45356faf53225aea36437d99930bbfa951155c01faecf79","deps":[],"features":"bad","links":null,"name":"foo","vers":"0.1.7","yanked":false,"rust_version":"bad","v":"bad"}"#, ) .publish(); Package::new("foo", "0.2.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = "0.1.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `foo = "^0.1.1"` version 0.1.3 requires cargo 1.2345 version 0.1.4 requires a Cargo version that supports index version 1000000000 version 0.1.5's index entry is invalid version 0.1.6 requires a Cargo version that supports index version 1000000000 version 0.1.7's index entry is invalid location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `a v0.5.0 ([ROOT]/foo)` "#]]) .run(); p.cargo("generate-lockfile") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `foo = "^0.1.1"` version 0.1.3 requires cargo 1.2345 version 0.1.4 requires a Cargo version that supports index version 1000000000 version 0.1.5's index entry is invalid version 0.1.6 requires a Cargo version that supports index version 1000000000 version 0.1.7's index entry is invalid location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `a v0.5.0 ([ROOT]/foo)` "#]]) .run(); } #[cargo_test] fn readonly_registry_still_works_http() { let _server = setup_http(); readonly_registry_still_works(); } #[cargo_test] fn readonly_registry_still_works_git() { readonly_registry_still_works(); } fn readonly_registry_still_works() { Package::new("foo", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = '0.1.0' "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); p.cargo("fetch --locked").run(); chmod_readonly(&paths::home(), true); p.cargo("check").run(); // make sure we un-readonly the files afterwards so "cargo clean" can remove them (#6934) chmod_readonly(&paths::home(), false); fn chmod_readonly(path: &Path, readonly: bool) { for entry in t!(path.read_dir()) { let entry = t!(entry); let path = entry.path(); if t!(entry.file_type()).is_dir() { chmod_readonly(&path, readonly); } else { set_readonly(&path, readonly); } } set_readonly(path, readonly); } fn set_readonly(path: &Path, readonly: bool) { let mut perms = t!(path.metadata()).permissions(); perms.set_readonly(readonly); t!(fs::set_permissions(path, perms)); } } #[cargo_test(ignore_windows = "On Windows setting file attributes is a bit complicated")] fn inaccessible_registry_cache_still_works() { Package::new("foo", "0.1.0").publish(); Package::new("fo2", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = '0.1.0' fo2 = '0.1.0' "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); p.cargo("fetch --locked").run(); let cache_path = inner_dir(&paths::cargo_home().join("registry/index")).join(".cache"); let f_cache_path = cache_path.join("3/f"); // Remove the permissions from the cache path that contains the "foo" crate set_permissions(&f_cache_path, 0o000); // Now run a build and make sure we properly build and warn the user p.cargo("build") .with_stderr_data(str![[r#" [WARNING] failed to write cache, path: [ROOT]/home/.cargo/registry/index/-[HASH]/.cache/3/f/fo[..], [ERROR] Permission denied (os error 13) [COMPILING] fo[..] v0.1.0 [COMPILING] fo[..] v0.1.0 [COMPILING] a v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); // make sure we add the permissions to the files afterwards so "cargo clean" can remove them (#6934) set_permissions(&f_cache_path, 0o777); #[cfg_attr(windows, allow(unused_variables))] fn set_permissions(path: &Path, permissions: u32) { #[cfg(not(windows))] { use std::os::unix::fs::PermissionsExt; let mut perms = t!(path.metadata()).permissions(); perms.set_mode(permissions); t!(fs::set_permissions(path, perms)); } #[cfg(windows)] panic!("This test is not supported on windows. See the reason in the #[cargo_test] macro"); } fn inner_dir(path: &Path) -> PathBuf { for entry in t!(path.read_dir()) { let path = t!(entry).path(); if path.is_dir() { return path; } } panic!("could not find inner directory of {path:?}"); } } #[cargo_test] fn registry_index_rejected_http() { let _server = setup_http(); registry_index_rejected( str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: the `registry.index` config value is no longer supported Use `[source]` replacement to alter the default index for crates.io. "#]], str![[r#" [ERROR] the `registry.index` config value is no longer supported Use `[source]` replacement to alter the default index for crates.io. "#]], ); } #[cargo_test] fn registry_index_rejected_git() { registry_index_rejected( str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: the `registry.index` config value is no longer supported Use `[source]` replacement to alter the default index for crates.io. "#]], str![[r#" [ERROR] the `registry.index` config value is no longer supported Use `[source]` replacement to alter the default index for crates.io. "#]], ); } fn registry_index_rejected(expected_check: impl IntoData, expected_login: impl IntoData) { Package::new("dep", "0.1.0").publish(); let p = project() .file( ".cargo/config.toml", r#" [registry] index = "https://example.com/" "#, ) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] dep = "0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(expected_check) .run(); p.cargo("login") .with_status(101) .with_stderr_data(expected_login) .run(); } #[cargo_test] fn package_lock_inside_package_is_overwritten() { let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1") .file("src/lib.rs", "") .file(".cargo-ok", "") .publish(); p.cargo("check").run(); let id = SourceId::for_registry(registry.index_url()).unwrap(); let hash = cargo::util::hex::short_hash(&id); let ok = paths::cargo_home() .join("registry") .join("src") .join(format!("-{}", hash)) .join("bar-0.0.1") .join(".cargo-ok"); assert_eq!(ok.metadata().unwrap().len(), 7); } #[cargo_test] fn package_lock_as_a_symlink_inside_package_is_overwritten() { let registry = registry::init(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1") .file("src/lib.rs", "pub fn f() {}") .symlink(".cargo-ok", "src/lib.rs") .publish(); p.cargo("check").run(); let id = SourceId::for_registry(registry.index_url()).unwrap(); let hash = cargo::util::hex::short_hash(&id); let pkg_root = paths::cargo_home() .join("registry") .join("src") .join(format!("-{}", hash)) .join("bar-0.0.1"); let ok = pkg_root.join(".cargo-ok"); let librs = pkg_root.join("src/lib.rs"); // Is correctly overwritten and doesn't affect the file linked to assert_eq!(ok.metadata().unwrap().len(), 7); assert_eq!(fs::read_to_string(librs).unwrap(), "pub fn f() {}"); } #[cargo_test] fn ignores_unknown_index_version_http() { let _server = setup_http(); ignores_unknown_index_version(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar v1.0.0 "#]]); } #[cargo_test] fn ignores_unknown_index_version_git() { ignores_unknown_index_version(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar v1.0.0 "#]]); } fn ignores_unknown_index_version(expected: impl IntoData) { // If the version field is not understood, it is ignored. Package::new("bar", "1.0.0").publish(); Package::new("bar", "1.0.1") .schema_version(u32::MAX) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("tree").with_stdout_data(expected).run(); } #[cargo_test] fn unknown_index_version_error() { Package::new("bar", "0.0.1").publish(); // If the version field is not understood, it is ignored. Package::new("bar", "1.0.1") .schema_version(u32::MAX) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `bar = "^1.0"` version 1.0.1 requires a Cargo version that supports index version 4294967295 location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.1.0 ([ROOT]/foo)` "#]]) .run(); p.cargo("generate-lockfile") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `bar = "^1.0"` version 1.0.1 requires a Cargo version that supports index version 4294967295 location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.1.0 ([ROOT]/foo)` "#]]) .run(); } #[cargo_test] fn unknown_index_version_with_msrv_error() { Package::new("bar", "0.0.1").publish(); // If the version field is not understood, it is ignored. Package::new("bar", "1.0.1") .schema_version(u32::MAX) .rust_version("1.2345") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `bar = "^1.0"` version 1.0.1 requires cargo 1.2345 location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.1.0 ([ROOT]/foo)` "#]]) .run(); } #[cargo_test] fn protocol() { cargo_process("install bar") .with_status(101) .env("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "invalid") .with_stderr_data(str![[r#" [ERROR] unsupported registry protocol `invalid` (defined in environment variable `CARGO_REGISTRIES_CRATES_IO_PROTOCOL`) "#]]) .run(); } #[cargo_test] fn http_requires_trailing_slash() { cargo_process("install bar --index sparse+https://invalid.crates.io/test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] sparse registry url must end in a slash `/`: sparse+https://invalid.crates.io/test "#]]) .run(); } // Limit the test to debug builds so that `__CARGO_TEST_MAX_UNPACK_SIZE` will take affect. #[cfg(debug_assertions)] #[cargo_test] fn reach_max_unpack_size() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); // Size of bar.crate is around 180 bytes. Package::new("bar", "0.0.1").publish(); p.cargo("check") .env("__CARGO_TEST_MAX_UNPACK_SIZE", "8") // hit 8 bytes limit and boom! .env("__CARGO_TEST_MAX_UNPACK_RATIO", "0") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [ERROR] failed to download replaced source registry `crates-io` Caused by: failed to unpack package `bar v0.0.1 (registry `dummy-registry`)` Caused by: failed to iterate over archive Caused by: maximum limit reached when reading "#]]) .run(); // Restore to the default ratio and it should compile. p.cargo("check") .env("__CARGO_TEST_MAX_UNPACK_SIZE", "8") .with_stderr_data(str![[r#" [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn sparse_blocking_count() { let fail_count = Mutex::new(0); let _registry = RegistryBuilder::new() .http_index() .add_responder("/index/3/b/bar", move |req, server| { let mut fail_count = fail_count.lock().unwrap(); if *fail_count < 1 { *fail_count += 1; server.internal_server_error(req) } else { server.index(req) } }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); // Ensure we have the expected number of `block_until_ready` calls. // The 1st (0 transfers pending), is the deliberate extra call in `ensure_loaded` for a source. // The 2nd (1 transfers pending), is the registry `config.json`. // the 3rd (1 transfers pending), is the package metadata for `bar`. p.cargo("check") .env("CARGO_LOG", "network::HttpRegistry::block_until_ready=trace") .with_stderr_data(str![[r#" [..] TRACE network::HttpRegistry::block_until_ready: 0 transfers pending [UPDATING] `dummy-registry` index [..] TRACE network::HttpRegistry::block_until_ready: 1 transfers pending [..] TRACE network::HttpRegistry::block_until_ready: 1 transfers pending [WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `[..]/index/3/b/bar` ([..]), got 500 body: internal server error [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]).run(); } #[cargo_test] fn sparse_retry_single() { let fail_count = Mutex::new(0); let _registry = RegistryBuilder::new() .http_index() .add_responder("/index/3/b/bar", move |req, server| { let mut fail_count = fail_count.lock().unwrap(); if *fail_count < 2 { *fail_count += 1; server.internal_server_error(req) } else { server.index(req) } }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check").with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 500 body: internal server error [WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 500 body: internal server error [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]).run(); } #[cargo_test] fn sparse_retry_multiple() { // Tests retry behavior of downloading lots of packages with various // failure rates accessing the sparse index. // The index is the number of retries, the value is the number of packages // that retry that number of times. Thus 50 packages succeed on first try, // 25 on second, etc. const RETRIES: &[u32] = &[50, 25, 12, 6]; let pkgs: Vec<_> = RETRIES .iter() .enumerate() .flat_map(|(retries, num)| { (0..*num) .into_iter() .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix()))) }) .collect(); let mut builder = RegistryBuilder::new().http_index(); let fail_counts: Arc>> = Arc::new(Mutex::new(vec![0; pkgs.len()])); let mut cargo_toml = r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] "# .to_string(); // The expected stderr output. let mut expected = "\ [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... " .to_string(); for (n, (retries, name)) in pkgs.iter().enumerate() { let count_clone = fail_counts.clone(); let retries = *retries; let ab = &name[..2]; let cd = &name[2..4]; builder = builder.add_responder(format!("/index/{ab}/{cd}/{name}"), move |req, server| { let mut fail_counts = count_clone.lock().unwrap(); if fail_counts[n] < retries { fail_counts[n] += 1; server.internal_server_error(req) } else { server.index(req) } }); write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap(); for retry in 0..retries { let remain = 3 - retry; write!( &mut expected, "[WARNING] spurious network error ({remain} {} remaining): \ failed to get successful HTTP response from \ `http://127.0.0.1:[..]/{ab}/{cd}/{name}` (127.0.0.1), got 500\n\ body:\n\ internal server error\n", if remain != 1 { "tries" } else { "try" } ) .unwrap(); } write!( &mut expected, "\ [DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`) " ) .unwrap(); } write!( &mut expected, "\ [LOCKING] 93 packages to latest compatible versions " ) .unwrap(); let _server = builder.build(); for (_, name) in &pkgs { Package::new(name, "1.0.0").publish(); } let p = project() .file("Cargo.toml", &cargo_toml) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_stderr_data(IntoData::unordered(expected)) .run(); } #[cargo_test] fn dl_retry_single() { // Tests retry behavior of downloading a package. // This tests a single package which exercises the code path that causes // it to block. let fail_count = Mutex::new(0); let _server = RegistryBuilder::new() .http_index() .add_responder("/dl/bar/1.0.0/download", move |req, server| { let mut fail_count = fail_count.lock().unwrap(); if *fail_count < 2 { *fail_count += 1; server.internal_server_error(req) } else { server.dl(req) } }) .build(); Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch").with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500 body: internal server error [WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500 body: internal server error [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) "#]]).run(); } /// Creates a random prefix to randomly spread out the package names /// to somewhat evenly distribute the different failures at different /// points. fn rand_prefix() -> String { use rand::RngExt; const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; let mut rng = rand::rng(); (0..5) .map(|_| CHARS[rng.random_range(0..CHARS.len())] as char) .collect() } #[cargo_test] fn dl_retry_multiple() { // Tests retry behavior of downloading lots of packages with various // failure rates. // The index is the number of retries, the value is the number of packages // that retry that number of times. Thus 50 packages succeed on first try, // 25 on second, etc. const RETRIES: &[u32] = &[50, 25, 12, 6]; let pkgs: Vec<_> = RETRIES .iter() .enumerate() .flat_map(|(retries, num)| { (0..*num) .into_iter() .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix()))) }) .collect(); let mut builder = RegistryBuilder::new().http_index(); let fail_counts: Arc>> = Arc::new(Mutex::new(vec![0; pkgs.len()])); let mut cargo_toml = r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] "# .to_string(); // The expected stderr output. let mut expected = "\ [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... " .to_string(); for (n, (retries, name)) in pkgs.iter().enumerate() { let count_clone = fail_counts.clone(); let retries = *retries; builder = builder.add_responder(format!("/dl/{name}/1.0.0/download"), move |req, server| { let mut fail_counts = count_clone.lock().unwrap(); if fail_counts[n] < retries { fail_counts[n] += 1; server.internal_server_error(req) } else { server.dl(req) } }); write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap(); for retry in 0..retries { let remain = 3 - retry; write!( &mut expected, "[WARNING] spurious network error ({remain} {} remaining): \ failed to get successful HTTP response from \ `http://127.0.0.1:[..]/dl/{name}/1.0.0/download` (127.0.0.1), got 500\n\ body:\n\ internal server error\n", if remain != 1 { "tries" } else { "try" } ) .unwrap(); } write!( &mut expected, "[DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`)\n" ) .unwrap(); } write!( &mut expected, "[LOCKING] 93 packages to latest compatible versions\n" ) .unwrap(); let _server = builder.build(); for (_, name) in &pkgs { Package::new(name, "1.0.0").publish(); } let p = project() .file("Cargo.toml", &cargo_toml) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_stderr_data(IntoData::unordered(expected)) .run(); } #[cargo_test] fn retry_too_many_requests() { let fail_count = Mutex::new(0); let _registry = RegistryBuilder::new() .http_index() .add_responder("/index/3/b/bar", move |req, server| { let mut fail_count = fail_count.lock().unwrap(); if *fail_count < 1 { *fail_count += 1; server.too_many_requests(req, std::time::Duration::from_secs(1)) } else { server.index(req) } }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").publish(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `[..]/index/3/b/bar` ([..]), got 429 body: too many requests, try again in 1 seconds [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]).run(); } #[cargo_test] fn deleted_entry() { // Checks the behavior when a package is removed from the index. // This is done occasionally on crates.io to handle things like // copyright takedowns. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "0.1" "#, ) .file("src/lib.rs", "") .build(); // First, test removing a single version, but leaving an older version. Package::new("bar", "0.1.0").publish(); let bar_path = Path::new("3/b/bar"); let bar_reg_path = registry_path().join(&bar_path); let old_index = fs::read_to_string(&bar_reg_path).unwrap(); Package::new("bar", "0.1.1").publish(); p.cargo("tree") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.1 (registry `dummy-registry`) "#]]) .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar v0.1.1 "#]]) .run(); // Remove 0.1.1 fs::remove_file(paths::root().join("dl/bar/0.1.1/download")).unwrap(); let repo = git2::Repository::open(registry_path()).unwrap(); let mut index = repo.index().unwrap(); fs::write(&bar_reg_path, &old_index).unwrap(); index.add_path(&bar_path).unwrap(); index.write().unwrap(); git::commit(&repo); // With `Cargo.lock` unchanged, it shouldn't have an impact. p.cargo("tree") .with_stderr_data("") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar v0.1.1 "#]]) .run(); // Regenerating Cargo.lock should switch to old version. fs::remove_file(p.root().join("Cargo.lock")).unwrap(); p.cargo("tree") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0 (registry `dummy-registry`) "#]]) .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar v0.1.0 "#]]) .run(); // Remove the package entirely. fs::remove_file(paths::root().join("dl/bar/0.1.0/download")).unwrap(); let mut index = repo.index().unwrap(); index.remove(&bar_path, 0).unwrap(); index.write().unwrap(); git::commit(&repo); fs::remove_file(&bar_reg_path).unwrap(); // With `Cargo.lock` unchanged, it shouldn't have an impact. p.cargo("tree") .with_stderr_data("") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar v0.1.0 "#]]) .run(); // Regenerating Cargo.lock should fail. fs::remove_file(p.root().join("Cargo.lock")).unwrap(); p.cargo("tree") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `bar` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.1.0 ([ROOT]/foo)` "#]]) .with_status(101) .run(); } #[cargo_test] fn corrupted_ok_overwritten() { // Checks what happens if .cargo-ok gets truncated, such as if the file is // created, but the flush/close is interrupted. Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) "#]]) .run(); let ok = glob::glob( paths::home() .join(".cargo/registry/src/*/bar-1.0.0/.cargo-ok") .to_str() .unwrap(), ) .unwrap() .next() .unwrap() .unwrap(); // Simulate cargo being interrupted, or filesystem corruption. fs::write(&ok, "").unwrap(); assert_eq!(fs::read_to_string(&ok).unwrap(), ""); p.cargo("fetch").with_stderr_data("").run(); assert_eq!(fs::read_to_string(&ok).unwrap(), r#"{"v":1}"#); } #[cargo_test] fn not_found_permutations() { // Test for querying permutations for a missing dependency. let misses = Arc::new(Mutex::new(Vec::new())); let misses2 = misses.clone(); let _registry = RegistryBuilder::new() .http_index() .not_found_handler(move |req, _server| { let mut misses = misses2.lock().unwrap(); misses.push(req.url.path().to_string()); Response { code: 404, headers: vec![], body: b"not found".to_vec(), } }) .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] a-b_c = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `a-b_c` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.0.1 ([ROOT]/foo)` "#]]) .run(); let mut misses = misses.lock().unwrap(); misses.sort(); assert_eq!( &*misses, &[ "/index/a-/b-/a-b-c", "/index/a-/b_/a-b_c", "/index/a_/b_/a_b_c" ] ); } #[cargo_test] fn default_auth_error() { // Check for the error message for an authentication error when default is set. let crates_io = RegistryBuilder::new().http_api().build(); let _alternative = RegistryBuilder::new().http_api().alternative().build(); paths::home().join(".cargo/credentials.toml").rm_rf(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); // Test output before setting the default. p.cargo("publish --no-verify") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] no token found, please run `cargo login` or use environment variable CARGO_REGISTRY_TOKEN "#]]) .with_status(101) .run(); p.cargo("publish --no-verify --registry alternative") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] no token found for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN "#]]) .with_status(101) .run(); // Test the output with the default. cargo_util::paths::append( &paths::cargo_home().join("config.toml"), br#" [registry] default = "alternative" "#, ) .unwrap(); p.cargo("publish --no-verify") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] no token found for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN "#]]) .with_status(101) .run(); p.cargo("publish --no-verify --registry crates-io") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] no token found, please run `cargo login --registry crates-io` or use environment variable CARGO_REGISTRY_TOKEN "#]]) .with_status(101) .run(); } const SAMPLE_HEADERS: &[&str] = &[ "x-amz-cf-pop: SFO53-P2", "x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==", "x-cache: Hit from cloudfront", "server: AmazonS3", "x-amz-version-id: pvsJYY_JGsWiSETZvLJKb7DeEW5wWq1W", "x-amz-server-side-encryption: AES256", "content-type: text/plain", "via: 1.1 bcbc5b46216015493e082cfbcf77ef10.cloudfront.net (CloudFront)", ]; #[cargo_test] fn debug_header_message_index() { // The error message should include some headers for debugging purposes. let _server = RegistryBuilder::new() .http_index() .add_responder("/index/3/b/bar", |_, _| Response { code: 503, headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(), body: b"Please slow down".to_vec(), }) .build(); Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 body: Please slow down [WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 body: Please slow down [WARNING] spurious network error (1 try remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 body: Please slow down [ERROR] failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to query replaced source registry `crates-io` Caused by: download of 3/b/bar failed Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 debug headers: x-amz-cf-pop: SFO53-P2 x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA== x-cache: Hit from cloudfront body: Please slow down "#]]) .run(); } #[cargo_test] fn debug_header_message_dl() { // Same as debug_header_message_index, but for the dl endpoint which goes // through a completely different code path. let _server = RegistryBuilder::new() .http_index() .add_responder("/dl/bar/1.0.0/download", |_, _| Response { code: 503, headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(), body: b"Please slow down".to_vec(), }) .build(); Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 body: Please slow down [WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 body: Please slow down [WARNING] spurious network error (1 try remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 body: Please slow down [ERROR] failed to download from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 debug headers: x-amz-cf-pop: SFO53-P2 x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA== x-cache: Hit from cloudfront body: Please slow down "#]]) .run(); } #[cfg(unix)] #[cargo_test] fn set_mask_during_unpacking() { use std::os::unix::fs::MetadataExt; Package::new("bar", "1.0.0") .file_with_mode("example.sh", 0o777, "#!/bin/sh") .file_with_mode("src/lib.rs", 0o666, "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) "#]]) .run(); let src_file_path = |path: &str| { glob::glob( paths::home() .join(".cargo/registry/src/*/bar-1.0.0/") .join(path) .to_str() .unwrap(), ) .unwrap() .next() .unwrap() .unwrap() }; let umask = cargo::util::get_umask(); let metadata = fs::metadata(src_file_path("src/lib.rs")).unwrap(); assert_eq!(metadata.mode() & 0o777, 0o666 & !umask); let metadata = fs::metadata(src_file_path("example.sh")).unwrap(); assert_eq!(metadata.mode() & 0o777, 0o777 & !umask); } #[cargo_test] fn unpack_again_when_cargo_ok_is_unrecognized() { Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) "#]]) .run(); let src_file_path = |path: &str| { glob::glob( paths::home() .join(".cargo/registry/src/*/bar-1.0.0/") .join(path) .to_str() .unwrap(), ) .unwrap() .next() .unwrap() .unwrap() }; // Change permissions to simulate the old behavior not respecting umask. let lib_rs = src_file_path("src/lib.rs"); let cargo_ok = src_file_path(".cargo-ok"); let mut perms = fs::metadata(&lib_rs).unwrap().permissions(); assert!(!perms.readonly()); perms.set_readonly(true); fs::set_permissions(&lib_rs, perms).unwrap(); let ok = fs::read_to_string(&cargo_ok).unwrap(); assert_eq!(&ok, r#"{"v":1}"#); p.cargo("fetch").with_stderr_data("").run(); // Without changing `.cargo-ok`, a unpack won't be triggered. let perms = fs::metadata(&lib_rs).unwrap().permissions(); assert!(perms.readonly()); // Write "ok" to simulate the old behavior and trigger the unpack again. fs::write(&cargo_ok, "ok").unwrap(); p.cargo("fetch").with_stderr_data("").run(); // Permission has been restored and `.cargo-ok` is in the new format. let perms = fs::metadata(lib_rs).unwrap().permissions(); assert!(!perms.readonly()); let ok = fs::read_to_string(&cargo_ok).unwrap(); assert_eq!(&ok, r#"{"v":1}"#); } #[cargo_test] fn differ_only_by_metadata() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "=0.0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1+b").publish(); Package::new("baz", "0.0.1+c").yanked(true).publish(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] baz v0.0.1+b (registry `dummy-registry`) [CHECKING] baz v0.0.1+b [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); Package::new("baz", "0.0.1+d").publish(); p.cargo("clean").run(); p.cargo("check") .with_stderr_data(str![[r#" [CHECKING] baz v0.0.1+b [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn differ_only_by_metadata_with_lockfile() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "=0.0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1+a").publish(); Package::new("baz", "0.0.1+b").publish(); Package::new("baz", "0.0.1+c").publish(); p.cargo("update --package baz --precise 0.0.1+b") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] baz v0.0.1+c -> v0.0.1+b "#]]) .run(); p.cargo("check") .with_stderr_data(str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] baz v0.0.1+b (registry `dummy-registry`) [CHECKING] baz v0.0.1+b [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn builtin_source_replacement() { // errors for builtin source replacement of crates.io // should not include mention of source replacement in the error message. let server = RegistryBuilder::new().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bad-cksum = ">= 0.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); let pkg = Package::new("bad-cksum", "0.0.1"); pkg.publish(); t!(File::create(&pkg.archive_dst())); p.cargo("check -v") .replace_crates_io(&server.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bad-cksum v0.0.1 [ERROR] failed to verify the checksum of `bad-cksum v0.0.1` "#]]) .run(); } #[cargo_test] fn builtin_source_replacement_no_vendor_error() { // errors for builtin source replacement of crates.io // should not mention outdated vendor dependencies let server = RegistryBuilder::new().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2021" [dependencies] dep = "0.2.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); let pkg = Package::new("dep", "0.1.0"); pkg.publish(); p.cargo("check -v") .replace_crates_io(&server.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] failed to select a version for the requirement `dep = "^0.2.0"` candidate versions found which didn't match: 0.1.0 location searched: crates.io index required by package `foo v0.0.1 ([ROOT]/foo)` "#]]) .run(); } #[cargo_test] fn deterministic_mtime() { let registry = registry::init(); Package::new("foo", "0.1.0") // content doesn't matter, we just want to check mtime .file("Cargo.lock", "") .file(".cargo_vcs_info.json", "") .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "a" edition = "2015" [dependencies] foo = '0.1.0' "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch").run(); let id = SourceId::for_registry(registry.index_url()).unwrap(); let hash = cargo::util::hex::short_hash(&id); let pkg_root = paths::cargo_home() .join("registry") .join("src") .join(format!("-{hash}")) .join("foo-0.1.0"); // Generated files should have deterministic mtime after unpacking. assert_deterministic_mtime(pkg_root.join("Cargo.lock")); assert_deterministic_mtime(pkg_root.join("Cargo.toml")); assert_deterministic_mtime(pkg_root.join(".cargo_vcs_info.json")); } #[cargo_test] fn symlink_and_directory() { // Tests for symlink and directory entry in a tar file. The tar crate // would incorrectly change the permissions of the symlink destination, // which could be anywhere on the filesystem. let victim = paths::root().join("victim"); fs::create_dir(&victim).unwrap(); #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let perm = fs::Permissions::from_mode(0o700); fs::set_permissions(&victim, perm).unwrap(); assert_eq!( victim.metadata().unwrap().permissions().mode() & 0o777, 0o700 ); } Package::new("bar", "1.0.0") .file("src/lib.rs", "") .symlink("smuggled", victim.to_str().unwrap()) .directory("smuggled") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [ERROR] failed to download replaced source registry `crates-io` Caused by: failed to unpack package `bar v1.0.0 (registry `dummy-registry`)` Caused by: failed to unpack entry at `bar-1.0.0/smuggled` Caused by: failed to unpack `[ROOT]/home/.cargo/registry/src/-[HASH]/bar-1.0.0/smuggled` Caused by: [..] when creating dir [ROOT]/home/.cargo/registry/src/-[HASH]/bar-1.0.0/smuggled "#]]) .run(); #[cfg(unix)] { // Permissions should not change. use std::os::unix::fs::PermissionsExt; assert_eq!( victim.metadata().unwrap().permissions().mode() & 0o777, 0o700 ); } } ================================================ FILE: tests/testsuite/registry_auth.rs ================================================ //! Tests for registry authentication. use crate::prelude::*; use cargo_test_support::compare::assert_e2e; use cargo_test_support::registry::{Package, RegistryBuilder, Token}; use cargo_test_support::str; use cargo_test_support::{Execs, Project, project}; fn cargo(p: &Project, s: &str) -> Execs { let mut e = p.cargo(s); e.masquerade_as_nightly_cargo(&["asymmetric-token"]) .arg("-Zasymmetric-token"); e.env( "CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS", "cargo:paseto cargo:token", ); e } fn make_project() -> Project { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] version = "0.0.1" registry = "alternative" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("bar", "0.0.1").alternative(true).publish(); p } #[cargo_test] fn requires_credential_provider() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_api() .build(); let p = make_project(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [ERROR] failed to download `bar v0.0.1 (registry `alternative`)` Caused by: unable to get packages from source Caused by: authenticated registries require a credential-provider to be configured see https://doc.rust-lang.org/cargo/reference/registry-authentication.html for details "#]]) .run(); } #[cargo_test] fn simple() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_index() .build(); let p = make_project(); cargo(&p, "build") .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn simple_with_asymmetric() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_index() .token(cargo_test_support::registry::Token::rfc_key()) .build(); let p = make_project(); cargo(&p, "build") .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn environment_config() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_registry() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .env( "CARGO_REGISTRIES_ALTERNATIVE_INDEX", registry.index_url().as_str(), ) .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", registry.token()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn environment_token() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", registry.token()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn environment_token_with_asymmetric() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::Keys( "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36" .to_string(), None, )) .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `alternative`) [COMPILING] bar v0.0.1 (registry `alternative`) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn bad_environment_token_with_asymmetric_subject() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::Keys( "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36" .to_string(), None, )) .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key()) .env( "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY_SUBJECT", "incorrect", ) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .with_status(101) .run(); } #[cargo_test] fn bad_environment_token_with_asymmetric_incorrect_subject() { let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::rfc_key()) .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key()) .env( "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY_SUBJECT", "incorrect", ) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .with_status(101) .run(); } #[cargo_test] fn bad_environment_token_with_incorrect_asymmetric() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .token(cargo_test_support::registry::Token::Keys( "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36" .to_string(), None, )) .build(); let p = make_project(); cargo(&p, "build") .env( "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", "k3.secret.9Vxr5hVlI_g_orBZN54vPz20bmB4O76wB_MVqUSuJJJqHFLwP8kdn_RY5g6J6pQG", ) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .with_status(101) .run(); } #[cargo_test] fn missing_token() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: no token found for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN "#]]) .run(); } #[cargo_test] fn missing_token_git() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .build(); let p = make_project(); cargo(&p, "build") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [ERROR] failed to download `bar v0.0.1 (registry `alternative`)` Caused by: unable to get packages from source Caused by: no token found for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN "#]]) .run(); } #[cargo_test] fn incorrect_token() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_index() .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", "incorrect") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: token rejected for `alternative`, please run `cargo login --registry alternative` or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 body: Unauthorized message from server. "#]]) .run(); } #[cargo_test] fn incorrect_token_git() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .http_api() .build(); let p = make_project(); cargo(&p, "build") .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", "incorrect") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [ERROR] failed to download from `http://127.0.0.1:[..]/dl/bar/0.0.1/download` Caused by: failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/0.0.1/download` (127.0.0.1), got 401 body: Unauthorized message from server. "#]]) .run(); } #[cargo_test] fn anonymous_alt_registry() { // An alternative registry that requires auth, but is not in the config. let registry = RegistryBuilder::new() .alternative() .auth_required() .no_configure_token() .no_configure_registry() .http_index() .build(); let p = make_project(); cargo(&p, &format!("install --index {} bar", registry.index_url())) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `sparse+http://127.0.0.1:[..]/index/` index [ERROR] no token found for `sparse+http://127.0.0.1:[..]/index/` consider setting up an alternate registry in Cargo's configuration as described by https://doc.rust-lang.org/cargo/reference/registries.html [registries] my-registry = { index = "sparse+http://127.0.0.1:[..]/index/" } "#]]) .run(); } #[cargo_test] fn login() { let _registry = RegistryBuilder::new() .alternative() .no_configure_token() .auth_required() .http_index() .build(); let p = make_project(); cargo(&p, "login --registry alternative") .with_stdin("sekrit") .run(); } #[cargo_test] fn login_existing_token() { let _registry = RegistryBuilder::new() .alternative() .auth_required() .http_index() .build(); let p = make_project(); cargo(&p, "login --registry alternative") .with_stdin("sekrit") .run(); } #[cargo_test] fn duplicate_index() { let server = RegistryBuilder::new() .alternative() .no_configure_token() .auth_required() .build(); let p = make_project(); // Two alternative registries with the same index. cargo(&p, "build") .env( "CARGO_REGISTRIES_ALTERNATIVE1_INDEX", server.index_url().as_str(), ) .env( "CARGO_REGISTRIES_ALTERNATIVE2_INDEX", server.index_url().as_str(), ) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [LOCKING] 1 package to latest compatible version [ERROR] failed to download `bar v0.0.1 (registry `alternative`)` Caused by: unable to get packages from source Caused by: multiple registries are configured with the same index url 'registry+[ROOTURL]/alternative-registry': alternative1, alternative2 "#]]) .run(); } #[cargo_test] fn token_not_logged() { // Checks that the token isn't displayed in debug output (for both HTTP // index and registry API). Note that this doesn't fully verify the // correct behavior since we don't have an HTTP2 server, and curl behaves // significantly differently when using HTTP2. let crates_io = RegistryBuilder::new() .http_api() .http_index() .auth_required() .token(Token::Plaintext("a-unique_token".to_string())) .build(); Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); let output = cargo(&p, "publish") .replace_crates_io(crates_io.index_url()) .env("CARGO_HTTP_DEBUG", "true") .env("CARGO_LOG", "trace") .run(); let log = String::from_utf8(output.stderr).unwrap(); assert_e2e().eq( &log, str![[r#" ... [PUBLISHED] foo v0.1.0 at registry `crates-io` "#]], ); let authorizations: Vec<_> = log .lines() .filter(|line| { line.contains("http-debug:") && line.to_lowercase().contains("authorization") }) .collect(); assert!(authorizations.iter().all(|line| line.contains("REDACTED"))); // Total authorizations: // 1. Initial config.json // 2. /index/3/f/foo // 3. config.json again for verification // 4. /index/3/b/bar // 5. config.json again for verification // 6. /index/3/b/bar // 7. /dl/bar/1.0.0/download // 8. /api/v1/crates/new // 9. config.json again for verification // 10. /index/3/f/foo for the "wait for publish" assert_eq!(authorizations.len(), 10); assert!(!log.contains("a-unique_token")); } ================================================ FILE: tests/testsuite/registry_overlay.rs ================================================ //! Tests for local-registry sources. use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::registry::{Package, RegistryBuilder, TestRegistry}; use cargo_test_support::str; fn setup() -> (TestRegistry, String) { let alt = RegistryBuilder::new().alternative().build(); ( RegistryBuilder::new().http_index().build(), alt.index_url() .to_file_path() .unwrap() .into_os_string() .into_string() .unwrap(), ) } #[cargo_test] fn overlay_hit() { let (reg, alt_path) = setup(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); // baz is only in the local registry, but it gets found Package::new("baz", "0.1.1") .alternative(true) .local(true) .publish(); p.cargo("check") .overlay_registry(®.index_url(), &alt_path) .run(); } #[cargo_test] fn registry_version_wins() { let (reg, alt_path) = setup(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); // The latest one is in the main registry, so it will get chosen. Package::new("baz", "0.1.1").publish(); Package::new("baz", "0.1.0") .alternative(true) .local(true) .publish(); p.cargo("check") .overlay_registry(®.index_url(), &alt_path) .with_stderr_data(str![[r#" [UPDATING] `sparse+http://127.0.0.1:[..]/index/` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] baz v0.1.1 (registry `sparse+http://127.0.0.1:[..]/index/`) [CHECKING] baz v0.1.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn overlay_version_wins() { let (reg, alt_path) = setup(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); // The latest one is in the overlay registry, so it will get chosen. Package::new("baz", "0.1.0").publish(); Package::new("baz", "0.1.1") .alternative(true) .local(true) .publish(); p.cargo("check") .overlay_registry(®.index_url(), &alt_path) .with_stderr_data(str![[r#" [UPDATING] `sparse+http://127.0.0.1:[..]/index/` index [LOCKING] 1 package to latest compatible version [UNPACKING] baz v0.1.1 (registry `[ROOT]/alternative-registry`) [CHECKING] baz v0.1.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn version_precedence() { let (reg, alt_path) = setup(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); // The one we want is in the main registry. Package::new("baz", "0.1.1").publish(); Package::new("baz", "0.1.1") .alternative(true) .local(true) .publish(); p.cargo("check") .overlay_registry(®.index_url(), &alt_path) .with_stderr_data(str![[r#" [UPDATING] `sparse+http://127.0.0.1:[..]/index/` index [LOCKING] 1 package to latest compatible version [UNPACKING] baz v0.1.1 (registry `[ROOT]/alternative-registry`) [CHECKING] baz v0.1.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn local_depends_on_old_registry_package() { let (reg, alt_path) = setup(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "0.1.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("baz", "0.0.1").publish(); // A new local package can depend on an older version in the registry. Package::new("baz", "0.1.1") .dep("baz", "=0.0.1") .alternative(true) .local(true) .publish(); p.cargo("check") .overlay_registry(®.index_url(), &alt_path) .run(); } #[cargo_test] fn registry_dep_depends_on_new_local_package() { let (reg, alt_path) = setup(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] registry-package = "0.1.0" workspace-package = "0.0.1" "#, ) .file("src/main.rs", "fn main() {}") .build(); Package::new("registry-package", "0.1.0") .dep("workspace-package", "0.1.0") .publish(); // The local overlay contains an updated version of workspace-package Package::new("workspace-package", "0.1.1") .alternative(true) .local(true) .publish(); // The registry contains older versions of workspace-package (one of which // we depend on directly). Package::new("workspace-package", "0.1.0").publish(); Package::new("workspace-package", "0.0.1").publish(); p.cargo("check") .overlay_registry(®.index_url(), &alt_path) .with_stderr_data( str![[r#" [UPDATING] `sparse+http://127.0.0.1:[..]/index/` index [LOCKING] 3 packages to latest compatible versions [ADDING] workspace-package v0.0.1 (available: v0.1.1) [DOWNLOADING] crates ... [UNPACKING] workspace-package v0.1.1 (registry `[ROOT]/alternative-registry`) [DOWNLOADED] registry-package v0.1.0 (registry `sparse+http://127.0.0.1:[..]/index/`) [DOWNLOADED] workspace-package v0.0.1 (registry `sparse+http://127.0.0.1:[..]/index/`) [CHECKING] workspace-package v0.1.1 [CHECKING] workspace-package v0.0.1 [CHECKING] registry-package v0.1.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); } // Test that we can overlay on top of alternate registries, not just crates-io. // Since the test framework only supports a single alternate registry, we repurpose // the dummy crates-io as the registry to overlay on top. #[cargo_test] fn alt_registry() { let alt = RegistryBuilder::new().http_index().alternative().build(); let crates_io = RegistryBuilder::new().build(); let crates_io_path = crates_io .index_url() .to_file_path() .unwrap() .into_os_string() .into_string() .unwrap(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = { version = "0.1.0", registry = "alternative" } "#, ) .file("src/main.rs", "fn main() {}") .build(); // This package isn't used, but publishing it forces the creation of the registry index. Package::new("bar", "0.0.1").local(true).publish(); Package::new("baz", "0.1.1").alternative(true).publish(); p.cargo("check") .overlay_registry(&alt.index_url(), &crates_io_path) .run(); } ================================================ FILE: tests/testsuite/rename_deps.rs ================================================ //! Tests for renaming dependencies. use crate::prelude::*; use cargo_test_support::git; use cargo_test_support::paths; use cargo_test_support::registry::{self, Package}; use cargo_test_support::{basic_manifest, project, str}; #[cargo_test] fn rename_dependency() { Package::new("bar", "0.1.0").publish(); Package::new("bar", "0.2.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = { version = "0.1.0" } baz = { version = "0.2.0", package = "bar" } "#, ) .file("src/lib.rs", "extern crate bar; extern crate baz;") .build(); p.cargo("build").run(); } #[cargo_test] fn rename_with_different_names() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = { path = "bar", package = "bar" } "#, ) .file("src/lib.rs", "extern crate baz;") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [lib] name = "random_name" "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("build").run(); } #[cargo_test] fn lots_of_names() { registry::alt_init(); Package::new("foo", "0.1.0") .file("src/lib.rs", "pub fn foo1() {}") .publish(); Package::new("foo", "0.2.0") .file("src/lib.rs", "pub fn foo() {}") .publish(); Package::new("foo", "0.1.0") .file("src/lib.rs", "pub fn foo2() {}") .alternative(true) .publish(); let g = git::repo(&paths::root().join("another")) .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("src/lib.rs", "pub fn foo3() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "test" version = "0.1.0" edition = "2015" authors = [] [dependencies] foo = "0.2" foo1 = {{ version = "0.1", package = "foo" }} foo2 = {{ version = "0.1", registry = "alternative", package = "foo" }} foo3 = {{ git = '{}', package = "foo" }} foo4 = {{ path = "foo", package = "foo" }} "#, g.url() ), ) .file( "src/lib.rs", " extern crate foo; extern crate foo1; extern crate foo2; extern crate foo3; extern crate foo4; pub fn foo() { foo::foo(); foo1::foo1(); foo2::foo2(); foo3::foo3(); foo4::foo4(); } ", ) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", "pub fn foo4() {}") .build(); p.cargo("build -v").run(); } #[cargo_test] fn rename_and_patch() { Package::new("foo", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { version = "0.1", package = "foo" } [patch.crates-io] foo = { path = "foo" } "#, ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::foo(); }", ) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", "pub fn foo() {}") .build(); p.cargo("build -v").run(); } #[cargo_test] fn rename_twice() { Package::new("foo", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { version = "0.1", package = "foo" } [build-dependencies] foo = { version = "0.1" } "#, ) .file("src/lib.rs", "") .build(); p.cargo("build -v") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] foo v0.1.0 (registry `dummy-registry`) [ERROR] the crate `test v0.1.0 ([ROOT]/foo)` depends on crate `foo v0.1.0` multiple times with different names "#]]) .run(); } #[cargo_test] fn rename_affects_fingerprint() { Package::new("foo", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.1.0" edition = "2015" authors = [] [dependencies] foo = { version = "0.1", package = "foo" } "#, ) .file("src/lib.rs", "extern crate foo;") .build(); p.cargo("build -v").run(); p.change_file( "Cargo.toml", r#" [package] name = "test" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { version = "0.1", package = "foo" } "#, ); p.cargo("build -v") .with_status(101) .with_stderr_data(str![[r#" [FRESH] foo v0.1.0 [DIRTY] test v0.1.0 ([ROOT]/foo): name of dependency changed (foo => bar) [COMPILING] test v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..]` error[E0463]: can't find crate for `foo` ... "#]]) .run(); } #[cargo_test] fn can_run_doc_tests() { Package::new("bar", "0.1.0").publish(); Package::new("bar", "0.2.0").publish(); let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = { version = "0.1.0" } baz = { version = "0.2.0", package = "bar" } "#, ) .file( "src/lib.rs", " extern crate bar; extern crate baz; ", ) .build(); foo.cargo("test -v").with_stderr_data(str![[r#" ... [DOCTEST] foo [RUNNING] `rustdoc [..]--test src/lib.rs [..] --extern bar=[ROOT]/foo/target/debug/deps/libbar-[HASH].rlib --extern baz=[ROOT]/foo/target/debug/deps/libbar-[HASH].rlib [..]` "#]]).run(); } #[cargo_test] fn features_still_work() { Package::new("foo", "0.1.0").publish(); Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.1.0" edition = "2015" authors = [] [dependencies] p1 = { path = 'a', features = ['b'] } p2 = { path = 'b' } "#, ) .file("src/lib.rs", "") .file( "a/Cargo.toml", r#" [package] name = "p1" version = "0.1.0" edition = "2015" authors = [] [dependencies] b = { version = "0.1", package = "foo", optional = true } "#, ) .file("a/src/lib.rs", "extern crate b;") .file( "b/Cargo.toml", r#" [package] name = "p2" version = "0.1.0" edition = "2015" authors = [] [dependencies] b = { version = "0.1", package = "bar", optional = true } [features] default = ['b'] "#, ) .file("b/src/lib.rs", "extern crate b;") .build(); p.cargo("build -v").run(); } #[cargo_test] fn features_not_working() { Package::new("foo", "0.1.0").publish(); Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.1.0" edition = "2015" authors = [] [dependencies] a = { path = 'a', package = 'p1', optional = true } [features] default = ['p1'] "#, ) .file("src/lib.rs", "") .file("a/Cargo.toml", &basic_manifest("p1", "0.1.0")) .build(); p.cargo("build -v") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: feature `default` includes `p1` which is neither a dependency nor another feature "#]]) .run(); } #[cargo_test] fn rename_with_dash() { let p = project() .file( "Cargo.toml", r#" [package] name = "qwerty" version = "0.1.0" edition = "2015" [dependencies] foo-bar = { path = 'a', package = 'a' } "#, ) .file("src/lib.rs", "extern crate foo_bar;") .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "") .build(); p.cargo("build").run(); } ================================================ FILE: tests/testsuite/replace.rs ================================================ //! Tests for `[replace]` table source replacement. use crate::prelude::*; use cargo_test_support::git; use cargo_test_support::paths; use cargo_test_support::registry::Package; use cargo_test_support::{basic_manifest, project, str}; #[cargo_test] fn override_simple() { Package::new("bar", "0.1.0").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, bar.url() ), ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 2 packages to latest compatible versions [CHECKING] bar v0.1.0 ([ROOTURL]/override#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn override_with_features() { Package::new("bar", "0.1.0").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{}', features = ["some_feature"] }} "#, bar.url() ), ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 2 packages to latest compatible versions [WARNING] unused field in replacement for `bar`: `features` | = [NOTE] configure `features` in the `dependencies` entry [CHECKING] bar v0.1.0 ([ROOTURL]/override#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn override_with_setting_default_features() { Package::new("bar", "0.1.0").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{}', default-features = false, features = ["none_default_feature"] }} "#, bar.url() ), ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 2 packages to latest compatible versions [WARNING] unused field in replacement for `bar`: `features`, `default-features` | = [NOTE] configure `features`, `default-features` in the `dependencies` entry [CHECKING] bar v0.1.0 ([ROOTURL]/override#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn missing_version() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] bar = { git = 'https://example.com' } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").with_status(101).with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: replacements must specify a version to replace, but `https://github.com/rust-lang/crates.io-index#bar` does not "#]]).run(); } #[cargo_test] fn invalid_semver_version() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" [replace] "bar:*" = { git = 'https://example.com' } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: replacements must specify a valid semver version to replace, but `bar:*` does not ... "#]]) .run(); } #[cargo_test] fn different_version() { Package::new("bar", "0.2.0").publish(); Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = "0.2.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").with_status(101).with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: replacements cannot specify a version requirement, but found one for `https://github.com/rust-lang/crates.io-index#bar@0.1.0` "#]]).run(); } #[cargo_test] fn transitive() { Package::new("bar", "0.1.0").publish(); Package::new("baz", "0.2.0") .dep("bar", "0.1.0") .file("src/lib.rs", "extern crate bar; fn baz() { bar::bar(); }") .publish(); let foo = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "0.2.0" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, foo.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 3 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] baz v0.2.0 (registry `dummy-registry`) [CHECKING] bar v0.1.0 ([ROOTURL]/override#[..]) [CHECKING] baz v0.2.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn persists_across_rebuilds() { Package::new("bar", "0.1.0").publish(); let foo = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, foo.url() ), ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 2 packages to latest compatible versions [CHECKING] bar v0.1.0 ([ROOTURL]/override#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn replace_registry_with_path() { Package::new("bar", "0.1.0").publish(); let _ = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = { path = "../bar" } "#, ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [CHECKING] bar v0.1.0 ([ROOT]/bar) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn use_a_spec_to_select() { Package::new("baz", "0.1.1") .file("src/lib.rs", "pub fn baz1() {}") .publish(); Package::new("baz", "0.2.0").publish(); Package::new("bar", "0.1.1") .dep("baz", "0.2") .file( "src/lib.rs", "extern crate baz; pub fn bar() { baz::baz3(); }", ) .publish(); let foo = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("baz", "0.2.0")) .file("src/lib.rs", "pub fn baz3() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1" baz = "0.1" [replace] "baz:0.2.0" = {{ git = '{}' }} "#, foo.url() ), ) .file( "src/lib.rs", " extern crate bar; extern crate baz; pub fn local() { baz::baz1(); bar::bar(); } ", ) .build(); p.cargo("check") .with_stderr_data( str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 4 packages to latest compatible versions [ADDING] baz v0.1.1 (available: v0.2.0) [DOWNLOADING] crates ... [DOWNLOADED] baz v0.1.1 (registry `dummy-registry`) [DOWNLOADED] bar v0.1.1 (registry `dummy-registry`) [CHECKING] baz v0.2.0 ([ROOTURL]/override#[..]) [CHECKING] baz v0.1.1 [CHECKING] bar v0.1.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); } #[cargo_test] fn override_adds_some_deps() { Package::new("baz", "0.1.1").publish(); Package::new("bar", "0.1.0").publish(); let foo = git::repo(&paths::root().join("override")) .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] baz = "0.1" "#, ) .file("src/lib.rs", "") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, foo.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 3 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] baz v0.1.1 (registry `dummy-registry`) [CHECKING] baz v0.1.1 [CHECKING] bar v0.1.0 ([ROOTURL]/override#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); Package::new("baz", "0.1.2").publish(); p.cargo("update") .arg(&format!("{}#bar", foo.url())) .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/override` [UPDATING] `dummy-registry` index [LOCKING] 0 packages to latest compatible versions [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest "#]]) .run(); p.cargo("update https://github.com/rust-lang/crates.io-index#bar") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 0 packages to latest compatible versions [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest "#]]) .run(); p.cargo("check") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn locked_means_locked_yes_no_seriously_i_mean_locked() { // this in theory exercises #2041 Package::new("baz", "0.1.0").publish(); Package::new("baz", "0.2.0").publish(); Package::new("bar", "0.1.0").publish(); let foo = git::repo(&paths::root().join("override")) .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] baz = "*" "#, ) .file("src/lib.rs", "") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1" baz = "0.1" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, foo.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); p.cargo("check") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn override_wrong_name() { Package::new("baz", "0.1.0").publish(); let foo = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] baz = "0.1" [replace] "baz:0.1.0" = {{ git = '{}' }} "#, foo.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [ERROR] failed to get `baz` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: no matching package for override `https://github.com/rust-lang/crates.io-index#baz@0.1.0` found location searched: [ROOTURL]/override version required: =0.1.0 "#]]) .run(); } #[cargo_test] fn override_with_nothing() { Package::new("bar", "0.1.0").publish(); let foo = git::repo(&paths::root().join("override")) .file("src/lib.rs", "") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, foo.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: failed to load source for dependency `bar` Caused by: unable to update [ROOTURL]/override Caused by: could not find `Cargo.toml` in `[ROOT]/home/.cargo/git/checkouts/override-[HASH]/[..]` "#]]) .run(); } #[cargo_test] fn override_wrong_version() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [replace] "bar:0.1.0" = { git = 'https://example.com', version = '0.2.0' } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").with_status(101).with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: replacements cannot specify a version requirement, but found one for `https://github.com/rust-lang/crates.io-index#bar@0.1.0` "#]]).run(); } #[cargo_test] fn multiple_specs() { Package::new("bar", "0.1.0").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{0}' }} [replace."https://github.com/rust-lang/crates.io-index#bar:0.1.0"] git = '{0}' "#, bar.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: overlapping replacement specifications found: * https://github.com/rust-lang/crates.io-index#bar@0.1.0 * https://github.com/rust-lang/crates.io-index#bar@0.1.0 both specifications match: bar v0.1.0 "#]]) .run(); } #[cargo_test] fn test_override_dep() { Package::new("bar", "0.1.0").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{0}' }} "#, bar.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("test -p bar") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 2 packages to latest compatible versions [ERROR] specification `bar` is ambiguous [HELP] re-run this command with one of the following specifications registry+https://github.com/rust-lang/crates.io-index#bar@0.1.0 git+[ROOTURL]/override#bar@0.1.0 "#]]) .run(); } #[cargo_test] fn update() { Package::new("bar", "0.1.0").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{0}' }} "#, bar.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); p.cargo("update") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 0 packages to latest compatible versions "#]]) .run(); } // foo -> near -> far // near is overridden with itself #[cargo_test] fn no_override_self() { let deps = git::repo(&paths::root().join("override")) .file("far/Cargo.toml", &basic_manifest("far", "0.1.0")) .file("far/src/lib.rs", "") .file( "near/Cargo.toml", r#" [package] name = "near" version = "0.1.0" edition = "2015" authors = [] [dependencies] far = { path = "../far" } "#, ) .file("near/src/lib.rs", "#![no_std] pub extern crate far;") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] near = {{ git = '{0}' }} [replace] "near:0.1.0" = {{ git = '{0}' }} "#, deps.url() ), ) .file("src/lib.rs", "#![no_std] pub extern crate near;") .build(); p.cargo("check --verbose").run(); } #[cargo_test] fn override_an_override() { Package::new("chrono", "0.2.0") .dep("serde", "< 0.9") .publish(); Package::new("serde", "0.7.0") .file("src/lib.rs", "pub fn serde07() {}") .publish(); Package::new("serde", "0.8.0") .file("src/lib.rs", "pub fn serde08() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] chrono = "0.2" serde = "0.8" [replace] "chrono:0.2.0" = { path = "chrono" } "serde:0.8.0" = { path = "serde" } "#, ) .file( "Cargo.lock", r#" [[package]] name = "foo" version = "0.0.1" dependencies = [ "chrono 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "chrono" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" replace = "chrono 0.2.0" [[package]] name = "chrono" version = "0.2.0" dependencies = [ "serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" replace = "serde 0.8.0" [[package]] name = "serde" version = "0.8.0" "#, ) .file( "src/lib.rs", " extern crate chrono; extern crate serde; pub fn foo() { chrono::chrono(); serde::serde08_override(); } ", ) .file( "chrono/Cargo.toml", r#" [package] name = "chrono" version = "0.2.0" edition = "2015" authors = [] [dependencies] serde = "< 0.9" "#, ) .file( "chrono/src/lib.rs", " extern crate serde; pub fn chrono() { serde::serde07(); } ", ) .file("serde/Cargo.toml", &basic_manifest("serde", "0.8.0")) .file("serde/src/lib.rs", "pub fn serde08_override() {}") .build(); p.cargo("check -v").run(); } #[cargo_test] fn overriding_nonexistent_no_spurious() { Package::new("bar", "0.1.0").dep("baz", "0.1").publish(); Package::new("baz", "0.1.0").publish(); let bar = git::repo(&paths::root().join("override")) .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] baz = { path = "baz" } "#, ) .file("src/lib.rs", "pub fn bar() {}") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", "pub fn baz() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{url}' }} "baz:0.1.0" = {{ git = '{url}' }} "#, url = bar.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); p.cargo("check") .with_stderr_data(str![[r#" [WARNING] package replacement is not used: https://github.com/rust-lang/crates.io-index#baz@0.1.0 [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_stdout_data("") .run(); } #[cargo_test] fn no_warnings_when_replace_is_used_in_another_workspace_member() { Package::new("bar", "0.1.0").publish(); Package::new("baz", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = [ "first_crate", "second_crate"] [replace] "bar:0.1.0" = { path = "local_bar" } "#, ) .file( "first_crate/Cargo.toml", r#" [package] name = "first_crate" version = "0.1.0" edition = "2015" [dependencies] bar = "0.1.0" "#, ) .file("first_crate/src/lib.rs", "") .file( "second_crate/Cargo.toml", &basic_manifest("second_crate", "0.1.0"), ) .file("second_crate/src/lib.rs", "") .file("local_bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("local_bar/src/lib.rs", "") .build(); p.cargo("check") .cwd("first_crate") .with_stdout_data("") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [CHECKING] bar v0.1.0 ([ROOT]/foo/local_bar) [CHECKING] first_crate v0.1.0 ([ROOT]/foo/first_crate) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check") .cwd("second_crate") .with_stdout_data("") .with_stderr_data(str![[r#" [CHECKING] second_crate v0.1.0 ([ROOT]/foo/second_crate) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn replace_to_path_dep() { Package::new("bar", "0.1.0").dep("baz", "0.1").publish(); Package::new("baz", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = { path = "bar" } "#, ) .file("src/lib.rs", "extern crate bar;") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] baz = { path = "baz" } "#, ) .file( "bar/src/lib.rs", "extern crate baz; pub fn bar() { baz::baz(); }", ) .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("bar/baz/src/lib.rs", "pub fn baz() {}") .build(); p.cargo("check").run(); } #[cargo_test] fn override_with_default_feature() { Package::new("another", "0.1.0").publish(); Package::new("another", "0.1.1").dep("bar", "0.1").publish(); Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = { path = "bar", default-features = false } another = "0.1" another2 = { path = "another2" } [replace] 'bar:0.1.0' = { path = "bar" } "#, ) .file("src/main.rs", "extern crate bar; fn main() { bar::bar(); }") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [features] default = [] "#, ) .file( "bar/src/lib.rs", r#" #[cfg(feature = "default")] pub fn bar() {} "#, ) .file( "another2/Cargo.toml", r#" [package] name = "another2" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { version = "0.1", default-features = false } "#, ) .file("another2/src/lib.rs", "") .build(); p.cargo("run").run(); } #[cargo_test] fn override_plus_dep() { Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1" [replace] 'bar:0.1.0' = { path = "bar" } "#, ) .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] foo = { path = ".." } "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] cyclic package dependency: package `bar v0.1.0 ([ROOT]/foo/bar)` depends on itself. Cycle: package `bar v0.1.0 ([ROOT]/foo/bar)` ... which satisfies dependency `bar = "^0.1"` of package `foo v0.0.1 ([ROOT]/foo)` ... which satisfies path dependency `foo` of package `bar v0.1.0 ([ROOT]/foo/bar)` "#]]) .run(); } #[cargo_test] fn override_generic_matching_other_versions() { Package::new("bar", "0.1.0+a").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, bar.url() ), ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check").with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([ROOT]/foo)` Caused by: replacement specification `https://github.com/rust-lang/crates.io-index#bar@0.1.0` matched 0.1.0+a and tried to override it with 0.1.0 avoid matching unrelated packages by being more specific "#]]).with_status(101).run(); } #[cargo_test] fn override_respects_spec_metadata() { Package::new("bar", "0.1.0+a").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0+a")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0+notTheBuild" = {{ git = '{}' }} "#, bar.url() ), ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check").with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [WARNING] package replacement is not used: https://github.com/rust-lang/crates.io-index#bar@0.1.0+notTheBuild [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0+a (registry `dummy-registry`) [CHECKING] bar v0.1.0+a [CHECKING] foo v0.0.1 ([ROOT]/foo) error[E0425]: cannot find function `bar`[..] ... [ERROR] could not compile `foo` (lib) due to 1 previous error "#]]).with_status(101).run(); } #[cargo_test] fn override_spec_metadata_is_optional() { Package::new("bar", "0.1.0+a").publish(); let bar = git::repo(&paths::root().join("override")) .file("Cargo.toml", &basic_manifest("bar", "0.1.0+a")) .file("src/lib.rs", "pub fn bar() {}") .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "0.1.0" [replace] "bar:0.1.0" = {{ git = '{}' }} "#, bar.url() ), ) .file( "src/lib.rs", "extern crate bar; pub fn foo() { bar::bar(); }", ) .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] git repository `[ROOTURL]/override` [LOCKING] 2 packages to latest compatible versions [CHECKING] bar v0.1.0+a ([ROOTURL]/override#[..]) [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } ================================================ FILE: tests/testsuite/required_features.rs ================================================ //! Tests for targets with `required-features`. use crate::prelude::*; use cargo_test_support::install::{assert_has_installed_exe, assert_has_not_installed_exe}; use cargo_test_support::is_nightly; use cargo_test_support::paths; use cargo_test_support::project; use cargo_test_support::str; #[cargo_test] fn build_bin_default_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a"] a = [] [[bin]] name = "foo" required-features = ["a"] "#, ) .file( "src/main.rs", r#" extern crate foo; #[cfg(feature = "a")] fn test() { foo::foo(); } fn main() {} "#, ) .file("src/lib.rs", r#"#[cfg(feature = "a")] pub fn foo() {}"#) .build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); p.cargo("build --no-default-features").run(); p.cargo("build --bin=foo").run(); assert!(p.bin("foo").is_file()); p.cargo("build --bin=foo --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); } #[cargo_test] fn build_bin_arg_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] a = [] [[bin]] name = "foo" required-features = ["a"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build --features a").run(); assert!(p.bin("foo").is_file()); } #[cargo_test] fn build_bin_multiple_required_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a", "b"] a = [] b = ["a"] c = [] [[bin]] name = "foo_1" path = "src/foo_1.rs" required-features = ["b", "c"] [[bin]] name = "foo_2" path = "src/foo_2.rs" required-features = ["a"] "#, ) .file("src/foo_1.rs", "fn main() {}") .file("src/foo_2.rs", "fn main() {}") .build(); p.cargo("build").run(); assert!(!p.bin("foo_1").is_file()); assert!(p.bin("foo_2").is_file()); p.cargo("build --features c").run(); assert!(p.bin("foo_1").is_file()); assert!(p.bin("foo_2").is_file()); p.cargo("build --no-default-features").run(); } #[cargo_test] fn build_example_default_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a"] a = [] [[example]] name = "foo" required-features = ["a"] "#, ) .file("examples/foo.rs", "fn main() {}") .build(); p.cargo("build --example=foo").run(); assert!(p.bin("examples/foo").is_file()); p.cargo("build --example=foo --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); } #[cargo_test] fn build_example_arg_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] a = [] [[example]] name = "foo" required-features = ["a"] "#, ) .file("examples/foo.rs", "fn main() {}") .build(); p.cargo("build --example=foo --features a").run(); assert!(p.bin("examples/foo").is_file()); } #[cargo_test] fn build_example_multiple_required_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a", "b"] a = [] b = ["a"] c = [] [[example]] name = "foo_1" required-features = ["b", "c"] [[example]] name = "foo_2" required-features = ["a"] "#, ) .file("examples/foo_1.rs", "fn main() {}") .file("examples/foo_2.rs", "fn main() {}") .build(); p.cargo("build --example=foo_1") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo_1` in package `foo` requires the features: `b`, `c` Consider enabling them by passing, e.g., `--features="b c"` "#]]) .run(); p.cargo("build --example=foo_2").run(); assert!(!p.bin("examples/foo_1").is_file()); assert!(p.bin("examples/foo_2").is_file()); p.cargo("build --example=foo_1 --features c").run(); p.cargo("build --example=foo_2 --features c").run(); assert!(p.bin("examples/foo_1").is_file()); assert!(p.bin("examples/foo_2").is_file()); p.cargo("build --example=foo_1 --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo_1` in package `foo` requires the features: `b`, `c` Consider enabling them by passing, e.g., `--features="b c"` "#]]) .run(); p.cargo("build --example=foo_2 --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo_2` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); } #[cargo_test] fn test_default_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a"] a = [] [[test]] name = "foo" required-features = ["a"] "#, ) .file("tests/foo.rs", "#[test]\nfn test() {}") .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("test --no-default-features") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_stdout_data("") .run(); p.cargo("test --test=foo") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("test --test=foo --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); } #[cargo_test] fn test_arg_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] a = [] [[test]] name = "foo" required-features = ["a"] "#, ) .file("tests/foo.rs", "#[test]\nfn test() {}") .build(); p.cargo("test --features a") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn test_multiple_required_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a", "b"] a = [] b = ["a"] c = [] [[test]] name = "foo_1" required-features = ["b", "c"] [[test]] name = "foo_2" required-features = ["a"] "#, ) .file("tests/foo_1.rs", "#[test]\nfn test() {}") .file("tests/foo_2.rs", "#[test]\nfn test() {}") .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo_2.rs (target/debug/deps/foo_2-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("test --features c") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo_1.rs (target/debug/deps/foo_1-[HASH][EXE]) [RUNNING] tests/foo_2.rs (target/debug/deps/foo_2-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("test --no-default-features") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_stdout_data("") .run(); } #[cargo_test(nightly, reason = "bench")] fn bench_default_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a"] a = [] [[bench]] name = "foo" required-features = ["a"] "#, ) .file( "benches/foo.rs", r#" #![feature(test)] extern crate test; #[bench] fn bench(_: &mut test::Bencher) { } "#, ) .build(); p.cargo("bench") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo.rs (target/release/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("bench --no-default-features") .with_stderr_data(str![[r#" [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s "#]]) .with_stdout_data("") .run(); p.cargo("bench --bench=foo") .with_stderr_data(str![[r#" [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo.rs (target/release/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("bench --bench=foo --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); } #[cargo_test(nightly, reason = "bench")] fn bench_arg_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] a = [] [[bench]] name = "foo" required-features = ["a"] "#, ) .file( "benches/foo.rs", r#" #![feature(test)] extern crate test; #[bench] fn bench(_: &mut test::Bencher) { } "#, ) .build(); p.cargo("bench --features a") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo.rs (target/release/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "bench")] fn bench_multiple_required_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a", "b"] a = [] b = ["a"] c = [] [[bench]] name = "foo_1" required-features = ["b", "c"] [[bench]] name = "foo_2" required-features = ["a"] "#, ) .file( "benches/foo_1.rs", r#" #![feature(test)] extern crate test; #[bench] fn bench(_: &mut test::Bencher) { } "#, ) .file( "benches/foo_2.rs", r#" #![feature(test)] extern crate test; #[bench] fn bench(_: &mut test::Bencher) { } "#, ) .build(); p.cargo("bench") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo_2.rs (target/release/deps/foo_2-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("bench --features c") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo_1.rs (target/release/deps/foo_1-[HASH][EXE]) [RUNNING] benches/foo_2.rs (target/release/deps/foo_2-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("bench --no-default-features") .with_stderr_data(str![[r#" [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s "#]]) .with_stdout_data("") .run(); } #[cargo_test] fn install_default_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a"] a = [] [[bin]] name = "foo" required-features = ["a"] [[example]] name = "foo" required-features = ["a"] "#, ) .file("src/main.rs", "fn main() {}") .file("examples/foo.rs", "fn main() {}") .build(); p.cargo("install --path .").run(); assert_has_installed_exe(paths::cargo_home(), "foo"); p.cargo("uninstall foo").run(); p.cargo("install --path . --no-default-features") .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [WARNING] none of the package's binaries are available for install using the selected features bin "foo" requires the features: `a` example "foo" requires the features: `a` Consider enabling some of the needed features by passing, e.g., `--features="a"` "#]]) .run(); assert_has_not_installed_exe(paths::cargo_home(), "foo"); p.cargo("install --path . --bin=foo").run(); assert_has_installed_exe(paths::cargo_home(), "foo"); p.cargo("uninstall foo").run(); p.cargo("install --path . --bin=foo --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to compile `foo v0.0.1 ([ROOT]/foo)`, intermediate artifacts can be found at `[ROOT]/foo/target`. To reuse those artifacts with a future compilation, set the environment variable `CARGO_BUILD_BUILD_DIR` to that path. Caused by: target `foo` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); assert_has_not_installed_exe(paths::cargo_home(), "foo"); p.cargo("install --path . --example=foo").run(); assert_has_installed_exe(paths::cargo_home(), "foo"); p.cargo("uninstall foo").run(); p.cargo("install --path . --example=foo --no-default-features") .with_status(101) .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [ERROR] failed to compile `foo v0.0.1 ([ROOT]/foo)`, intermediate artifacts can be found at `[ROOT]/foo/target`. To reuse those artifacts with a future compilation, set the environment variable `CARGO_BUILD_BUILD_DIR` to that path. Caused by: target `foo` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); assert_has_not_installed_exe(paths::cargo_home(), "foo"); } #[cargo_test] fn install_arg_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] a = [] [[bin]] name = "foo" required-features = ["a"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("install --features a").run(); assert_has_installed_exe(paths::cargo_home(), "foo"); p.cargo("uninstall foo").run(); } #[cargo_test] fn install_multiple_required_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a", "b"] a = [] b = ["a"] c = [] [[bin]] name = "foo_1" path = "src/foo_1.rs" required-features = ["b", "c"] [[bin]] name = "foo_2" path = "src/foo_2.rs" required-features = ["a"] [[example]] name = "foo_3" path = "src/foo_3.rs" required-features = ["b", "c"] [[example]] name = "foo_4" path = "src/foo_4.rs" required-features = ["a"] "#, ) .file("src/foo_1.rs", "fn main() {}") .file("src/foo_2.rs", "fn main() {}") .file("src/foo_3.rs", "fn main() {}") .file("src/foo_4.rs", "fn main() {}") .build(); p.cargo("install --path .").run(); assert_has_not_installed_exe(paths::cargo_home(), "foo_1"); assert_has_installed_exe(paths::cargo_home(), "foo_2"); assert_has_not_installed_exe(paths::cargo_home(), "foo_3"); assert_has_not_installed_exe(paths::cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); p.cargo("install --path . --bins --examples").run(); assert_has_not_installed_exe(paths::cargo_home(), "foo_1"); assert_has_installed_exe(paths::cargo_home(), "foo_2"); assert_has_not_installed_exe(paths::cargo_home(), "foo_3"); assert_has_installed_exe(paths::cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); p.cargo("install --path . --features c").run(); assert_has_installed_exe(paths::cargo_home(), "foo_1"); assert_has_installed_exe(paths::cargo_home(), "foo_2"); assert_has_not_installed_exe(paths::cargo_home(), "foo_3"); assert_has_not_installed_exe(paths::cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); p.cargo("install --path . --features c --bins --examples") .run(); assert_has_installed_exe(paths::cargo_home(), "foo_1"); assert_has_installed_exe(paths::cargo_home(), "foo_2"); assert_has_installed_exe(paths::cargo_home(), "foo_3"); assert_has_installed_exe(paths::cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); p.cargo("install --path . --no-default-features") .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [WARNING] none of the package's binaries are available for install using the selected features bin "foo_1" requires the features: `b`, `c` bin "foo_2" requires the features: `a` example "foo_3" requires the features: `b`, `c` example "foo_4" requires the features: `a` Consider enabling some of the needed features by passing, e.g., `--features="b c"` "#]]) .run(); p.cargo("install --path . --no-default-features --bins") .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [WARNING] target filter `bins` specified, but no targets matched; this is a no-op [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [WARNING] none of the package's binaries are available for install using the selected features bin "foo_1" requires the features: `b`, `c` bin "foo_2" requires the features: `a` example "foo_3" requires the features: `b`, `c` example "foo_4" requires the features: `a` Consider enabling some of the needed features by passing, e.g., `--features="b c"` "#]]) .run(); p.cargo("install --path . --no-default-features --examples") .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [WARNING] target filter `examples` specified, but no targets matched; this is a no-op [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [WARNING] none of the package's binaries are available for install using the selected features bin "foo_1" requires the features: `b`, `c` bin "foo_2" requires the features: `a` example "foo_3" requires the features: `b`, `c` example "foo_4" requires the features: `a` Consider enabling some of the needed features by passing, e.g., `--features="b c"` "#]]) .run(); p.cargo("install --path . --no-default-features --bins --examples") .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [WARNING] target filters `bins`, `examples` specified, but no targets matched; this is a no-op [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [WARNING] none of the package's binaries are available for install using the selected features bin "foo_1" requires the features: `b`, `c` bin "foo_2" requires the features: `a` example "foo_3" requires the features: `b`, `c` example "foo_4" requires the features: `a` Consider enabling some of the needed features by passing, e.g., `--features="b c"` "#]]) .run(); assert_has_not_installed_exe(paths::cargo_home(), "foo_1"); assert_has_not_installed_exe(paths::cargo_home(), "foo_2"); assert_has_not_installed_exe(paths::cargo_home(), "foo_3"); assert_has_not_installed_exe(paths::cargo_home(), "foo_4"); } #[cargo_test] fn dep_feature_in_toml() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = { path = "bar", features = ["a"] } [[bin]] name = "foo" required-features = ["bar/a"] [[example]] name = "foo" required-features = ["bar/a"] [[test]] name = "foo" required-features = ["bar/a"] [[bench]] name = "foo" required-features = ["bar/a"] "#, ) .file("src/main.rs", "fn main() {}") .file("examples/foo.rs", "fn main() {}") .file("tests/foo.rs", "#[test]\nfn test() {}") .file( "benches/foo.rs", r#" #![feature(test)] extern crate test; #[bench] fn bench(_: &mut test::Bencher) { } "#, ) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [features] a = [] "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("build").run(); // bin p.cargo("build --bin=foo").run(); assert!(p.bin("foo").is_file()); // example p.cargo("build --example=foo").run(); assert!(p.bin("examples/foo").is_file()); // test p.cargo("test --test=foo") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); // bench if is_nightly() { p.cargo("bench --bench=foo") .with_stderr_data(str![[r#" [COMPILING] bar v0.0.1 ([ROOT]/foo/bar) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo.rs (target/release/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); } // install p.cargo("install").run(); assert_has_installed_exe(paths::cargo_home(), "foo"); p.cargo("uninstall foo").run(); } #[cargo_test] fn dep_feature_in_cmd_line() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = { path = "bar" } [[bin]] name = "foo" required-features = ["bar/a"] [[example]] name = "foo" required-features = ["bar/a"] [[test]] name = "foo" required-features = ["bar/a"] [[bench]] name = "foo" required-features = ["bar/a"] "#, ) .file("src/main.rs", "fn main() {}") .file("examples/foo.rs", "fn main() {}") .file( "tests/foo.rs", r#" #[test] fn bin_is_built() { let s = format!("target/debug/foo{}", std::env::consts::EXE_SUFFIX); let p = std::path::Path::new(&s); assert!(p.exists(), "foo does not exist"); } "#, ) .file( "benches/foo.rs", r#" #![feature(test)] extern crate test; #[bench] fn bench(_: &mut test::Bencher) { } "#, ) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [features] a = [] "#, ) .file("bar/src/lib.rs", "") .build(); // This is a no-op p.cargo("build") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); assert!(!p.bin("foo").is_file()); // bin p.cargo("build --bin=foo") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo` in package `foo` requires the features: `bar/a` Consider enabling them by passing, e.g., `--features="bar/a"` "#]]) .run(); p.cargo("build --bin=foo --features bar/a").run(); assert!(p.bin("foo").is_file()); // example p.cargo("build --example=foo") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo` in package `foo` requires the features: `bar/a` Consider enabling them by passing, e.g., `--features="bar/a"` "#]]) .run(); p.cargo("build --example=foo --features bar/a").run(); assert!(p.bin("examples/foo").is_file()); // test // This is a no-op, since no tests are enabled p.cargo("test") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_stdout_data("") .run(); // Delete the target directory so this can check if the main.rs gets built. p.build_dir().rm_rf(); p.cargo("test --test=foo --features bar/a") .with_stderr_data(str![[r#" [COMPILING] bar v0.0.1 ([ROOT]/foo/bar) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bin_is_built ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); // bench if is_nightly() { p.cargo("bench") .with_stderr_data(str![[r#" [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s "#]]) .with_stdout_data("") .run(); p.cargo("bench --bench=foo --features bar/a") .with_stderr_data(str![[r#" [COMPILING] bar v0.0.1 ([ROOT]/foo/bar) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo.rs (target/release/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test bench ... bench: [AVG_ELAPSED] ns/iter (+/- [JITTER]) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); } // install p.cargo("install --path .") .with_stderr_data(str![[r#" [INSTALLING] foo v0.0.1 ([ROOT]/foo) [LOCKING] 1 package to latest compatible version [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [WARNING] none of the package's binaries are available for install using the selected features bin "foo" requires the features: `bar/a` example "foo" requires the features: `bar/a` Consider enabling some of the needed features by passing, e.g., `--features="bar/a"` "#]]) .run(); assert_has_not_installed_exe(paths::cargo_home(), "foo"); p.cargo("install --features bar/a").run(); assert_has_installed_exe(paths::cargo_home(), "foo"); p.cargo("uninstall foo").run(); } #[cargo_test] fn test_skips_compiling_bin_with_missing_required_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] a = [] [[bin]] name = "bin_foo" path = "src/bin/foo.rs" required-features = ["a"] "#, ) .file("src/bin/foo.rs", "extern crate bar; fn main() {}") .file("tests/foo.rs", "") .file("benches/foo.rs", "") .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("test --features a -j 1") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) error[E0463]: can't find crate for `bar` ... "#]]) .run(); if is_nightly() { p.cargo("bench") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] benches/foo.rs (target/release/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("bench --features a -j 1") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) error[E0463]: can't find crate for `bar` ... "#]]) .run(); } } #[cargo_test] fn run_default() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = [] a = [] [[bin]] name = "foo" required-features = ["a"] "#, ) .file("src/lib.rs", "") .file("src/main.rs", "extern crate foo; fn main() {}") .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] target `foo` in package `foo` requires the features: `a` Consider enabling them by passing, e.g., `--features="a"` "#]]) .run(); p.cargo("run --features a").run(); } #[cargo_test] fn run_default_multiple_required_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] default = ["a"] a = [] b = [] [[bin]] name = "foo1" path = "src/foo1.rs" required-features = ["a"] [[bin]] name = "foo3" path = "src/foo3.rs" required-features = ["b"] [[bin]] name = "foo2" path = "src/foo2.rs" required-features = ["b"] "#, ) .file("src/lib.rs", "") .file("src/foo1.rs", "extern crate foo; fn main() {}") .file("src/foo3.rs", "extern crate foo; fn main() {}") .file("src/foo2.rs", "extern crate foo; fn main() {}") .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key. available binaries: foo1, foo2, foo3 "#]]) .run(); } #[cargo_test] fn renamed_required_features() { // Test that required-features uses renamed package feature names. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [[bin]] name = "x" required-features = ["a1/f1"] [dependencies] a1 = {path="a1", package="a"} a2 = {path="a2", package="a"} "#, ) .file( "src/bin/x.rs", r#" fn main() { a1::f(); a2::f(); } "#, ) .file( "a1/Cargo.toml", r#" [package] name = "a" version = "0.1.0" [features] f1 = [] "#, ) .file( "a1/src/lib.rs", r#" pub fn f() { if cfg!(feature="f1") { println!("a1 f1"); } } "#, ) .file( "a2/Cargo.toml", r#" [package] name = "a" version = "0.2.0" [features] f2 = [] "#, ) .file( "a2/src/lib.rs", r#" pub fn f() { if cfg!(feature="f2") { println!("a2 f2"); } } "#, ) .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [LOCKING] 2 packages to latest compatible versions [ERROR] target `x` in package `foo` requires the features: `a1/f1` Consider enabling them by passing, e.g., `--features="a1/f1"` "#]]) .run(); p.cargo("build --features a1/f1").run(); p.rename_run("x", "x_with_f1") .with_stdout_data(str![[r#" a1 f1 "#]]) .run(); p.cargo("build --features a1/f1,a2/f2").run(); p.rename_run("x", "x_with_f1_f2") .with_stdout_data(str![[r#" a1 f1 a2 f2 "#]]) .run(); } #[cargo_test] fn truncated_install_warning_message() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2021" [features] feature1 = [] feature2 = [] feature3 = [] feature4 = [] feature5 = [] [[bin]] name = "foo1" required-features = ["feature1", "feature2", "feature3"] [[bin]] name = "foo2" required-features = ["feature2"] [[bin]] name = "foo3" required-features = ["feature3"] [[bin]] name = "foo4" required-features = ["feature4", "feature1"] [[bin]] name = "foo5" required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] [[bin]] name = "foo6" required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] [[bin]] name = "foo7" required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] [[bin]] name = "foo8" required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] [[bin]] name = "foo9" required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] [[bin]] name = "foo10" required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] [[example]] name = "example1" required-features = ["feature1", "feature2"] "#, ) .file("src/bin/foo1.rs", "fn main() {}") .file("src/bin/foo2.rs", "fn main() {}") .file("src/bin/foo3.rs", "fn main() {}") .file("src/bin/foo4.rs", "fn main() {}") .file("src/bin/foo5.rs", "fn main() {}") .file("src/bin/foo6.rs", "fn main() {}") .file("src/bin/foo7.rs", "fn main() {}") .file("src/bin/foo8.rs", "fn main() {}") .file("src/bin/foo9.rs", "fn main() {}") .file("src/bin/foo10.rs", "fn main() {}") .file("examples/example1.rs", "fn main() {}") .build(); p.cargo("install --path .").with_stderr_data(str![[r#" [INSTALLING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [WARNING] none of the package's binaries are available for install using the selected features bin "foo1" requires the features: `feature1`, `feature2`, `feature3` bin "foo10" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5` bin "foo2" requires the features: `feature2` bin "foo3" requires the features: `feature3` bin "foo4" requires the features: `feature4`, `feature1` bin "foo5" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5` bin "foo6" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5` 4 more targets also requires features not enabled. See them in the Cargo.toml file. Consider enabling some of the needed features by passing, e.g., `--features="feature1 feature2 feature3"` "#]]).run(); } ================================================ FILE: tests/testsuite/run.rs ================================================ //! Tests for the `cargo run` command. use crate::prelude::*; use cargo_test_support::{ Project, basic_bin_manifest, basic_lib_manifest, basic_manifest, project, str, }; use cargo_util::paths::dylib_path_envvar; #[cargo_test] fn simple() { let p = project() .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]]) .with_stdout_data(str![[r#" hello "#]]) .run(); assert!(p.bin("foo").is_file()); } #[cargo_test] fn quiet_arg() { let p = project() .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run -q") .with_stderr_data("") .with_stdout_data(str![[r#" hello "#]]) .run(); p.cargo("run --quiet") .with_stderr_data("") .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn unsupported_silent_arg() { let p = project() .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run -s") .with_stderr_data(str![[r#" [ERROR] unexpected argument '--silent' found tip: a similar argument exists: '--quiet' Usage: cargo[EXE] run [OPTIONS] [ARGS]... For more information, try '--help'. "#]]) .with_status(1) .run(); p.cargo("run --silent") .with_stderr_data(str![[r#" [ERROR] unexpected argument '--silent' found tip: a similar argument exists: '--quiet' Usage: cargo[EXE] run [OPTIONS] [ARGS]... For more information, try '--help'. "#]]) .with_status(1) .run(); } #[cargo_test] fn quiet_arg_and_verbose_arg() { let p = project() .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run -q -v") .with_status(101) .with_stderr_data(str![[r#" [ERROR] cannot set both --verbose and --quiet "#]]) .run(); } #[cargo_test] fn quiet_arg_and_verbose_config() { let p = project() .file( ".cargo/config.toml", r#" [term] verbose = true "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run -q") .with_stderr_data("") .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn verbose_arg_and_quiet_config() { let p = project() .file( ".cargo/config.toml", r#" [term] quiet = true "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]]) .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn quiet_config_alone() { let p = project() .file( ".cargo/config.toml", r#" [term] quiet = true "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run") .with_stderr_data("") .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn verbose_config_alone() { let p = project() .file( ".cargo/config.toml", r#" [term] verbose = true "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]]) .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn quiet_config_and_verbose_config() { let p = project() .file( ".cargo/config.toml", r#" [term] verbose = true quiet = true "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] cannot set both `term.verbose` and `term.quiet` "#]]) .run(); } #[cargo_test] fn simple_with_args() { let p = project() .file( "src/main.rs", r#" fn main() { assert_eq!(std::env::args().nth(1).unwrap(), "hello"); assert_eq!(std::env::args().nth(2).unwrap(), "world"); } "#, ) .build(); p.cargo("run hello world").run(); } #[cfg(unix)] #[cargo_test] fn simple_with_non_utf8_args() { use std::os::unix::ffi::OsStrExt; let p = project() .file( "src/main.rs", r#" use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; fn main() { assert_eq!(std::env::args_os().nth(1).unwrap(), OsStr::from_bytes(b"hello")); assert_eq!(std::env::args_os().nth(2).unwrap(), OsStr::from_bytes(b"ab\xffcd")); } "#, ) .build(); p.cargo("run") .arg("hello") .arg(std::ffi::OsStr::from_bytes(b"ab\xFFcd")) .run(); } #[cargo_test] fn exit_code() { let p = project() .file("src/main.rs", "fn main() { std::process::exit(2); }") .build(); let expected = if !cfg!(unix) { str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` [ERROR] process didn't exit successfully: `target/debug/foo[EXE]` ([EXIT_STATUS]: 2) "#]] } else { str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo` "#]] }; p.cargo("run") .with_status(2) .with_stderr_data(expected) .run(); } #[cargo_test] fn exit_code_verbose() { let p = project() .file("src/main.rs", "fn main() { std::process::exit(2); }") .build(); let expected = if !cfg!(unix) { str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` [ERROR] process didn't exit successfully: `target/debug/foo[EXE]` ([EXIT_STATUS]: 2) "#]] } else { str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]] }; p.cargo("run -v") .with_status(2) .with_stderr_data(expected) .run(); } #[cargo_test] fn no_main_file() { let p = project().file("src/lib.rs", "").build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] a bin target must be available for `cargo run` "#]]) .run(); } #[cargo_test] fn too_many_bins() { let p = project() .file("src/lib.rs", "") .file("src/bin/a.rs", "") .file("src/bin/b.rs", "") .build(); // Using [..] here because the order is not stable p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key. available binaries: a, b "#]]) .run(); } #[cargo_test] fn specify_name() { let p = project() .file("src/lib.rs", "") .file( "src/bin/a.rs", r#" #[allow(unused_extern_crates)] extern crate foo; fn main() { println!("hello a.rs"); } "#, ) .file( "src/bin/b.rs", r#" #[allow(unused_extern_crates)] extern crate foo; fn main() { println!("hello b.rs"); } "#, ) .build(); p.cargo("run --bin a -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] src/lib.rs [..]` [RUNNING] `rustc [..] src/bin/a.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/a[EXE]` "#]]) .with_stdout_data(str![[r#" hello a.rs "#]]) .run(); p.cargo("run --bin b -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] src/bin/b.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/b[EXE]` "#]]) .with_stdout_data(str![[r#" hello b.rs "#]]) .run(); } #[cargo_test] fn specify_default_run() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] default-run = "a" "#, ) .file("src/lib.rs", "") .file("src/bin/a.rs", r#"fn main() { println!("hello A"); }"#) .file("src/bin/b.rs", r#"fn main() { println!("hello B"); }"#) .build(); p.cargo("run") .with_stdout_data(str![[r#" hello A "#]]) .run(); p.cargo("run --bin a") .with_stdout_data(str![[r#" hello A "#]]) .run(); p.cargo("run --bin b") .with_stdout_data(str![[r#" hello B "#]]) .run(); } #[cargo_test] fn bogus_default_run() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] default-run = "b" "#, ) .file("src/lib.rs", "") .file("src/bin/a.rs", r#"fn main() { println!("hello A"); }"#) .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: default-run target `b` not found [HELP] a target with a similar name exists: `a` "#]]) .run(); } #[cargo_test] fn run_example() { let p = project() .file("src/lib.rs", "") .file("examples/a.rs", r#"fn main() { println!("example"); }"#) .file("src/bin/a.rs", r#"fn main() { println!("bin"); }"#) .build(); p.cargo("run --example a") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/examples/a[EXE]` "#]]) .with_stdout_data(str![[r#" example "#]]) .run(); } #[cargo_test] fn run_library_example() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[example]] name = "bar" crate-type = ["lib"] "#, ) .file("src/lib.rs", "") .file("examples/bar.rs", "fn foo() {}") .build(); p.cargo("run --example bar") .with_status(101) .with_stderr_data(str![[r#" [ERROR] example target `bar` is a library and cannot be executed "#]]) .run(); } #[cargo_test] fn run_bin_example() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [[example]] name = "bar" crate-type = ["bin"] "#, ) .file("src/lib.rs", "") .file("examples/bar.rs", r#"fn main() { println!("example"); }"#) .build(); p.cargo("run --example bar") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/examples/bar[EXE]` "#]]) .with_stdout_data(str![[r#" example "#]]) .run(); } fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option) -> Project { let autoexamples = match autoexamples { None => "".to_string(), Some(bool) => format!("autoexamples = {}", bool), }; project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" authors = [] edition = "{rust_edition}" {autoexamples} [features] magic = [] [[example]] name = "do_magic" required-features = ["magic"] "#, rust_edition = rust_edition, autoexamples = autoexamples ), ) .file("examples/a.rs", r#"fn main() { println!("example"); }"#) .file( "examples/do_magic.rs", r#" fn main() { println!("magic example"); } "#, ) .build() } #[cargo_test] fn run_example_autodiscover_2015() { let p = autodiscover_examples_project("2015", None); p.cargo("run --example a") .with_status(101) .with_stderr_data(str![[r#" [WARNING] An explicit [[example]] section is specified in Cargo.toml which currently disables Cargo from automatically inferring other example targets. This inference behavior will change in the Rust 2018 edition and the following files will be included as a example target: * examples/a.rs This is likely to break cargo build or cargo test as these files may not be ready to be compiled as a example target today. You can future-proof yourself and disable this warning by adding `autoexamples = false` to your [package] section. You may also move the files to a location where Cargo would not automatically infer them to be a target, such as in subfolders. For more information on this warning you can consult https://github.com/rust-lang/cargo/issues/5330 [ERROR] no example target named `a` in default-run packages [HELP] available example targets: do_magic "#]]) .run(); } #[cargo_test] fn run_example_autodiscover_2015_with_autoexamples_enabled() { let p = autodiscover_examples_project("2015", Some(true)); p.cargo("run --example a") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/examples/a[EXE]` "#]]) .with_stdout_data(str![[r#" example "#]]) .run(); } #[cargo_test] fn run_example_autodiscover_2015_with_autoexamples_disabled() { let p = autodiscover_examples_project("2015", Some(false)); p.cargo("run --example a") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no example target named `a` in default-run packages [HELP] available example targets: do_magic "#]]) .run(); } #[cargo_test] fn run_example_autodiscover_2018() { let p = autodiscover_examples_project("2018", None); p.cargo("run --example a") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/examples/a[EXE]` "#]]) .with_stdout_data(str![[r#" example "#]]) .run(); } #[cargo_test] fn autobins_disables() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" autobins = false "#, ) .file("src/lib.rs", "pub mod bin;") .file("src/bin/mod.rs", "// empty") .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] a bin target must be available for `cargo run` "#]]) .run(); } #[cargo_test] fn run_bins() { let p = project() .file("src/lib.rs", "") .file("examples/a.rs", r#"fn main() { println!("example"); }"#) .file("src/bin/a.rs", r#"fn main() { println!("bin"); }"#) .build(); p.cargo("run --bins") .with_status(1) .with_stderr_data(str![[r#" [ERROR] unexpected argument '--bins' found tip: a similar argument exists: '--bin' ... "#]]) .run(); } #[cargo_test] fn run_with_filename() { let p = project() .file("src/lib.rs", "") .file( "src/bin/a.rs", r#" extern crate foo; fn main() { println!("hello a.rs"); } "#, ) .file("examples/a.rs", r#"fn main() { println!("example"); }"#) .build(); p.cargo("run --bin bin.rs") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `bin.rs` in default-run packages [HELP] available bin targets: a "#]]) .run(); p.cargo("run --bin a.rs") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `a.rs` in default-run packages [HELP] a target with a similar name exists: `a` "#]]) .run(); p.cargo("run --example example.rs") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no example target named `example.rs` in default-run packages [HELP] available example targets: a "#]]) .run(); p.cargo("run --example a.rs") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no example target named `a.rs` in default-run packages [HELP] a target with a similar name exists: `a` "#]]) .run(); } #[cargo_test] fn ambiguous_bin_name() { let p = project() .file( "Cargo.toml", r#" [workspace] resolver = "3" members = ["crate1", "crate2", "crate3", "crate4"] "#, ) .file("crate1/src/bin/ambiguous.rs", "fn main(){}") .file( "crate1/Cargo.toml", r#" [package] name = "crate1" version = "0.1.0" edition = "2024" "#, ) .file("crate2/src/bin/ambiguous.rs", "fn main(){}") .file( "crate2/Cargo.toml", r#" [package] name = "crate2" version = "0.1.0" edition = "2024" "#, ) .file("crate3/src/bin/ambiguous.rs", "fn main(){}") .file( "crate3/Cargo.toml", r#" [package] name = "crate3" version = "0.1.0" edition = "2024" "#, ) .file("crate4/src/bin/ambiguous.rs", "fn main(){}") .file( "crate4/Cargo.toml", r#" [package] name = "crate4" version = "0.1.0" edition = "2024" "#, ); let p = p.build(); p.cargo("run --bin ambiguous") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` can run at most one executable, but multiple were specified [HELP] available targets: bin `ambiguous` in package `crate1` bin `ambiguous` in package `crate2` bin `ambiguous` in package `crate3` bin `ambiguous` in package `crate4` "#]]) .run(); p.cargo("run --bin crate1/ambiguous") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `crate1/ambiguous` in default-run packages [HELP] available bin targets: ambiguous in package crate1 ambiguous in package crate2 ambiguous in package crate3 ambiguous in package crate4 "#]]) .run(); } // See rust-lang/cargo#14544 #[cargo_test] fn print_available_targets_within_virtual_workspace() { let p = project() .file( "Cargo.toml", r#" [workspace] resolver = "3" members = ["crate1", "crate2", "pattern1", "pattern2"] default-members = ["crate1"] "#, ) .file("crate1/src/main.rs", "fn main(){}") .file( "crate1/Cargo.toml", r#" [package] name = "crate1" version = "0.1.0" edition = "2024" "#, ) .file("crate2/src/main.rs", "fn main(){}") .file( "crate2/Cargo.toml", r#" [package] name = "crate2" version = "0.1.0" edition = "2024" "#, ) .file("pattern1/src/main.rs", "fn main(){}") .file( "pattern1/Cargo.toml", r#" [package] name = "pattern1" version = "0.1.0" edition = "2024" "#, ) .file("pattern2/src/main.rs", "fn main(){}") .file( "pattern2/Cargo.toml", r#" [package] name = "pattern2" version = "0.1.0" edition = "2024" "#, ) .file("another/src/main.rs", "fn main(){}") .file( "another/Cargo.toml", r#" [package] name = "another" version = "0.1.0" edition = "2024" "#, ); let p = p.build(); p.cargo("run --bin") .with_status(101) .with_stderr_data(str![[r#" [ERROR] "--bin" takes one argument. Available binaries: crate1 "#]]) .run(); p.cargo("run -p crate1 --bin crate2") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `crate2` in `crate1` package [HELP] a target with a similar name exists: `crate1` [HELP] available bin in `crate2` package: crate2 "#]]) .run(); p.cargo("check -p crate1 -p pattern1 -p pattern2 --bin crate2") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `crate2` in `crate1`, ... packages [HELP] a target with a similar name exists: `crate1` [HELP] available bin in `crate2` package: crate2 "#]]) .run(); p.cargo("run --bin crate2") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `crate2` in default-run packages [HELP] a target with a similar name exists: `crate1` [HELP] available bin in `crate2` package: crate2 "#]]) .run(); p.cargo("check --bin pattern*") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target matches pattern `pattern*` in default-run packages [HELP] available bin in `pattern1` package: pattern1 [HELP] available bin in `pattern2` package: pattern2 "#]]) .run(); // This another branch that none of similar name exists, and print available targets in the // default-members. p.change_file( "Cargo.toml", r#" [workspace] resolver = "3" members = ["crate1", "crate2", "another"] default-members = ["another"] "#, ); p.cargo("run --bin crate2") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `crate2` in default-run packages [HELP] available bin in `crate2` package: crate2 "#]]) .run(); } #[cargo_test] fn either_name_or_example() { let p = project() .file("src/bin/a.rs", r#"fn main() { println!("hello a.rs"); }"#) .file("examples/b.rs", r#"fn main() { println!("hello b.rs"); }"#) .build(); p.cargo("run --bin a --example b") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` can run at most one executable, but multiple were specified [HELP] available targets: bin `a` in package `foo` example `b` in package `foo` "#]]) .run(); } #[cargo_test] fn one_bin_multiple_examples() { let p = project() .file("src/lib.rs", "") .file( "src/bin/main.rs", r#"fn main() { println!("hello main.rs"); }"#, ) .file("examples/a.rs", r#"fn main() { println!("hello a.rs"); }"#) .file("examples/b.rs", r#"fn main() { println!("hello b.rs"); }"#) .build(); p.cargo("run") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/main[EXE]` "#]]) .with_stdout_data(str![[r#" hello main.rs "#]]) .run(); } #[cargo_test] fn example_with_release_flag() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] version = "*" path = "bar" "#, ) .file( "examples/a.rs", r#" extern crate bar; fn main() { if cfg!(debug_assertions) { println!("slow1") } else { println!("fast1") } bar::baz(); } "#, ) .file("bar/Cargo.toml", &basic_lib_manifest("bar")) .file( "bar/src/bar.rs", r#" pub fn baz() { if cfg!(debug_assertions) { println!("slow2") } else { println!("fast2") } } "#, ) .build(); p.cargo("run -v --release --example a") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] bar v0.5.0 ([ROOT]/foo/bar) [RUNNING] `rustc --crate-name bar --edition=2015 bar/src/bar.rs [..]--crate-type lib --emit=[..]link -C opt-level=3[..] -C metadata=[..] --out-dir [ROOT]/foo/target/release/deps -C strip=debuginfo -L dependency=[ROOT]/foo/target/release/deps` [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name a --edition=2015 examples/a.rs [..]--crate-type bin --emit=[..]link -C opt-level=3[..] -C metadata=[..] --out-dir [ROOT]/foo/target/release/examples -C strip=debuginfo -L dependency=[ROOT]/foo/target/release/deps --extern bar=[ROOT]/foo/target/release/deps/libbar-[HASH].rlib` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [RUNNING] `target/release/examples/a[EXE]` "#]]) .with_stdout_data(str![[r#" fast1 fast2 "#]]) .run(); p.cargo("run -v --example a") .with_stderr_data(str![[r#" [COMPILING] bar v0.5.0 ([ROOT]/foo/bar) [RUNNING] `rustc --crate-name bar --edition=2015 bar/src/bar.rs [..]--crate-type lib --emit=[..]link [..]-C debuginfo=2 [..]-C metadata=[..] --out-dir [ROOT]/foo/target/debug/deps -L dependency=[ROOT]/foo/target/debug/deps` [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name a --edition=2015 examples/a.rs [..]--crate-type bin --emit=[..]link [..]-C debuginfo=2 [..]-C metadata=[..] --out-dir [ROOT]/foo/target/debug/examples -L dependency=[ROOT]/foo/target/debug/deps --extern bar=[ROOT]/foo/target/debug/deps/libbar-[HASH].rlib` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/examples/a[EXE]` "#]]) .with_stdout_data(str![[r#" slow1 slow2 "#]]) .run(); } #[cargo_test] fn run_dylib_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] path = "bar" "#, ) .file( "src/main.rs", r#"extern crate bar; fn main() { bar::bar(); }"#, ) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" authors = [] [lib] name = "bar" crate-type = ["dylib"] "#, ) .file("bar/src/lib.rs", "pub fn bar() {}") .build(); p.cargo("run hello world").run(); } #[cargo_test] fn run_with_bin_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies.bar] path = "bar" "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "bar" "#, ) .file("bar/src/main.rs", r#"fn main() { println!("bar"); }"#) .build(); p.cargo("run") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [WARNING] foo v0.0.1 ([ROOT]/foo) ignoring invalid dependency `bar` which is missing a lib target [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]]) .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn run_with_bin_deps() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies.bar1] path = "bar1" [dependencies.bar2] path = "bar2" "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .file( "bar1/Cargo.toml", r#" [package] name = "bar1" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "bar1" "#, ) .file("bar1/src/main.rs", r#"fn main() { println!("bar1"); }"#) .file( "bar2/Cargo.toml", r#" [package] name = "bar2" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "bar2" "#, ) .file("bar2/src/main.rs", r#"fn main() { println!("bar2"); }"#) .build(); p.cargo("run") .with_stderr_data(str![[r#" [LOCKING] 2 packages to latest compatible versions [WARNING] foo v0.0.1 ([ROOT]/foo) ignoring invalid dependency `bar1` which is missing a lib target [WARNING] foo v0.0.1 ([ROOT]/foo) ignoring invalid dependency `bar2` which is missing a lib target [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]]) .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn run_with_bin_dep_in_workspace() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo1", "foo2"] "#, ) .file( "foo1/Cargo.toml", r#" [package] name = "foo1" version = "0.0.1" edition = "2015" [dependencies.bar1] path = "bar1" "#, ) .file("foo1/src/main.rs", r#"fn main() { println!("hello"); }"#) .file( "foo1/bar1/Cargo.toml", r#" [package] name = "bar1" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "bar1" "#, ) .file( "foo1/bar1/src/main.rs", r#"fn main() { println!("bar1"); }"#, ) .file( "foo2/Cargo.toml", r#" [package] name = "foo2" version = "0.0.1" edition = "2015" [dependencies.bar2] path = "bar2" "#, ) .file("foo2/src/main.rs", r#"fn main() { println!("hello"); }"#) .file( "foo2/bar2/Cargo.toml", r#" [package] name = "bar2" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "bar2" "#, ) .file( "foo2/bar2/src/main.rs", r#"fn main() { println!("bar2"); }"#, ) .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key. available binaries: bar1, bar2, foo1, foo2 "#]]) .run(); p.cargo("run --bin foo1") .with_stderr_data(str![[r#" [WARNING] foo1 v0.0.1 ([ROOT]/foo/foo1) ignoring invalid dependency `bar1` which is missing a lib target [WARNING] foo2 v0.0.1 ([ROOT]/foo/foo2) ignoring invalid dependency `bar2` which is missing a lib target [COMPILING] foo1 v0.0.1 ([ROOT]/foo/foo1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo1[EXE]` "#]]) .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn release_works() { let p = project() .file( "src/main.rs", r#" fn main() { if cfg!(debug_assertions) { panic!() } } "#, ) .build(); p.cargo("run --release") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [RUNNING] `target/release/foo[EXE]` "#]]) .run(); assert!(p.release_bin("foo").is_file()); } #[cargo_test] fn release_short_works() { let p = project() .file( "src/main.rs", r#" fn main() { if cfg!(debug_assertions) { panic!() } } "#, ) .build(); p.cargo("run -r") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [RUNNING] `target/release/foo[EXE]` "#]]) .run(); assert!(p.release_bin("foo").is_file()); } #[cargo_test] fn run_bin_different_name() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "bar" "#, ) .file("src/bar.rs", "fn main() {}") .build(); p.cargo("run").run(); } #[cargo_test] fn dashes_are_forwarded() { let p = project() .file( "src/bin/bar.rs", r#" fn main() { let s: Vec = std::env::args().collect(); assert_eq!(s[1], "--"); assert_eq!(s[2], "a"); assert_eq!(s[3], "--"); assert_eq!(s[4], "b"); } "#, ) .build(); p.cargo("run -- -- a -- b").run(); } #[cargo_test] fn run_from_executable_folder() { let p = project() .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); let cwd = p.root().join("target").join("debug"); p.cargo("build").run(); p.cargo("run") .cwd(cwd) .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `./foo[EXE]` "#]]) .with_stdout_data(str![[r#" hello "#]]) .run(); } #[cargo_test] fn run_with_library_paths() { let p = project(); // Only link search directories within the target output directory are // propagated through to dylib_path_envvar() (see #3366). let mut dir1 = p.target_debug_dir(); dir1.push("foo\\backslash"); let mut dir2 = p.target_debug_dir(); dir2.push("dir=containing=equal=signs"); let p = p .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] build = "build.rs" "#, ) .file( "build.rs", &format!( r##" fn main() {{ println!(r#"cargo::rustc-link-search=native={}"#); println!(r#"cargo::rustc-link-search={}"#); }} "##, dir1.display(), dir2.display() ), ) .file( "src/main.rs", &format!( r##" fn main() {{ let search_path = std::env::var_os("{}").unwrap(); let paths = std::env::split_paths(&search_path).collect::>(); println!("{{:#?}}", paths); assert!(paths.contains(&r#"{}"#.into())); assert!(paths.contains(&r#"{}"#.into())); }} "##, dylib_path_envvar(), dir1.display(), dir2.display() ), ) .build(); p.cargo("run").run(); } #[cargo_test] fn library_paths_sorted_alphabetically() { let p = project(); let mut dir1 = p.target_debug_dir(); dir1.push("zzzzzzz"); let mut dir2 = p.target_debug_dir(); dir2.push("BBBBBBB"); let mut dir3 = p.target_debug_dir(); dir3.push("aaaaaaa"); let p = p .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] build = "build.rs" "#, ) .file( "build.rs", &format!( r##" fn main() {{ println!(r#"cargo::rustc-link-search=native={}"#); println!(r#"cargo::rustc-link-search=native={}"#); println!(r#"cargo::rustc-link-search=native={}"#); }} "##, dir1.display(), dir2.display(), dir3.display() ), ) .file( "src/main.rs", &format!( r##" fn main() {{ let search_path = std::env::var_os("{}").unwrap(); let paths = std::env::split_paths(&search_path).collect::>(); // ASCII case-sensitive sort assert_eq!("BBBBBBB", paths[0].file_name().unwrap().to_string_lossy()); assert_eq!("aaaaaaa", paths[1].file_name().unwrap().to_string_lossy()); assert_eq!("zzzzzzz", paths[2].file_name().unwrap().to_string_lossy()); }} "##, dylib_path_envvar() ), ) .build(); p.cargo("run").run(); } #[cargo_test] fn fail_no_extra_verbose() { let p = project() .file("src/main.rs", "fn main() { std::process::exit(1); }") .build(); p.cargo("run -q") .with_status(1) .with_stdout_data("") .with_stderr_data("") .run(); } #[cargo_test] fn run_multiple_packages() { let p = project() .no_manifest() .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [workspace] [dependencies] d1 = { path = "d1" } d2 = { path = "d2" } d3 = { path = "../d3" } # outside of the workspace [[bin]] name = "foo" "#, ) .file("foo/src/foo.rs", "fn main() { println!(\"foo\"); }") .file("foo/d1/Cargo.toml", &basic_bin_manifest("d1")) .file("foo/d1/src/lib.rs", "") .file("foo/d1/src/main.rs", "fn main() { println!(\"d1\"); }") .file("foo/d2/Cargo.toml", &basic_bin_manifest("d2")) .file("foo/d2/src/main.rs", "fn main() { println!(\"d2\"); }") .file("d3/Cargo.toml", &basic_bin_manifest("d3")) .file("d3/src/main.rs", "fn main() { println!(\"d2\"); }") .build(); let cargo = || { let mut process_builder = p.cargo("run"); process_builder.cwd("foo"); process_builder }; cargo() .arg("-p") .arg("d1") .with_stdout_data(str![[r#" d1 "#]]) .run(); cargo() .arg("-p") .arg("d2") .arg("--bin") .arg("d2") .with_stdout_data(str![[r#" d2 "#]]) .run(); cargo() .with_stdout_data(str![[r#" foo "#]]) .run(); cargo() .arg("-p") .arg("d1") .arg("-p") .arg("d2") .with_status(1) .with_stderr_data(str![[r#" [ERROR] the argument '--package []' cannot be used multiple times ... "#]]) .run(); cargo() .arg("-p") .arg("d3") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package(s) `d3` not found in workspace `[ROOT]/foo/foo` "#]]) .run(); cargo() .arg("-p") .arg("d*") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` does not support glob pattern `d*` on package selection "#]]) .run(); } #[cargo_test] fn explicit_bin_with_args() { let p = project() .file( "src/main.rs", r#" fn main() { assert_eq!(std::env::args().nth(1).unwrap(), "hello"); assert_eq!(std::env::args().nth(2).unwrap(), "world"); } "#, ) .build(); p.cargo("run --bin foo hello world").run(); } #[cargo_test] fn run_workspace() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file("a/Cargo.toml", &basic_bin_manifest("a")) .file("a/src/main.rs", r#"fn main() {println!("run-a");}"#) .file("b/Cargo.toml", &basic_bin_manifest("b")) .file("b/src/main.rs", r#"fn main() {println!("run-b");}"#) .build(); p.cargo("run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key. available binaries: a, b "#]]) .run(); p.cargo("run --bin a") .with_stdout_data(str![[r#" run-a "#]]) .run(); } #[cargo_test] fn default_run_workspace() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.0.1" edition = "2015" default-run = "a" "#, ) .file("a/src/main.rs", r#"fn main() {println!("run-a");}"#) .file("b/Cargo.toml", &basic_bin_manifest("b")) .file("b/src/main.rs", r#"fn main() {println!("run-b");}"#) .build(); p.cargo("run") .with_stdout_data(str![[r#" run-a "#]]) .run(); } #[cargo_test] fn print_env_verbose() { let p = project() .file("Cargo.toml", &basic_manifest("a", "0.0.1")) .file("src/main.rs", r#"fn main() {println!("run-a");}"#) .build(); p.cargo("run -vv") .with_stderr_data(str![[r#" [COMPILING] a v0.0.1 ([ROOT]/foo) [RUNNING] `[..]CARGO_MANIFEST_DIR=[ROOT]/foo[..] rustc --crate-name a[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[..]CARGO_MANIFEST_DIR=[ROOT]/foo[..] target/debug/a[EXE]` "#]]) .run(); } #[cargo_test] #[cfg(target_os = "macos")] fn run_link_system_path_macos() { use cargo_test_support::paths; use std::fs; // Check that the default system library path is honored. // First, build a shared library that will be accessed from // DYLD_FALLBACK_LIBRARY_PATH. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [lib] crate-type = ["cdylib"] "#, ) .file( "src/lib.rs", "#[no_mangle] pub extern fn something_shared() {}", ) .build(); p.cargo("build").run(); // This is convoluted. Since this test can't modify things in /usr, // this needs to dance around to check that things work. // // The default DYLD_FALLBACK_LIBRARY_PATH is: // $(HOME)/lib:/usr/local/lib:/lib:/usr/lib // // This will make use of ~/lib in the path, but the default cc link // path is /usr/lib:/usr/local/lib. So first need to build in one // location, and then move it to ~/lib. // // 1. Build with rustc-link-search pointing to libfoo so the initial // binary can be linked. // 2. Move the library to ~/lib // 3. Run `cargo run` to make sure it can still find the library in // ~/lib. // // This should be equivalent to having the library in /usr/local/lib. let p2 = project() .at("bar") .file("Cargo.toml", &basic_bin_manifest("bar")) .file( "src/main.rs", r#" extern { fn something_shared(); } fn main() { unsafe { something_shared(); } } "#, ) .file( "build.rs", &format!( r#" fn main() {{ println!("cargo::rustc-link-lib=foo"); println!("cargo::rustc-link-search={}"); }} "#, p.target_debug_dir().display() ), ) .build(); p2.cargo("build").run(); p2.cargo("test").run(); let libdir = paths::home().join("lib"); fs::create_dir(&libdir).unwrap(); fs::rename( p.target_debug_dir().join("libfoo.dylib"), libdir.join("libfoo.dylib"), ) .unwrap(); p.root().rm_rf(); const VAR: &str = "DYLD_FALLBACK_LIBRARY_PATH"; // Reset DYLD_FALLBACK_LIBRARY_PATH so that we don't inherit anything that // was set by the cargo that invoked the test. p2.cargo("run").env_remove(VAR).run(); p2.cargo("test").env_remove(VAR).run(); // Ensure this still works when DYLD_FALLBACK_LIBRARY_PATH has // a value set. p2.cargo("run").env(VAR, &libdir).run(); p2.cargo("test").env(VAR, &libdir).run(); } #[cargo_test] fn run_binary_with_same_name_as_dependency() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [dependencies] foo = { path = "foo" } "#, ) .file( "src/main.rs", r#" fn main() {} "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [lib] name = "foo" path = "foo.rs" "#, ) .file("foo/foo.rs", "") .build(); p.cargo("run").run(); p.cargo("check -p foo@0.5.0").run(); p.cargo("run -p foo@0.5.0").run(); p.cargo("run -p foo@0.5").run(); p.cargo("run -p foo@0.4") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package(s) `foo@0.4` not found in workspace `[ROOT]/foo` "#]]) .run(); } ================================================ FILE: tests/testsuite/rust_version.rs ================================================ //! Tests for targets with `rust-version`. use crate::prelude::*; use crate::utils::cargo_process; use cargo_test_support::{project, registry::Package, str}; #[cargo_test] fn rust_version_satisfied() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.1.1" [[bin]] name = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check").run(); p.cargo("check --ignore-rust-version").run(); } #[cargo_test] fn rust_version_error() { project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "^1.43" [[bin]] name = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build() .cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] unexpected version requirement, expected a version like "1.32" --> Cargo.toml:7:28 | 7 | rust-version = "^1.43" | ^^^^^^^ "#]]) .run(); } #[cargo_test] fn rust_version_older_than_edition() { project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] rust-version = "1.1" edition = "2018" [[bin]] name = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build() .cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: rust-version 1.1 is incompatible with the version (1.31.0) required by the specified edition (2018) "#]]) .run(); } #[cargo_test] fn lint_self_incompatible_with_rust_version() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.9876.0" [[bin]] name = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] rustc [..] is not supported by the following package: foo@0.0.1 requires rustc 1.9876.0 "#]]) .run(); p.cargo("check --ignore-rust-version").run(); } #[cargo_test] fn lint_dep_incompatible_with_rust_version() { Package::new("too_new_parent", "0.0.1") .dep("too_new_child", "0.0.1") .rust_version("1.2345.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("too_new_child", "0.0.1") .rust_version("1.2345.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("rustc_compatible", "0.0.1") .rust_version("1.60.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" rust-version = "1.50" authors = [] [dependencies] too_new_parent = "0.0.1" rustc_compatible = "0.0.1" "#, ) .file("src/main.rs", "fn main(){}") .build(); p.cargo("generate-lockfile") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 3 packages to latest compatible versions [ADDING] too_new_child v0.0.1 (requires Rust 1.2345.0) [ADDING] too_new_parent v0.0.1 (requires Rust 1.2345.0) "#]]) .run(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] too_new_parent v0.0.1 (registry `dummy-registry`) [DOWNLOADED] too_new_child v0.0.1 (registry `dummy-registry`) [DOWNLOADED] rustc_compatible v0.0.1 (registry `dummy-registry`) [ERROR] rustc [..] is not supported by the following packages: too_new_child@0.0.1 requires rustc 1.2345.0 too_new_parent@0.0.1 requires rustc 1.2345.0 Either upgrade rustc or select compatible dependency versions with `cargo update @ --precise ` where `` is the latest version supporting rustc [..] "#]]) .run(); p.cargo("check --ignore-rust-version").run(); } #[cargo_test] fn resolve_with_rust_version() { Package::new("only-newer", "1.6.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.5.0") .rust_version("1.55.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.6.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.60.0" [dependencies] only-newer = "1.0.0" newer-and-older = "1.0.0" "#, ) .file("src/main.rs", "fn main(){}") .build(); p.cargo("generate-lockfile --ignore-rust-version") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.6.0 └── only-newer v1.6.0 "#]]) .run(); p.cargo("generate-lockfile") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest Rust 1.60.0 compatible versions [ADDING] newer-and-older v1.5.0 (available: v1.6.0, requires Rust 1.65.0) [ADDING] only-newer v1.6.0 (requires Rust 1.65.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.5.0 └── only-newer v1.6.0 "#]]) .run(); } #[cargo_test] fn resolve_with_rustc() { Package::new("only-newer", "1.6.0") .rust_version("1.2345") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.5.0") .rust_version("1.55.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.6.0") .rust_version("1.2345") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.60.0" [dependencies] only-newer = "1.0.0" newer-and-older = "1.0.0" "#, ) .file("src/main.rs", "fn main(){}") .build(); p.cargo("generate-lockfile --ignore-rust-version") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [ADDING] newer-and-older v1.6.0 (requires Rust 1.2345) [ADDING] only-newer v1.6.0 (requires Rust 1.2345) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.6.0 └── only-newer v1.6.0 "#]]) .run(); p.cargo("generate-lockfile") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest Rust 1.60.0 compatible versions [ADDING] newer-and-older v1.5.0 (available: v1.6.0, requires Rust 1.2345) [ADDING] only-newer v1.6.0 (requires Rust 1.2345) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.5.0 └── only-newer v1.6.0 "#]]) .run(); } #[cargo_test] fn resolve_with_backtracking() { Package::new("has-rust-version", "1.6.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("no-rust-version", "2.1.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("no-rust-version", "2.2.0") .file("src/lib.rs", "fn other_stuff() {}") .dep("has-rust-version", "1.6.0") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.60.0" [dependencies] no-rust-version = "2" "#, ) .file("src/main.rs", "fn main(){}") .build(); p.cargo("generate-lockfile --ignore-rust-version") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) └── no-rust-version v2.2.0 └── has-rust-version v1.6.0 "#]]) .run(); // Ideally we'd pick `has-rust-version` 1.6.0 which requires backtracking p.cargo("generate-lockfile") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest Rust 1.60.0 compatible versions [ADDING] has-rust-version v1.6.0 (requires Rust 1.65.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) └── no-rust-version v2.2.0 └── has-rust-version v1.6.0 "#]]) .run(); } #[cargo_test] fn resolve_with_multiple_rust_versions() { Package::new(&format!("shared-only-newer"), "1.65.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); for ver in ["1.45.0", "1.55.0", "1.65.0"] { Package::new(&format!("shared-newer-and-older"), ver) .rust_version(ver) .file("src/lib.rs", "fn other_stuff() {}") .publish(); } Package::new(&format!("lower-only-newer"), "1.65.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); for ver in ["1.45.0", "1.55.0"] { Package::new(&format!("lower-newer-and-older"), ver) .rust_version(ver) .file("src/lib.rs", "fn other_stuff() {}") .publish(); } Package::new(&format!("higher-only-newer"), "1.65.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); for ver in ["1.55.0", "1.65.0"] { Package::new(&format!("higher-newer-and-older"), ver) .rust_version(ver) .file("src/lib.rs", "fn other_stuff() {}") .publish(); } let p = project() .file( "Cargo.toml", r#" [workspace] members = ["lower"] [package] name = "higher" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.60.0" [dependencies] higher-only-newer = "1" higher-newer-and-older = "1" shared-only-newer = "1" shared-newer-and-older = "1" "#, ) .file("src/main.rs", "fn main() {}") .file( "lower/Cargo.toml", r#" [package] name = "lower" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.50.0" [dependencies] lower-only-newer = "1" lower-newer-and-older = "1" shared-only-newer = "1" shared-newer-and-older = "1" "#, ) .file("lower/src/main.rs", "fn main() {}") .build(); p.cargo("generate-lockfile --ignore-rust-version") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 6 packages to latest compatible versions "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" higher v0.0.1 ([ROOT]/foo) ├── higher-newer-and-older v1.65.0 ├── higher-only-newer v1.65.0 ├── shared-newer-and-older v1.65.0 └── shared-only-newer v1.65.0 "#]]) .run(); p.cargo("generate-lockfile") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 6 packages to latest Rust 1.50.0 compatible versions [ADDING] higher-newer-and-older v1.55.0 (available: v1.65.0, requires Rust 1.65.0) [ADDING] higher-only-newer v1.65.0 (requires Rust 1.65.0) [ADDING] lower-newer-and-older v1.45.0 (available: v1.55.0, requires Rust 1.55.0) [ADDING] lower-only-newer v1.65.0 (requires Rust 1.65.0) [ADDING] shared-newer-and-older v1.45.0 (available: v1.65.0, requires Rust 1.65.0) [ADDING] shared-only-newer v1.65.0 (requires Rust 1.65.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" higher v0.0.1 ([ROOT]/foo) ├── higher-newer-and-older v1.55.0 ├── higher-only-newer v1.65.0 ├── shared-newer-and-older v1.45.0 └── shared-only-newer v1.65.0 "#]]) .run(); } #[cargo_test] fn resolve_edition2024() { Package::new("only-newer", "1.6.0") .rust_version("1.999.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.5.0") .rust_version("1.80.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.6.0") .rust_version("1.999.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2024" authors = [] rust-version = "1.85.0" [dependencies] only-newer = "1.0.0" newer-and-older = "1.0.0" "#, ) .file("src/main.rs", "fn main(){}") .build(); // Edition2024 should resolve for MSRV p.cargo("generate-lockfile") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest Rust 1.85.0 compatible versions [ADDING] newer-and-older v1.5.0 (available: v1.6.0, requires Rust 1.999.0) [ADDING] only-newer v1.6.0 (requires Rust 1.999.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.5.0 └── only-newer v1.6.0 "#]]) .run(); // `--ignore-rust-version` has precedence over Edition2024 p.cargo("generate-lockfile --ignore-rust-version") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [ADDING] newer-and-older v1.6.0 (requires Rust 1.999.0) [ADDING] only-newer v1.6.0 (requires Rust 1.999.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.6.0 └── only-newer v1.6.0 "#]]) .run(); // config has precedence over Edition2024 p.cargo("generate-lockfile") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "allow") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [ADDING] newer-and-older v1.6.0 (requires Rust 1.999.0) [ADDING] only-newer v1.6.0 (requires Rust 1.999.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.6.0 └── only-newer v1.6.0 "#]]) .run(); } #[cargo_test] fn resolve_v3() { Package::new("only-newer", "1.6.0") .rust_version("1.999.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.5.0") .rust_version("1.80.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.6.0") .rust_version("1.999.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.85.0" resolver = "3" [dependencies] only-newer = "1.0.0" newer-and-older = "1.0.0" "#, ) .file("src/main.rs", "fn main(){}") .build(); // v3 should resolve for MSRV p.cargo("generate-lockfile") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest Rust 1.85.0 compatible versions [ADDING] newer-and-older v1.5.0 (available: v1.6.0, requires Rust 1.999.0) [ADDING] only-newer v1.6.0 (requires Rust 1.999.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.5.0 └── only-newer v1.6.0 "#]]) .run(); // `--ignore-rust-version` has precedence over v3 p.cargo("generate-lockfile --ignore-rust-version") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [ADDING] newer-and-older v1.6.0 (requires Rust 1.999.0) [ADDING] only-newer v1.6.0 (requires Rust 1.999.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.6.0 └── only-newer v1.6.0 "#]]) .run(); // config has precedence over v3 p.cargo("generate-lockfile") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "allow") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [ADDING] newer-and-older v1.6.0 (requires Rust 1.999.0) [ADDING] only-newer v1.6.0 (requires Rust 1.999.0) "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.6.0 └── only-newer v1.6.0 "#]]) .run(); } #[cargo_test] fn update_msrv_resolve() { Package::new("bar", "1.5.0") .rust_version("1.55.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("bar", "1.6.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.60.0" [dependencies] bar = "1.0.0" "#, ) .file("src/main.rs", "fn main(){}") .build(); p.cargo("update") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest Rust 1.60.0 compatible version [ADDING] bar v1.5.0 (available: v1.6.0, requires Rust 1.65.0) "#]]) .run(); p.cargo("update --ignore-rust-version") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v1.5.0 -> v1.6.0 "#]]) .run(); } #[cargo_test] fn update_precise_overrides_msrv_resolver() { Package::new("bar", "1.5.0") .rust_version("1.55.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("bar", "1.6.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.60.0" [dependencies] bar = "1.0.0" "#, ) .file("src/main.rs", "fn main(){}") .build(); p.cargo("update") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest Rust 1.60.0 compatible version [ADDING] bar v1.5.0 (available: v1.6.0, requires Rust 1.65.0) "#]]) .run(); p.cargo("update --precise 1.6.0 bar") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] bar v1.5.0 -> v1.6.0 (requires Rust 1.65.0) "#]]) .run(); } #[cargo_test] fn check_msrv_resolve() { Package::new("only-newer", "1.6.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.5.0") .rust_version("1.55.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("newer-and-older", "1.6.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] rust-version = "1.60.0" [dependencies] only-newer = "1.0.0" newer-and-older = "1.0.0" "#, ) .file("src/main.rs", "fn main(){}") .build(); p.cargo("check --ignore-rust-version") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] only-newer v1.6.0 (registry `dummy-registry`) [DOWNLOADED] newer-and-older v1.6.0 (registry `dummy-registry`) [CHECKING] only-newer v1.6.0 [CHECKING] newer-and-older v1.6.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.6.0 └── only-newer v1.6.0 "#]]) .run(); std::fs::remove_file(p.root().join("Cargo.lock")).unwrap(); p.cargo("check") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest Rust 1.60.0 compatible versions [ADDING] newer-and-older v1.5.0 (available: v1.6.0, requires Rust 1.65.0) [ADDING] only-newer v1.6.0 (requires Rust 1.65.0) [DOWNLOADING] crates ... [DOWNLOADED] newer-and-older v1.5.0 (registry `dummy-registry`) [CHECKING] newer-and-older v1.5.0 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("tree") .with_stdout_data(str![[r#" foo v0.0.1 ([ROOT]/foo) ├── newer-and-older v1.5.0 └── only-newer v1.6.0 "#]]) .run(); } #[cargo_test] fn cargo_install_ignores_msrv_config() { Package::new("dep", "1.0.0") .rust_version("1.50") .file("src/lib.rs", "fn hello() {}") .publish(); Package::new("dep", "1.1.0") .rust_version("1.70") .file("src/lib.rs", "fn hello() {}") .publish(); Package::new("foo", "0.0.1") .rust_version("1.60") .file("src/main.rs", "fn main() {}") .dep("dep", "1") .publish(); cargo_process("install foo") .env( "CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback", ) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] foo v0.0.1 (registry `dummy-registry`) [INSTALLING] foo v0.0.1 [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] dep v1.1.0 (registry `dummy-registry`) [COMPILING] dep v1.1.0 [COMPILING] foo v0.0.1 [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE] [INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`) [WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries "#]]) .run(); } #[cargo_test] fn cargo_install_ignores_resolver_v3_msrv_change() { Package::new("dep", "1.0.0") .rust_version("1.50") .file("src/lib.rs", "fn hello() {}") .publish(); Package::new("dep", "1.1.0") .rust_version("1.70") .file("src/lib.rs", "fn hello() {}") .publish(); Package::new("foo", "0.0.1") .rust_version("1.60") .resolver("3") .file("src/main.rs", "fn main() {}") .dep("dep", "1") .publish(); cargo_process("install foo") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] foo v0.0.1 (registry `dummy-registry`) [INSTALLING] foo v0.0.1 [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] dep v1.1.0 (registry `dummy-registry`) [COMPILING] dep v1.1.0 [COMPILING] foo v0.0.1 [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE] [INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`) [WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries "#]]) .run(); } #[cargo_test] fn report_rust_versions() { Package::new("dep-only-low-compatible", "1.55.0") .rust_version("1.55.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-only-low-incompatible", "1.75.0") .rust_version("1.75.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-only-high-compatible", "1.65.0") .rust_version("1.65.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-only-high-incompatible", "1.75.0") .rust_version("1.75.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-only-unset-unset", "1.0.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-only-unset-compatible", "1.75.0") .rust_version("1.75.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-only-unset-incompatible", "1.2345.0") .rust_version("1.2345.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-shared-compatible", "1.55.0") .rust_version("1.55.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); Package::new("dep-shared-incompatible", "1.75.0") .rust_version("1.75.0") .file("src/lib.rs", "fn other_stuff() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["high", "low", "unset"] "#, ) .file( "high/Cargo.toml", r#" [package] name = "high" edition = "2015" rust-version = "1.70.0" [dependencies] dep-only-high-compatible = "1" dep-only-high-incompatible = "1" dep-shared-compatible = "1" dep-shared-incompatible = "1" "#, ) .file("high/src/main.rs", "fn main(){}") .file( "low/Cargo.toml", r#" [package] name = "low" edition = "2015" rust-version = "1.60.0" [dependencies] dep-only-low-compatible = "1" dep-only-low-incompatible = "1" dep-shared-compatible = "1" dep-shared-incompatible = "1" "#, ) .file("low/src/main.rs", "fn main(){}") .file( "unset/Cargo.toml", r#" [package] name = "unset" edition = "2015" [dependencies] dep-only-unset-unset = "1" dep-only-unset-compatible = "1" dep-only-unset-incompatible = "1" dep-shared-compatible = "1" dep-shared-incompatible = "1" "#, ) .file("unset/src/main.rs", "fn main(){}") .build(); p.cargo("update") .env("CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS", "fallback") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 9 packages to latest Rust 1.60.0 compatible versions [ADDING] dep-only-high-incompatible v1.75.0 (requires Rust 1.75.0) [ADDING] dep-only-low-incompatible v1.75.0 (requires Rust 1.75.0) [ADDING] dep-only-unset-incompatible v1.2345.0 (requires Rust 1.2345.0) [ADDING] dep-shared-incompatible v1.75.0 (requires Rust 1.75.0) "#]]) .run(); } ================================================ FILE: tests/testsuite/rustc.rs ================================================ //! Tests for the `cargo rustc` command. use crate::prelude::*; use cargo_test_support::basic_bin_manifest; use cargo_test_support::basic_lib_manifest; use cargo_test_support::basic_manifest; use cargo_test_support::project; use cargo_test_support::str; use cargo_test_support::target_spec_json; #[cargo_test] fn build_lib_for_foo() { let p = project() .file("src/main.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc --lib -v").with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C debuginfo=2 [..]-C metadata=[..] [..]--out-dir [ROOT]/foo/target/debug/deps -L dependency=[ROOT]/foo/target/debug/deps` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]).run(); } #[cargo_test] fn lib() { let p = project() .file("src/main.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc --lib -v -- -C debug-assertions=off") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C debuginfo=2 [..]-C metadata=[..] [..]--out-dir [ROOT]/foo/target/debug/deps -L dependency=[ROOT]/foo/target/debug/deps[..]-C debug-assertions=off[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_main_and_allow_unstable_options() { let p = project() .file("src/main.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc -v --bin foo -- -C debug-assertions") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C debuginfo=2 [..]-C metadata=[..] --out-dir [ROOT]/foo/target/debug/deps -L dependency=[ROOT]/foo/target/debug/deps` [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]--crate-type bin --emit=[..]link[..]-C debuginfo=2 [..]-C metadata=[..] --out-dir [ROOT]/foo/target/debug/deps -L dependency=[ROOT]/foo/target/debug/deps --extern foo=[ROOT]/foo/target/debug/deps/libfoo-[HASH].rlib[..]-C debug-assertions[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn fails_when_trying_to_build_main_and_lib_with_args() { let p = project() .file("src/main.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc -v -- -C debug-assertions") .with_status(101) .with_stderr_data(str![[r#" [ERROR] extra arguments to `rustc` can only be passed to one target, consider filtering the package by passing, e.g., `--lib` or `--bin NAME` to specify a single target "#]]) .run(); } #[cargo_test] fn build_with_args_to_one_of_multiple_binaries() { let p = project() .file("src/bin/foo.rs", "fn main() {}") .file("src/bin/bar.rs", "fn main() {}") .file("src/bin/baz.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc -v --bin bar -- -C debug-assertions") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C debuginfo=2 [..]-C metadata=[..] --out-dir [..]` [RUNNING] `rustc --crate-name bar --edition=2015 src/bin/bar.rs [..]--crate-type bin --emit=[..]link[..]-C debuginfo=2 [..]-C debug-assertions[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn fails_with_args_to_all_binaries() { let p = project() .file("src/bin/foo.rs", "fn main() {}") .file("src/bin/bar.rs", "fn main() {}") .file("src/bin/baz.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc -v -- -C debug-assertions") .with_status(101) .with_stderr_data(str![[r#" [ERROR] extra arguments to `rustc` can only be passed to one target, consider filtering the package by passing, e.g., `--lib` or `--bin NAME` to specify a single target "#]]) .run(); } #[cargo_test] fn fails_with_crate_type_to_multi_binaries() { let p = project() .file("src/bin/foo.rs", "fn main() {}") .file("src/bin/bar.rs", "fn main() {}") .file("src/bin/baz.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc --crate-type lib") .with_status(101) .with_stderr_data(str![[r#" [ERROR] crate types to rustc can only be passed to one target, consider filtering the package by passing, e.g., `--lib` or `--example` to specify a single target "#]]) .run(); } #[cargo_test] fn fails_with_crate_type_to_multi_examples() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[example]] name = "ex1" crate-type = ["rlib"] [[example]] name = "ex2" crate-type = ["rlib"] "#, ) .file("src/lib.rs", "") .file("examples/ex1.rs", "") .file("examples/ex2.rs", "") .build(); p.cargo("rustc -v --example ex1 --example ex2 --crate-type lib,cdylib") .with_status(101) .with_stderr_data(str![[r#" [ERROR] crate types to rustc can only be passed to one target, consider filtering the package by passing, e.g., `--lib` or `--example` to specify a single target "#]]) .run(); } #[cargo_test] fn fails_with_crate_type_to_binary() { let p = project().file("src/bin/foo.rs", "fn main() {}").build(); p.cargo("rustc --crate-type lib") .with_status(101) .with_stderr_data(str![[r#" [ERROR] crate types can only be specified for libraries and example libraries. Binaries, tests, and benchmarks are always the `bin` crate type "#]]) .run(); } #[cargo_test] fn build_with_crate_type_for_foo() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustc -v --crate-type cdylib") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type cdylib [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_with_crate_type_for_foo_with_deps() { let p = project() .file( "src/lib.rs", r#" extern crate a; pub fn foo() { a::hello(); } "#, ) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] a = { path = "a" } "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "pub fn hello() {}") .build(); p.cargo("rustc -v --crate-type cdylib") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] a v0.1.0 ([ROOT]/foo/a) [RUNNING] `rustc --crate-name a --edition=2015 a/src/lib.rs [..]--crate-type lib [..]` [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type cdylib [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_with_crate_types_for_foo() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustc -v --crate-type lib,cdylib") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib --crate-type cdylib [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_with_crate_type_to_example() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[example]] name = "ex" crate-type = ["rlib"] "#, ) .file("src/lib.rs", "") .file("examples/ex.rs", "") .build(); p.cargo("rustc -v --example ex --crate-type cdylib") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib [..]` [RUNNING] `rustc --crate-name ex --edition=2015 examples/ex.rs [..]--crate-type cdylib [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_with_crate_types_to_example() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[example]] name = "ex" crate-type = ["rlib"] "#, ) .file("src/lib.rs", "") .file("examples/ex.rs", "") .build(); p.cargo("rustc -v --example ex --crate-type lib,cdylib") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib [..]` [RUNNING] `rustc --crate-name ex --edition=2015 examples/ex.rs [..]--crate-type lib --crate-type cdylib [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_with_crate_types_to_one_of_multi_examples() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[example]] name = "ex1" crate-type = ["rlib"] [[example]] name = "ex2" crate-type = ["rlib"] "#, ) .file("src/lib.rs", "") .file("examples/ex1.rs", "") .file("examples/ex2.rs", "") .build(); p.cargo("rustc -v --example ex1 --crate-type lib,cdylib") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib [..]` [RUNNING] `rustc --crate-name ex1 --edition=2015 examples/ex1.rs [..]--crate-type lib --crate-type cdylib [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_with_args_to_one_of_multiple_tests() { let p = project() .file("tests/foo.rs", r#" "#) .file("tests/bar.rs", r#" "#) .file("tests/baz.rs", r#" "#) .file("src/lib.rs", r#" "#) .build(); p.cargo("rustc -v --test bar -- -C debug-assertions") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C debuginfo=2 [..]-C metadata=[..] --out-dir [..]` [RUNNING] `rustc --crate-name bar --edition=2015 tests/bar.rs [..]--emit=[..]link[..]-C debuginfo=2 [..]--test[..]-C debug-assertions[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_foo_with_bar_dependency() { let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] path = "../bar" "#, ) .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }") .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn baz() {}") .build(); foo.cargo("rustc -v -- -C debug-assertions") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] bar v0.1.0 ([ROOT]/bar) [RUNNING] `rustc --crate-name bar [..] -C debuginfo=2[..]` [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] -C debuginfo=2 [..]-C debug-assertions[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_only_bar_dependency() { let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] path = "../bar" "#, ) .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }") .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("src/lib.rs", "pub fn baz() {}") .build(); foo.cargo("rustc -v -p bar -- -C debug-assertions") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] bar v0.1.0 ([ROOT]/bar) [RUNNING] `rustc --crate-name bar [..]--crate-type lib [..] -C debug-assertions[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn targets_selected_default() { let p = project().file("src/main.rs", "fn main() {}").build(); p.cargo("rustc -v") // bin .with_stderr_contains( "[RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]--crate-type bin \ --emit=[..]link[..]", ) // bench .with_stderr_does_not_contain( "[RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]--emit=[..]link \ -C opt-level=3 --test [..]", ) // unit test .with_stderr_does_not_contain( "[RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]--emit=[..]link \ -C debuginfo=2 [..]--test [..]", ) .run(); } #[cargo_test] fn targets_selected_all() { let p = project().file("src/main.rs", "fn main() {}").build(); p.cargo("rustc -v --all-targets") // bin and unit test .with_stderr_data( str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]--crate-type bin --emit=[..]link[..]` [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]--emit[..]link[..] -C debuginfo=2 [..]--test [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]].unordered() ) .run(); } #[cargo_test] fn fail_with_multiple_packages() { let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] path = "../bar" [dependencies.baz] path = "../baz" "#, ) .file("src/main.rs", "fn main() {}") .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.1.0")) .file( "src/main.rs", r#" fn main() { if cfg!(flag = "1") { println!("Yeah from bar!"); } } "#, ) .build(); let _baz = project() .at("baz") .file("Cargo.toml", &basic_manifest("baz", "0.1.0")) .file( "src/main.rs", r#" fn main() { if cfg!(flag = "1") { println!("Yeah from baz!"); } } "#, ) .build(); foo.cargo("rustc -v -p bar -p baz") .with_status(1) .with_stderr_data(str![[r#" [ERROR] the argument '--package []' cannot be used multiple times ... "#]]) .run(); } #[cargo_test] fn fail_with_bad_bin_no_package() { let p = project() .file( "src/main.rs", r#" fn main() { println!("hello a.rs"); } "#, ) .build(); p.cargo("rustc --bin main") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `main` [HELP] available bin targets: foo ... "#]]) .run(); } #[cargo_test] fn fail_with_glob() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }") .build(); p.cargo("rustc -p '*z'") .with_status(101) .with_stderr_data(str![[r#" [ERROR] glob patterns on package selection are not supported. "#]]) .run(); } #[cargo_test] fn rustc_with_other_profile() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dev-dependencies] a = { path = "a" } "#, ) .file( "src/main.rs", r#" #[cfg(test)] extern crate a; #[test] fn foo() {} "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "") .build(); p.cargo("rustc --profile test").run(); } #[cargo_test] fn rustc_fingerprint() { // Verify that the fingerprint includes the rustc args. let p = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", "") .build(); p.cargo("rustc -v -- -C debug-assertions") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]-C debug-assertions[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("rustc -v -- -C debug-assertions") .with_stderr_data(str![[r#" [FRESH] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("rustc -v") .with_stderr_does_not_contain("-C debug-assertions") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("rustc -v") .with_stderr_data(str![[r#" [FRESH] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn rustc_test_with_implicit_bin() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file( "src/main.rs", r#" #[cfg(foo)] fn f() { compile_fail!("Foo shouldn't be set."); } fn main() {} "#, ) .file( "tests/test1.rs", r#" #[cfg(not(foo))] fn f() { compile_fail!("Foo should be set."); } "#, ) .build(); p.cargo("rustc --test test1 -v -- --cfg foo") .with_stderr_data( str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name test1 --edition=2015 tests/test1.rs [..] --cfg foo[..]` [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]` ... [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); } #[cargo_test] fn rustc_with_print_cfg_single_target() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/main.rs", r#"fn main() {} "#) .build(); p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --print cfg") .masquerade_as_nightly_cargo(&["print"]) .with_stdout_data( str![[r#" debug_assertions target_arch="x86_64" target_endian="little" target_env="msvc" target_family="windows" target_os="windows" target_pointer_width="64" target_vendor="pc" windows ... "#]] .unordered(), ) .run(); } #[cargo_test] fn rustc_with_print_cfg_multiple_targets() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/main.rs", r#"fn main() {} "#) .build(); p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --target i686-unknown-linux-gnu --print cfg") .masquerade_as_nightly_cargo(&["print"]) .with_stdout_data(str![[r#" debug_assertions target_arch="x86" target_endian="little" target_env="gnu" target_family="unix" target_os="linux" target_pointer_width="32" target_vendor="unknown" unix debug_assertions target_arch="x86_64" target_endian="little" target_env="msvc" target_family="windows" target_os="windows" target_pointer_width="64" target_vendor="pc" windows ... "#]].unordered()) .run(); } #[cargo_test] fn rustc_with_print_cfg_rustflags_env_var() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/main.rs", r#"fn main() {} "#) .build(); p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --print cfg") .masquerade_as_nightly_cargo(&["print"]) .env("RUSTFLAGS", "-C target-feature=+crt-static") .with_stdout_data( str![[r#" debug_assertions target_arch="x86_64" target_endian="little" target_env="msvc" target_family="windows" target_feature="crt-static" target_os="windows" target_pointer_width="64" target_vendor="pc" windows ... "#]] .unordered(), ) .run(); } #[cargo_test] fn rustc_with_print_cfg_config_toml() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file( ".cargo/config.toml", r#" [target.x86_64-pc-windows-msvc] rustflags = ["-C", "target-feature=+crt-static"] "#, ) .file("src/main.rs", r#"fn main() {} "#) .build(); p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --print cfg") .masquerade_as_nightly_cargo(&["print"]) .env("RUSTFLAGS", "-C target-feature=+crt-static") .with_stdout_data( str![[r#" debug_assertions target_arch="x86_64" target_endian="little" target_env="msvc" target_family="windows" target_feature="crt-static" target_os="windows" target_pointer_width="64" target_vendor="pc" windows ... "#]] .unordered(), ) .run(); } #[cargo_test(nightly, reason = "custom targets are unstable in rustc")] fn rustc_with_print_cfg_config_toml_env() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("targets/best-target.json", target_spec_json()) .file( ".cargo/config.toml", r#" [build] target = "best-target" [env] RUST_TARGET_PATH = { value = "./targets", relative = true } "#, ) .file("src/main.rs", r#"fn main() {} "#) .build(); p.cargo("rustc -Z unstable-options --print cfg") .masquerade_as_nightly_cargo(&["print"]) .with_stdout_data(str!["..."].unordered()) .env("RUSTFLAGS", "-Z unstable-options") .run(); } #[cargo_test] fn precedence() { // Ensure that the precedence of cargo-rustc is only lower than RUSTFLAGS, // but higher than most flags set by cargo. // // See rust-lang/cargo#14346 let p = project() .file( "Cargo.toml", r#" [package] name = "foo" edition = "2021" [lints.rust] unexpected_cfgs = "allow" "#, ) .file("src/lib.rs", "") .build(); p.cargo("rustc --release -v -- --cfg cargo_rustc -C strip=symbols") .env("RUSTFLAGS", "--cfg from_rustflags") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.0 ([ROOT]/foo) [RUNNING] `rustc [..]-C strip=debuginfo [..]--cfg cargo_rustc -C strip=symbols --cfg from_rustflags` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_with_duplicate_crate_types() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustc -v --crate-type staticlib --crate-type staticlib") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] --crate-type staticlib --emit[..] [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("rustc -v --crate-type staticlib --crate-type staticlib") .with_stderr_data(str![[r#" [FRESH] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } ================================================ FILE: tests/testsuite/rustc_info_cache.rs ================================================ //! Tests for the cache file for the rustc version info. use std::env; use crate::prelude::*; use cargo_test_support::basic_bin_manifest; use cargo_test_support::{basic_manifest, project}; const MISS: &str = "[..] rustc info cache miss[..]"; const HIT: &str = "[..]rustc info cache hit[..]"; const UPDATE: &str = "[..]updated rustc info cache[..]"; #[cargo_test] fn rustc_info_cache() { let p = project() .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .with_stderr_contains("[..]failed to read rustc info cache[..]") .with_stderr_contains(MISS) .with_stderr_does_not_contain(HIT) .with_stderr_contains(UPDATE) .run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .with_stderr_contains("[..]reusing existing rustc info cache[..]") .with_stderr_contains(HIT) .with_stderr_does_not_contain(MISS) .with_stderr_does_not_contain(UPDATE) .run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env("CARGO_CACHE_RUSTC_INFO", "0") .with_stderr_contains("[..]rustc info cache disabled[..]") .with_stderr_does_not_contain(UPDATE) .run(); let other_rustc = { let p = project() .at("compiler") .file("Cargo.toml", &basic_manifest("compiler", "0.1.0")) .file( "src/main.rs", r#" use std::process::Command; use std::env; fn main() { let mut cmd = Command::new("rustc"); for arg in env::args_os().skip(1) { cmd.arg(arg); } std::process::exit(cmd.status().unwrap().code().unwrap()); } "#, ) .build(); p.cargo("build").run(); p.root() .join("target/debug/compiler") .with_extension(env::consts::EXE_EXTENSION) }; p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env("RUSTC", other_rustc.display().to_string()) .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]") .with_stderr_contains(MISS) .with_stderr_does_not_contain(HIT) .with_stderr_contains(UPDATE) .run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env("RUSTC", other_rustc.display().to_string()) .with_stderr_contains("[..]reusing existing rustc info cache[..]") .with_stderr_contains(HIT) .with_stderr_does_not_contain(MISS) .with_stderr_does_not_contain(UPDATE) .run(); other_rustc.move_into_the_future(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env("RUSTC", other_rustc.display().to_string()) .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]") .with_stderr_contains(MISS) .with_stderr_does_not_contain(HIT) .with_stderr_contains(UPDATE) .run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env("RUSTC", other_rustc.display().to_string()) .with_stderr_contains("[..]reusing existing rustc info cache[..]") .with_stderr_contains(HIT) .with_stderr_does_not_contain(MISS) .with_stderr_does_not_contain(UPDATE) .run(); } #[cargo_test] fn rustc_info_cache_with_wrappers() { let wrapper_project = project() .at("wrapper") .file("Cargo.toml", &basic_bin_manifest("wrapper")) .file("src/main.rs", r#"fn main() { }"#) .build(); let wrapper = wrapper_project.bin("wrapper"); let p = project() .file( "Cargo.toml", r#" [package] name = "test" version = "0.0.0" authors = [] [workspace] "#, ) .file("src/main.rs", r#"fn main() { println!("hello"); }"#) .build(); for &wrapper_env in ["RUSTC_WRAPPER", "RUSTC_WORKSPACE_WRAPPER"].iter() { p.cargo("clean").with_status(0).run(); wrapper_project.change_file( "src/main.rs", r#" fn main() { let mut args = std::env::args_os(); let _me = args.next().unwrap(); let rustc = args.next().unwrap(); let status = std::process::Command::new(rustc).args(args).status().unwrap(); std::process::exit(if status.success() { 0 } else { 1 }) } "#, ); wrapper_project.cargo("build").with_status(0).run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env(wrapper_env, &wrapper) .with_stderr_contains("[..]failed to read rustc info cache[..]") .with_stderr_contains(MISS) .with_stderr_contains(UPDATE) .with_stderr_does_not_contain(HIT) .with_status(0) .run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env(wrapper_env, &wrapper) .with_stderr_contains("[..]reusing existing rustc info cache[..]") .with_stderr_contains(HIT) .with_stderr_does_not_contain(UPDATE) .with_stderr_does_not_contain(MISS) .with_status(0) .run(); wrapper_project.change_file("src/main.rs", r#"fn main() { panic!() }"#); wrapper_project.cargo("build").with_status(0).run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env(wrapper_env, &wrapper) .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]") .with_stderr_contains(MISS) .with_stderr_contains(UPDATE) .with_stderr_does_not_contain(HIT) .with_status(101) .run(); p.cargo("build") .env("CARGO_LOG", "cargo::util::rustc=debug") .env(wrapper_env, &wrapper) .with_stderr_contains("[..]reusing existing rustc info cache[..]") .with_stderr_contains(HIT) .with_stderr_does_not_contain(UPDATE) .with_stderr_does_not_contain(MISS) .with_status(101) .run(); } } ================================================ FILE: tests/testsuite/rustdoc.rs ================================================ //! Tests for the `cargo rustdoc` command. use crate::prelude::*; use cargo_test_support::str; use cargo_test_support::{basic_manifest, cross_compile, project}; use crate::utils::cross_compile::disabled as cross_compile_disabled; #[cargo_test] fn rustdoc_simple() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc -v") .with_stderr_data(str![[r#" [DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustdoc [..] --crate-name foo src/lib.rs -o [ROOT]/foo/target/doc [..] -L dependency=[ROOT]/foo/target/debug/deps [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); } #[cargo_test] fn rustdoc_simple_html() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc --output-format html --open -v") .with_status(101) .with_stderr_data(str![[r#" [ERROR] the `--output-format` flag is unstable, and only available on the nightly channel of Cargo, but this is the `stable` channel See https://doc.rust-lang.org/book/[..].html for more information about Rust release channels. See https://github.com/rust-lang/cargo/issues/12103 for more information about the `--output-format` flag. "#]]) .run(); } #[cargo_test(nightly, reason = "--output-format is unstable")] fn rustdoc_simple_json() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc -Z unstable-options --output-format json -v") .masquerade_as_nightly_cargo(&["rustdoc-output-format"]) .with_stderr_data(str![[r#" [DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustdoc [..] --crate-name foo [..]-o [ROOT]/foo/target/doc [..] --output-format=json[..] [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo.json "#]]) .run(); assert!(p.root().join("target/doc/foo.json").is_file()); } #[cargo_test] fn rustdoc_invalid_output_format() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc -Z unstable-options --output-format pdf -v") .masquerade_as_nightly_cargo(&["rustdoc-output-format"]) .with_status(1) .with_stderr_data(str![[r#" [ERROR] invalid value 'pdf' for '--output-format ' [possible values: html, json] For more information, try '--help'. "#]]) .run(); } #[cargo_test] fn rustdoc_json_stable() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc -Z unstable-options --output-format json -v") .with_status(101) .with_stderr_data( str![[r#" [ERROR] the `-Z` flag is only accepted on the nightly channel of Cargo, but this is the `stable` channel See https://doc.rust-lang.org/book/[..].html for more information about Rust release channels. "#]] ) .run(); } #[cargo_test] fn rustdoc_json_without_unstable_options() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc --output-format json -v") .masquerade_as_nightly_cargo(&["rustdoc-output-format"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] the `--output-format` flag is unstable, pass `-Z unstable-options` to enable it See https://github.com/rust-lang/cargo/issues/12103 for more information about the `--output-format` flag. "#]]) .run(); } #[cargo_test] fn rustdoc_args() { let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc -v -- --cfg=foo") .with_stderr_data(str![[r#" [DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustdoc [..] --crate-name foo src/lib.rs -o [ROOT]/foo/target/doc [..]-C metadata=[..] -L dependency=[ROOT]/foo/target/debug/deps [..]--cfg=foo[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); } #[cargo_test] fn rustdoc_binary_args_passed() { let p = project().file("src/main.rs", "").build(); p.cargo("rustdoc -v") .arg("--") .arg("--markdown-no-toc") .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..] --markdown-no-toc[..]` ... "#]]) .run(); } #[cargo_test] fn rustdoc_foo_with_bar_dependency() { let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] path = "../bar" "#, ) .file("src/lib.rs", "extern crate bar; pub fn foo() {}") .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file("src/lib.rs", "pub fn baz() {}") .build(); foo.cargo("rustdoc -v -- --cfg=foo") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [CHECKING] bar v0.0.1 ([ROOT]/bar) [RUNNING] `rustc [..] [ROOT]/bar/src/lib.rs [..]` [DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustdoc [..] --crate-name foo src/lib.rs -o [ROOT]/foo/target/doc [..]-C metadata=[..] -L dependency=[ROOT]/foo/target/debug/deps --extern [..]--cfg=foo[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); } #[cargo_test] fn rustdoc_only_bar_dependency() { let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] path = "../bar" "#, ) .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }") .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file("src/lib.rs", "pub fn baz() {}") .build(); foo.cargo("rustdoc -v -p bar -- --cfg=foo") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [DOCUMENTING] bar v0.0.1 ([ROOT]/bar) [RUNNING] `rustdoc [..] --crate-name bar [ROOT]/bar/src/lib.rs -o [ROOT]/foo/target/doc [..]-C metadata=[..] -L dependency=[ROOT]/foo/target/debug/deps [..]--cfg=foo[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/bar/index.html "#]]) .run(); } #[cargo_test] fn rustdoc_same_name_documents_lib() { let p = project() .file("src/main.rs", "fn main() {}") .file("src/lib.rs", r#" "#) .build(); p.cargo("rustdoc -v -- --cfg=foo") .with_stderr_data(str![[r#" [DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustdoc [..] --crate-name foo src/lib.rs -o [ROOT]/foo/target/doc [..]-C metadata=[..] -L dependency=[ROOT]/foo/target/debug/deps [..]--cfg=foo[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); } #[cargo_test] fn features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] quux = [] "#, ) .file("src/lib.rs", "") .build(); p.cargo("rustdoc --verbose --features quux") .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]feature=[..]quux[..]` ... "#]]) .run(); } #[cargo_test] fn proc_macro_crate_type() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [lib] proc-macro = true "#, ) .file("src/lib.rs", "") .build(); p.cargo("rustdoc --verbose") .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc --edition=2015 --crate-type proc-macro [..]` ... "#]]) .run(); } #[cargo_test] fn rustdoc_target() { if cross_compile_disabled() { return; } let p = project().file("src/lib.rs", "").build(); p.cargo("rustdoc --verbose --target") .arg(cross_compile::alternate()) .with_stderr_data(str![[r#" [DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]--target [ALT_TARGET] -o [ROOT]/foo/target/[ALT_TARGET]/doc [..] -L dependency=[ROOT]/foo/target/[ALT_TARGET]/debug/deps -L dependency=[ROOT]/foo/target/debug/deps[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/[..]/doc/foo/index.html "#]]) .run(); } #[cargo_test] fn fail_with_glob() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }") .build(); p.cargo("rustdoc -p '*z'") .with_status(101) .with_stderr_data(str![[r#" [ERROR] glob patterns on package selection are not supported. "#]]) .run(); } ================================================ FILE: tests/testsuite/rustdoc_extern_html.rs ================================================ //! Tests for the -Zrustdoc-map feature. use crate::prelude::*; use cargo_test_support::registry::{self, Package}; use cargo_test_support::{Project, paths, project, str}; fn basic_project() -> Project { Package::new("bar", "1.0.0") .file("src/lib.rs", "pub struct Straw;") .publish(); project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [dependencies] bar = "1.0" "#, ) .file( "src/lib.rs", r#" pub fn myfun() -> Option { None } "#, ) .build() } #[cargo_test] fn ignores_on_stable() { // Requires -Zrustdoc-map to use. let p = basic_project(); p.cargo("doc -v --no-deps") .with_stderr_does_not_contain("[..]--extern-html-root-url[..]") .run(); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn simple() { // Basic test that it works with crates.io. let p = basic_project(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--crate-name foo [..]--extern-html-root-url [..]bar=https://docs.rs/bar/1.0.0/[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); let myfun = p.read_file("target/doc/foo/fn.myfun.html"); assert!(myfun.contains(r#"href="https://docs.rs/bar/1.0.0/bar/struct.Straw.html""#)); } #[ignore = "Broken, temporarily disabled until https://github.com/rust-lang/rust/pull/82776 is resolved."] #[cargo_test] // #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn std_docs() { // Mapping std docs somewhere else. // For local developers, skip this test if docs aren't installed. let docs = std::path::Path::new(&paths::sysroot()).join("share/doc/rust/html"); if !docs.exists() { if cargo_util::is_ci() { panic!("std docs are not installed, check that the rust-docs component is installed"); } else { eprintln!( "documentation not found at {}, \ skipping test (run `rustdoc component add rust-docs` to install", docs.display() ); return; } } let p = basic_project(); p.change_file( ".cargo/config.toml", r#" [doc.extern-map] std = "local" "#, ); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_contains("[RUNNING] `rustdoc [..]--crate-name foo [..]std=file://[..]") .run(); let myfun = p.read_file("target/doc/foo/fn.myfun.html"); assert!(myfun.contains(r#"share/doc/rust/html/core/option/enum.Option.html""#)); p.change_file( ".cargo/config.toml", r#" [doc.extern-map] std = "https://example.com/rust/" "#, ); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_contains( "[RUNNING] `rustdoc [..]--crate-name foo [..]std=https://example.com/rust/[..]", ) .run(); let myfun = p.read_file("target/doc/foo/fn.myfun.html"); assert!(myfun.contains(r#"href="https://example.com/rust/core/option/enum.Option.html""#)); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn renamed_dep() { // Handles renamed dependencies. Package::new("bar", "1.0.0") .file("src/lib.rs", "pub struct Straw;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [dependencies] groovy = { version = "1.0", package = "bar" } "#, ) .file( "src/lib.rs", r#" pub fn myfun() -> Option { None } "#, ) .build(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--crate-name foo [..]--extern-html-root-url [..]bar=https://docs.rs/bar/1.0.0/[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); let myfun = p.read_file("target/doc/foo/fn.myfun.html"); assert!(myfun.contains(r#"href="https://docs.rs/bar/1.0.0/bar/struct.Straw.html""#)); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn lib_name() { // Handles lib name != package name. Package::new("bar", "1.0.0") .file( "Cargo.toml", r#" [package] name = "bar" version = "1.0.0" [lib] name = "rumpelstiltskin" "#, ) .file("src/lib.rs", "pub struct Straw;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = "1.0" "#, ) .file( "src/lib.rs", r#" pub fn myfun() -> Option { None } "#, ) .build(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--crate-name foo [..]--extern-html-root-url [..]rumpelstiltskin=https://docs.rs/bar/1.0.0/[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); let myfun = p.read_file("target/doc/foo/fn.myfun.html"); assert!( myfun.contains(r#"href="https://docs.rs/bar/1.0.0/rumpelstiltskin/struct.Straw.html""#) ); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn alt_registry() { // Supports other registry names. registry::alt_init(); Package::new("bar", "1.0.0") .alternative(true) .file( "src/lib.rs", r#" extern crate baz; pub struct Queen; pub use baz::King; "#, ) .registry_dep("baz", "1.0") .publish(); Package::new("baz", "1.0.0") .alternative(true) .file("src/lib.rs", "pub struct King;") .publish(); Package::new("grimm", "1.0.0") .file("src/lib.rs", "pub struct Gold;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [dependencies] bar = { version = "1.0", registry="alternative" } grimm = "1.0" "#, ) .file( "src/lib.rs", r#" pub fn queen() -> bar::Queen { bar::Queen } pub fn king() -> bar::King { bar::King } pub fn gold() -> grimm::Gold { grimm::Gold } "#, ) .file( ".cargo/config.toml", r#" [doc.extern-map.registries] alternative = "https://example.com/{pkg_name}/{version}/" crates-io = "https://docs.rs/" "#, ) .build(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--crate-name foo [..]--extern-html-root-url [..]bar=https://example.com/bar/1.0.0/[..] --extern-html-root-url [..]baz=https://example.com/baz/1.0.0/[..] --extern-html-root-url [..]grimm=https://docs.rs/grimm/1.0.0/[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); let queen = p.read_file("target/doc/foo/fn.queen.html"); assert!(queen.contains(r#"href="https://example.com/bar/1.0.0/bar/struct.Queen.html""#)); let king = p.read_file("target/doc/foo/fn.king.html"); assert!(king.contains(r#"href="https://example.com/baz/1.0.0/baz/struct.King.html""#)); let gold = p.read_file("target/doc/foo/fn.gold.html"); assert!(gold.contains(r#"href="https://docs.rs/grimm/1.0.0/grimm/struct.Gold.html""#)); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn multiple_versions() { // What happens when there are multiple versions. // NOTE: This is currently broken behavior. Rustdoc does not provide a way // to match renamed dependencies. Package::new("bar", "1.0.0") .file("src/lib.rs", "pub struct Spin;") .publish(); Package::new("bar", "2.0.0") .file("src/lib.rs", "pub struct Straw;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [dependencies] bar = "1.0" bar2 = {version="2.0", package="bar"} "#, ) .file( "src/lib.rs", " pub fn fn1() -> bar::Spin {bar::Spin} pub fn fn2() -> bar2::Straw {bar2::Straw} ", ) .build(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--crate-name foo [..]--extern-html-root-url [..]bar=https://docs.rs/bar/1.0.0/[..] --extern-html-root-url [..]bar=https://docs.rs/bar/2.0.0/[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); let fn1 = p.read_file("target/doc/foo/fn.fn1.html"); // This should be 1.0.0, rustdoc seems to use the last entry when there // are duplicates. assert!(fn1.contains(r#"href="https://docs.rs/bar/2.0.0/bar/struct.Spin.html""#)); let fn2 = p.read_file("target/doc/foo/fn.fn2.html"); assert!(fn2.contains(r#"href="https://docs.rs/bar/2.0.0/bar/struct.Straw.html""#)); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn rebuilds_when_changing() { // Make sure it rebuilds if the map changes. let p = basic_project(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--extern-html-root-url[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); // This also tests that the map for docs.rs can be overridden. p.change_file( ".cargo/config.toml", r#" [doc.extern-map.registries] crates-io = "https://example.com/" "#, ); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--extern-html-root-url [..]bar=https://example.com/bar/1.0.0/[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn alt_sparse_registry() { // Supports other registry names. registry::init(); let _registry = registry::RegistryBuilder::new() .http_index() .alternative() .build(); Package::new("bar", "1.0.0") .alternative(true) .file( "src/lib.rs", r#" extern crate baz; pub struct Queen; pub use baz::King; "#, ) .registry_dep("baz", "1.0") .publish(); Package::new("baz", "1.0.0") .alternative(true) .file("src/lib.rs", "pub struct King;") .publish(); Package::new("grimm", "1.0.0") .file("src/lib.rs", "pub struct Gold;") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [dependencies] bar = { version = "1.0", registry="alternative" } grimm = "1.0" "#, ) .file( "src/lib.rs", r#" pub fn queen() -> bar::Queen { bar::Queen } pub fn king() -> bar::King { bar::King } pub fn gold() -> grimm::Gold { grimm::Gold } "#, ) .file( ".cargo/config.toml", r#" [doc.extern-map.registries] alternative = "https://example.com/{pkg_name}/{version}/" crates-io = "https://docs.rs/" "#, ) .build(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..]--crate-name foo [..]--extern-html-root-url [..]bar=https://example.com/bar/1.0.0/[..] --extern-html-root-url [..]baz=https://example.com/baz/1.0.0/[..] --extern-html-root-url [..]grimm=https://docs.rs/grimm/1.0.0/[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); let queen = p.read_file("target/doc/foo/fn.queen.html"); assert!(queen.contains(r#"href="https://example.com/bar/1.0.0/bar/struct.Queen.html""#)); let king = p.read_file("target/doc/foo/fn.king.html"); assert!(king.contains(r#"href="https://example.com/baz/1.0.0/baz/struct.King.html""#)); let gold = p.read_file("target/doc/foo/fn.gold.html"); assert!(gold.contains(r#"href="https://docs.rs/grimm/1.0.0/grimm/struct.Gold.html""#)); } #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")] fn same_deps_multi_occurrence_in_dep_tree() { // rust-lang/cargo#13543 Package::new("baz", "1.0.0") .file("src/lib.rs", "") .publish(); Package::new("bar", "1.0.0") .file("src/lib.rs", "") .dep("baz", "1.0") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" edition = "2018" [dependencies] bar = "1.0" baz = "1.0" "#, ) .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [doc.extern-map.registries] crates-io = "https://docs.rs/" "#, ) .build(); p.cargo("doc -v --no-deps -Zrustdoc-map") .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_does_not_contain( "[..]--extern-html-root-url[..]bar=https://docs.rs\ [..]--extern-html-root-url[..]baz=https://docs.rs\ [..]--extern-html-root-url[..]baz=https://docs.rs[..]", ) .with_stderr_contains( "[..]--extern-html-root-url[..]bar=https://docs.rs\ [..]--extern-html-root-url[..]baz=https://docs.rs[..]", ) .run(); } ================================================ FILE: tests/testsuite/rustdocflags.rs ================================================ //! Tests for setting custom rustdoc flags. use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::rustc_host; use cargo_test_support::rustc_host_env; use cargo_test_support::str; #[cargo_test] fn parses_env() { let p = project().file("src/lib.rs", "").build(); p.cargo("doc -v") .env("RUSTDOCFLAGS", "--cfg=foo") .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..] --cfg=foo[..]` ... "#]]) .run(); } #[cargo_test] fn parses_config() { let p = project() .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [build] rustdocflags = ["--cfg", "foo"] "#, ) .build(); p.cargo("doc -v") .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..] --cfg foo [..]` ... "#]]) .run(); } #[cargo_test] fn bad_flags() { let p = project().file("src/lib.rs", "").build(); p.cargo("doc") .env("RUSTDOCFLAGS", "--bogus") .with_status(101) .with_stderr_data(str![[r#" ... [ERROR] Unrecognized option: 'bogus' ... "#]]) .run(); } #[cargo_test] fn rerun() { let p = project().file("src/lib.rs", "").build(); p.cargo("doc").env("RUSTDOCFLAGS", "--cfg=foo").run(); p.cargo("doc") .env("RUSTDOCFLAGS", "--cfg=foo") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); p.cargo("doc") .env("RUSTDOCFLAGS", "--cfg=bar") .with_stderr_data(str![[r#" [DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [GENERATED] [ROOT]/foo/target/doc/foo/index.html "#]]) .run(); } #[cargo_test] fn rustdocflags_passed_to_rustdoc_through_cargo_test() { let p = project() .file( "src/lib.rs", r#" //! ``` //! assert!(cfg!(do_not_choke)); //! ``` "#, ) .build(); p.cargo("test --doc") .env("RUSTDOCFLAGS", "--cfg do_not_choke") .run(); } #[cargo_test] fn rustdocflags_passed_to_rustdoc_through_cargo_test_only_once() { let p = project().file("src/lib.rs", "").build(); p.cargo("test --doc") .env("RUSTDOCFLAGS", "--markdown-no-toc") .run(); } #[cargo_test] fn rustdocflags_misspelled() { let p = project().file("src/main.rs", "fn main() { }").build(); p.cargo("doc") .env("RUSTDOC_FLAGS", "foo") .with_stderr_data(str![[r#" [WARNING] ignoring environment variable `RUSTDOC_FLAGS` | = [HELP] rustdoc flags are passed via `RUSTDOCFLAGS` ... "#]]) .run(); } #[cargo_test] fn whitespace() { // Checks behavior of different whitespace characters. let p = project().file("src/lib.rs", "").build(); // "too many operands" p.cargo("doc") .env("RUSTDOCFLAGS", "--crate-version this has spaces") .with_stderr_data(str![[r#" ... [ERROR] could not document `foo` ... "#]]) .with_status(101) .run(); p.cargo("doc") .env_remove("__CARGO_TEST_FORCE_ARGFILE") // Not applicable for argfile. .env( "RUSTDOCFLAGS", "--crate-version 1111\n2222\t3333\u{00a0}4444", ) .run(); let contents = p.read_file("target/doc/foo/index.html"); assert!(contents.contains("1111")); assert!(contents.contains("2222")); assert!(contents.contains("3333")); assert!(contents.contains("4444")); } #[cargo_test] fn not_affected_by_target_rustflags() { let cfg = if cfg!(windows) { "windows" } else { "unix" }; let p = project() .file("src/lib.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.'cfg({cfg})'] rustflags = ["-D", "missing-docs"] [build] rustdocflags = ["--cfg", "foo"] "#, ), ) .build(); // `cargo build` should fail due to missing docs. p.cargo("build -v") .with_status(101) .with_stderr_data(str![[r#" ... [RUNNING] `rustc [..] -D missing-docs` ... "#]]) .run(); // `cargo doc` shouldn't fail. p.cargo("doc -v") .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..] --cfg foo[..]` ... "#]]) .run(); } #[cargo_test] fn target_triple_rustdocflags_works() { let host = rustc_host(); let host_env = rustc_host_env(); let p = project().file("src/lib.rs", "").build(); // target.triple.rustdocflags in env works p.cargo("doc -v") .env( &format!("CARGO_TARGET_{host_env}_RUSTDOCFLAGS"), "--cfg=foo", ) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc[..]--cfg[..]foo[..]` ... "#]]) .run(); // target.triple.rustdocflags in config works p.cargo("doc -v") .arg("--config") .arg(format!("target.{host}.rustdocflags=['--cfg', 'foo']")) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc [..] --cfg foo [..]` ... "#]]) .run(); } #[cargo_test] fn target_triple_rustdocflags_works_through_cargo_test() { let host = rustc_host(); let host_env = rustc_host_env(); let p = project() .file( "src/lib.rs", r#" //! ``` //! assert!(cfg!(foo)); //! ``` "#, ) .build(); // target.triple.rustdocflags in env works p.cargo("test --doc -v") .env( &format!("CARGO_TARGET_{host_env}_RUSTDOCFLAGS"), "--cfg=foo", ) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc[..]--test[..]--cfg[..]foo[..]` "#]]) .with_stdout_data(str![[r#" running 1 test test src/lib.rs - (line 2) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); // target.triple.rustdocflags in config works p.cargo("test --doc -v") .arg("--config") .arg(format!("target.{host}.rustdocflags=['--cfg', 'foo']")) .with_stderr_data(str![[r#" ... [RUNNING] `rustdoc[..]--test[..]--cfg[..]foo[..]` "#]]) .with_stdout_data(str![[r#" running 1 test test src/lib.rs - (line 2) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); } ================================================ FILE: tests/testsuite/rustflags.rs ================================================ //! Tests for setting custom rustc flags. use std::fs; use crate::prelude::*; use cargo_test_support::registry::Package; use cargo_test_support::{ RawOutput, basic_manifest, paths, project, project_in_home, rustc_host, str, }; use snapbox::assert_data_eq; #[cargo_test] fn env_rustflags_normal_source() { let p = project() .file("src/lib.rs", "") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( "benches/d.rs", r#" #![feature(test)] extern crate test; #[bench] fn run1(_ben: &mut test::Bencher) { } "#, ) .build(); // Use RUSTFLAGS to pass an argument that will generate an error p.cargo("check --lib") .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --bin=a") .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --example=b") .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("test") .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("bench") .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn env_rustflags_build_script() { // RUSTFLAGS should be passed to rustc for build scripts // when --target is not specified. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(cfg!(foo)); } "#, ) .build(); p.cargo("check").env("RUSTFLAGS", "--cfg foo").run(); } #[cargo_test] fn env_rustflags_build_script_dep() { // RUSTFLAGS should be passed to rustc for build scripts // when --target is not specified. // In this test if --cfg foo is not passed the build will fail. let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" [build-dependencies.bar] path = "../bar" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file( "src/lib.rs", r#" fn bar() { } #[cfg(not(foo))] fn bar() { } "#, ) .build(); foo.cargo("check").env("RUSTFLAGS", "--cfg foo").run(); } #[cargo_test] fn env_rustflags_normal_source_with_target() { let p = project() .file("src/lib.rs", "") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( "benches/d.rs", r#" #![feature(test)] extern crate test; #[bench] fn run1(_ben: &mut test::Bencher) { } "#, ) .build(); let host = &rustc_host(); // Use RUSTFLAGS to pass an argument that will generate an error p.cargo("check --lib --target") .arg(host) .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --bin=a --target") .arg(host) .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --example=b --target") .arg(host) .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("test --target") .arg(host) .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("bench --target") .arg(host) .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn env_rustflags_build_script_with_target() { // RUSTFLAGS should not be passed to rustc for build scripts // when --target is specified. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(!cfg!(foo)); } "#, ) .build(); let host = rustc_host(); p.cargo("check --target") .arg(host) .env("RUSTFLAGS", "--cfg foo") .run(); } #[cargo_test] fn env_rustflags_build_script_with_target_doesnt_apply_to_host_kind() { // RUSTFLAGS should *not* be passed to rustc for build scripts when --target is specified as the // host triple even if target-applies-to-host-kind is enabled, to match legacy Cargo behavior. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(!cfg!(foo)); } "#, ) .file( ".cargo/config.toml", r#" target-applies-to-host = true "#, ) .build(); let host = rustc_host(); p.cargo("check --target") .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg(host) .arg("-Ztarget-applies-to-host") .env("RUSTFLAGS", "--cfg foo") .run(); } #[cargo_test] fn env_rustflags_build_script_dep_with_target() { // RUSTFLAGS should not be passed to rustc for build scripts // when --target is specified. // In this test if --cfg foo is passed the build will fail. let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" [build-dependencies.bar] path = "../bar" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file( "src/lib.rs", r#" fn bar() { } #[cfg(foo)] fn bar() { } "#, ) .build(); let host = rustc_host(); foo.cargo("check --target") .arg(host) .env("RUSTFLAGS", "--cfg foo") .run(); } #[cargo_test] fn env_rustflags_recompile() { let p = project().file("src/lib.rs", "").build(); p.cargo("check").run(); // Setting RUSTFLAGS forces a recompile p.cargo("check") .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn env_rustflags_recompile2() { let p = project().file("src/lib.rs", "").build(); p.cargo("check").env("RUSTFLAGS", "--cfg foo").run(); // Setting RUSTFLAGS forces a recompile p.cargo("check") .env("RUSTFLAGS", "-Z bogus") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn env_rustflags_no_recompile() { let p = project().file("src/lib.rs", "").build(); p.cargo("check").env("RUSTFLAGS", "--cfg foo").run(); p.cargo("check") .env("RUSTFLAGS", "--cfg foo") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_rustflags_normal_source() { let p = project() .file("src/lib.rs", "") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( "benches/d.rs", r#" #![feature(test)] extern crate test; #[bench] fn run1(_ben: &mut test::Bencher) { } "#, ) .file( ".cargo/config.toml", r#" [build] rustflags = ["-Z", "bogus"] "#, ) .build(); p.cargo("check --lib") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --bin=a") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --example=b") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("bench") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn build_rustflags_build_script() { // RUSTFLAGS should be passed to rustc for build scripts // when --target is not specified. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(cfg!(foo)); } "#, ) .file( ".cargo/config.toml", r#" [build] rustflags = ["--cfg", "foo"] "#, ) .build(); p.cargo("check").run(); } #[cargo_test] fn build_rustflags_build_script_dep() { // RUSTFLAGS should be passed to rustc for build scripts // when --target is not specified. // In this test if --cfg foo is not passed the build will fail. let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" [build-dependencies.bar] path = "../bar" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .file( ".cargo/config.toml", r#" [build] rustflags = ["--cfg", "foo"] "#, ) .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file( "src/lib.rs", r#" fn bar() { } #[cfg(not(foo))] fn bar() { } "#, ) .build(); foo.cargo("check").run(); } #[cargo_test] fn build_rustflags_normal_source_with_target() { let p = project() .file("src/lib.rs", "") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( "benches/d.rs", r#" #![feature(test)] extern crate test; #[bench] fn run1(_ben: &mut test::Bencher) { } "#, ) .file( ".cargo/config.toml", r#" [build] rustflags = ["-Z", "bogus"] "#, ) .build(); let host = &rustc_host(); // Use build.rustflags to pass an argument that will generate an error p.cargo("check --lib --target") .arg(host) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --bin=a --target") .arg(host) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --example=b --target") .arg(host) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("test --target") .arg(host) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("bench --target") .arg(host) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn build_rustflags_build_script_with_target() { // RUSTFLAGS should not be passed to rustc for build scripts // when --target is specified. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(!cfg!(foo)); } "#, ) .file( ".cargo/config.toml", r#" [build] rustflags = ["--cfg", "foo"] "#, ) .build(); let host = rustc_host(); p.cargo("check --target").arg(host).run(); } #[cargo_test] fn build_rustflags_build_script_dep_with_target() { // RUSTFLAGS should not be passed to rustc for build scripts // when --target is specified. // In this test if --cfg foo is passed the build will fail. let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" [build-dependencies.bar] path = "../bar" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .file( ".cargo/config.toml", r#" [build] rustflags = ["--cfg", "foo"] "#, ) .build(); let _bar = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file( "src/lib.rs", r#" fn bar() { } #[cfg(foo)] fn bar() { } "#, ) .build(); let host = rustc_host(); foo.cargo("check --target").arg(host).run(); } #[cargo_test] fn build_rustflags_recompile() { let p = project().file("src/lib.rs", "").build(); p.cargo("check").run(); // Setting RUSTFLAGS forces a recompile let config = r#" [build] rustflags = ["-Z", "bogus"] "#; let config_file = paths::root().join("foo/.cargo/config.toml"); fs::create_dir_all(config_file.parent().unwrap()).unwrap(); fs::write(config_file, config).unwrap(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn build_rustflags_recompile2() { let p = project().file("src/lib.rs", "").build(); p.cargo("check").env("RUSTFLAGS", "--cfg foo").run(); // Setting RUSTFLAGS forces a recompile let config = r#" [build] rustflags = ["-Z", "bogus"] "#; let config_file = paths::root().join("foo/.cargo/config.toml"); fs::create_dir_all(config_file.parent().unwrap()).unwrap(); fs::write(config_file, config).unwrap(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn build_rustflags_no_recompile() { let p = project() .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [build] rustflags = ["--cfg", "foo"] "#, ) .build(); p.cargo("check").env("RUSTFLAGS", "--cfg foo").run(); p.cargo("check") .env("RUSTFLAGS", "--cfg foo") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn build_rustflags_with_home_config() { // We need a config file inside the home directory let home = paths::home(); let home_config = home.join(".cargo"); fs::create_dir(&home_config).unwrap(); fs::write( &home_config.join("config"), r#" [build] rustflags = ["-Cllvm-args=-x86-asm-syntax=intel"] "#, ) .unwrap(); // And we need the project to be inside the home directory // so the walking process finds the home project twice. let p = project_in_home("foo").file("src/lib.rs", "").build(); p.cargo("check -v").run(); } #[cargo_test] fn target_rustflags_normal_source() { let p = project() .file("src/lib.rs", "") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( "benches/d.rs", r#" #![feature(test)] extern crate test; #[bench] fn run1(_ben: &mut test::Bencher) { } "#, ) .file( ".cargo/config.toml", &format!( " [target.{}] rustflags = [\"-Z\", \"bogus\"] ", rustc_host() ), ) .build(); p.cargo("check --lib") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --bin=a") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --example=b") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("bench") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn target_rustflags_also_for_build_scripts() { let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(cfg!(foo)); } "#, ) .file( ".cargo/config.toml", &format!( " [target.{}] rustflags = [\"--cfg=foo\"] ", rustc_host() ), ) .build(); p.cargo("check").run(); } #[cargo_test] fn target_rustflags_not_for_build_scripts_with_target() { let host = rustc_host(); let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(!cfg!(foo)); } "#, ) .file( ".cargo/config.toml", &format!( " [target.{}] rustflags = [\"--cfg=foo\"] ", host ), ) .build(); p.cargo("check --target").arg(host).run(); // Enabling -Ztarget-applies-to-host should not make a difference without the config setting p.cargo("check --target") .arg(host) .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .run(); // Even with the setting, the rustflags from `target.` should not apply, to match the legacy // Cargo behavior. p.change_file( ".cargo/config.toml", &format!( " target-applies-to-host = true [target.{}] rustflags = [\"--cfg=foo\"] ", host ), ); p.cargo("check --target") .arg(host) .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .run(); } #[cargo_test] fn build_rustflags_for_build_scripts() { let host = rustc_host(); let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert!(cfg!(foo), "CFG FOO!"); } "#, ) .file( ".cargo/config.toml", " [build] rustflags = [\"--cfg=foo\"] ", ) .build(); // With "legacy" behavior, build.rustflags should apply to build scripts without --target p.cargo("check").run(); // But should _not_ apply _with_ --target p.cargo("check --target") .arg(host) .with_status(101) .with_stderr_data("...\n[..]CFG FOO![..]\n...") .run(); // Enabling -Ztarget-applies-to-host should not make a difference without the config setting p.cargo("check") .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .run(); p.cargo("check --target") .arg(host) .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .with_status(101) .with_stderr_data("...\n[..]CFG FOO![..]\n...") .run(); // When set to false though, the "proper" behavior where host artifacts _only_ pick up on // [host] should be applied. p.change_file( ".cargo/config.toml", " target-applies-to-host = false [build] rustflags = [\"--cfg=foo\"] ", ); p.cargo("check") .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .with_status(101) .with_stderr_data("...\n[..]CFG FOO![..]\n...") .run(); p.cargo("check --target") .arg(host) .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .with_status(101) .with_stderr_data("...\n[..]CFG FOO![..]\n...") .run(); } #[cargo_test] fn host_rustflags_for_build_scripts() { let host = rustc_host(); let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" // Ensure that --cfg=foo is passed. fn main() { assert!(cfg!(foo)); } "#, ) .file( ".cargo/config.toml", &format!( " target-applies-to-host = false [host.{}] rustflags = [\"--cfg=foo\"] ", host ), ) .build(); p.cargo("check --target") .arg(host) .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"]) .arg("-Ztarget-applies-to-host") .arg("-Zhost-config") .run(); } // target.{}.rustflags takes precedence over build.rustflags #[cargo_test] fn target_rustflags_precedence() { let p = project() .file("src/lib.rs", "") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( ".cargo/config.toml", &format!( " [build] rustflags = [\"--cfg\", \"foo\"] [target.{}] rustflags = [\"-Z\", \"bogus\"] ", rustc_host() ), ) .build(); p.cargo("check --lib") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --bin=a") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("check --example=b") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); p.cargo("bench") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to run `rustc` to learn about target-specific information Caused by: [..]bogus[..] ... "#]]) .run(); } #[cargo_test] fn cfg_rustflags_normal_source() { let p = project() .file("src/lib.rs", "pub fn t() {}") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( ".cargo/config.toml", &format!( r#" [target.'cfg({})'] rustflags = ["--cfg", "bar"] "#, if rustc_host().contains("-windows-") { "windows" } else { "not(windows)" } ), ) .build(); p.cargo("build --lib -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] --cfg bar` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("build --bin=a -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name a [..] --cfg bar` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("build --example=b -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name b [..] --cfg bar` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("test --no-run -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/a-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/c-[HASH][EXE]` "#]]) .run(); p.cargo("bench --no-run -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/release/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/release/deps/a-[HASH][EXE]` "#]]) .run(); } // target.'cfg(...)'.rustflags takes precedence over build.rustflags #[cargo_test] fn cfg_rustflags_precedence() { let p = project() .file("src/lib.rs", "pub fn t() {}") .file("src/bin/a.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("tests/c.rs", "#[test] fn f() { }") .file( ".cargo/config.toml", &format!( r#" [build] rustflags = ["--cfg", "foo"] [target.'cfg({})'] rustflags = ["--cfg", "bar"] "#, if rustc_host().contains("-windows-") { "windows" } else { "not(windows)" } ), ) .build(); p.cargo("build --lib -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] --cfg bar` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("build --bin=a -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name a [..] --cfg bar` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("build --example=b -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name b [..] --cfg bar` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("test --no-run -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/a-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/c-[HASH][EXE]` "#]]) .run(); p.cargo("bench --no-run -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [RUNNING] `rustc [..] --cfg bar` [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/release/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/release/deps/a-[HASH][EXE]` "#]]) .run(); } #[cargo_test] fn target_rustflags_string_and_array_form1() { let p1 = project() .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [build] rustflags = ["--cfg", "foo"] "#, ) .build(); p1.cargo("check -v") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] --cfg foo` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); let p2 = project() .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [build] rustflags = "--cfg foo" "#, ) .build(); p2.cargo("check -v") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] --cfg foo` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn target_rustflags_string_and_array_form2() { let p1 = project() .file( ".cargo/config.toml", &format!( r#" [target.{}] rustflags = ["--cfg", "foo"] "#, rustc_host() ), ) .file("src/lib.rs", "") .build(); p1.cargo("check -v") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] --cfg foo` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); let p2 = project() .file( ".cargo/config.toml", &format!( r#" [target.{}] rustflags = "--cfg foo" "#, rustc_host() ), ) .file("src/lib.rs", "") .build(); p2.cargo("check -v") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..] --cfg foo` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn two_matching_in_config() { let p1 = project() .file( ".cargo/config.toml", r#" [target.'cfg(unix)'] rustflags = ["--cfg", 'foo="a"'] [target.'cfg(windows)'] rustflags = ["--cfg", 'foo="a"'] [target.'cfg(target_pointer_width = "32")'] rustflags = ["--cfg", 'foo="b"'] [target.'cfg(target_pointer_width = "64")'] rustflags = ["--cfg", 'foo="b"'] "#, ) .file( "src/main.rs", r#" #![allow(unexpected_cfgs)] fn main() { if cfg!(foo = "a") { println!("a"); } else if cfg!(foo = "b") { println!("b"); } else { panic!() } } "#, ) .build(); p1.cargo("run").run(); p1.cargo("build") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn env_rustflags_misspelled() { let p = project().file("src/main.rs", "fn main() { }").build(); for cmd in &["check", "build", "run", "test", "bench"] { p.cargo(cmd) .env("RUST_FLAGS", "foo") .with_stderr_data(str![[r#" [WARNING] ignoring environment variable `RUST_FLAGS` | = [HELP] rust flags are passed via `RUSTFLAGS` ... "#]]) .run(); } } #[cargo_test] fn env_rustflags_misspelled_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2021" build = "build.rs" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() { }") .build(); p.cargo("check") .env("RUST_FLAGS", "foo") .with_stderr_data(str![[r#" [WARNING] ignoring environment variable `RUST_FLAGS` | = [HELP] rust flags are passed via `RUSTFLAGS` [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn remap_path_prefix_works() { // Check that remap-path-prefix works. Package::new("bar", "0.1.0") .file("src/lib.rs", "pub fn f() -> &'static str { file!() }") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = "0.1" "#, ) .file( "src/main.rs", r#" fn main() { println!("{}", bar::f()); } "#, ) .build(); p.cargo("run") .env( "RUSTFLAGS", format!("--remap-path-prefix={}=/foo", paths::root().display()), ) .with_stdout_data(str![[r#" /foo/home/.cargo/registry/src/-[HASH]/bar-0.1.0/src/lib.rs "#]]) .run(); } #[cargo_test] fn rustflags_remap_path_prefix_ignored_for_c_metadata() { let p = project().file("src/lib.rs", "").build(); let build_output = p .cargo("build -v") .env( "RUSTFLAGS", "--remap-path-prefix=/abc=/zoo --remap-path-prefix /spaced=/zoo", ) .run(); let first_c_metadata = dbg!(get_c_metadata(build_output)); p.cargo("clean").run(); let build_output = p .cargo("build -v") .env( "RUSTFLAGS", "--remap-path-prefix=/def=/zoo --remap-path-prefix /earth=/zoo", ) .run(); let second_c_metadata = dbg!(get_c_metadata(build_output)); assert_data_eq!(first_c_metadata, second_c_metadata); } #[cargo_test] fn rustc_remap_path_prefix_ignored_for_c_metadata() { let p = project().file("src/lib.rs", "").build(); let build_output = p .cargo("rustc -v -- --remap-path-prefix=/abc=/zoo --remap-path-prefix /spaced=/zoo") .run(); let first_c_metadata = dbg!(get_c_metadata(build_output)); p.cargo("clean").run(); let build_output = p .cargo("rustc -v -- --remap-path-prefix=/def=/zoo --remap-path-prefix /earth=/zoo") .run(); let second_c_metadata = dbg!(get_c_metadata(build_output)); assert_data_eq!(first_c_metadata, second_c_metadata); } // `--remap-path-prefix` is meant to take two different binaries and make them the same but the // rlib name, including `-Cextra-filename`, can still end up in the binary so it can't change #[cargo_test] fn rustflags_remap_path_prefix_ignored_for_c_extra_filename() { let p = project().file("src/lib.rs", "").build(); let build_output = p .cargo("build -v") .env( "RUSTFLAGS", "--remap-path-prefix=/abc=/zoo --remap-path-prefix /spaced=/zoo", ) .run(); let first_c_extra_filename = dbg!(get_c_extra_filename(build_output)); p.cargo("clean").run(); let build_output = p .cargo("build -v") .env( "RUSTFLAGS", "--remap-path-prefix=/def=/zoo --remap-path-prefix /earth=/zoo", ) .run(); let second_c_extra_filename = dbg!(get_c_extra_filename(build_output)); assert_data_eq!(first_c_extra_filename, second_c_extra_filename); } // `--remap-path-prefix` is meant to take two different binaries and make them the same but the // rlib name, including `-Cextra-filename`, can still end up in the binary so it can't change #[cargo_test] fn rustc_remap_path_prefix_ignored_for_c_extra_filename() { let p = project().file("src/lib.rs", "").build(); let build_output = p .cargo("rustc -v -- --remap-path-prefix=/abc=/zoo --remap-path-prefix /spaced=/zoo") .run(); let first_c_extra_filename = dbg!(get_c_extra_filename(build_output)); p.cargo("clean").run(); let build_output = p .cargo("rustc -v -- --remap-path-prefix=/def=/zoo --remap-path-prefix /earth=/zoo") .run(); let second_c_extra_filename = dbg!(get_c_extra_filename(build_output)); assert_data_eq!(first_c_extra_filename, second_c_extra_filename); } fn get_c_metadata(output: RawOutput) -> String { let get_c_metadata_re = regex::Regex::new(r".* (--crate-name [^ ]+).* (-C ?metadata=[^ ]+).*").unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); let mut c_metadata = get_c_metadata_re .captures_iter(&stderr) .map(|c| { let (_, [name, c_metadata]) = c.extract(); format!("{name} {c_metadata}") }) .collect::>(); assert!( !c_metadata.is_empty(), "`{get_c_metadata_re:?}` did not match:\n```\n{stderr}\n```" ); c_metadata.sort(); c_metadata.join("\n") } fn get_c_extra_filename(output: RawOutput) -> String { let get_c_extra_filename_re = regex::Regex::new(r".* (--crate-name [^ ]+).* (-C ?extra-filename=[^ ]+).*").unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); let mut c_extra_filename = get_c_extra_filename_re .captures_iter(&stderr) .map(|c| { let (_, [name, c_extra_filename]) = c.extract(); format!("{name} {c_extra_filename}") }) .collect::>(); assert!( !c_extra_filename.is_empty(), "`{get_c_extra_filename_re:?}` did not match:\n```\n{stderr}\n```" ); c_extra_filename.sort(); c_extra_filename.join("\n") } #[cargo_test] fn host_config_rustflags_with_target() { // regression test for https://github.com/rust-lang/cargo/issues/10206 let p = project() .file("src/lib.rs", "") .file("build.rs", "fn main() { assert!(cfg!(foo)); }") .file(".cargo/config.toml", "target-applies-to-host = false") .build(); p.cargo("check") .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"]) .arg("-Zhost-config") .arg("-Ztarget-applies-to-host") .arg("-Zunstable-options") .arg("--config") .arg("host.rustflags=[\"--cfg=foo\"]") .run(); } #[cargo_test] fn target_applies_to_host_rustflags_works() { // Ensures that rustflags are passed to the target when // target_applies_to_host=false let p = project() .file( "src/lib.rs", r#"#[cfg(feature = "flag")] compile_error!("flag passed");"#, ) .build(); // Use RUSTFLAGS to pass an argument that will generate an error. p.cargo("check") .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .env("CARGO_TARGET_APPLIES_TO_HOST", "false") .env("RUSTFLAGS", r#"--cfg feature="flag""#) .with_status(101) .with_stderr_data( "[CHECKING] foo v0.0.1 ([ROOT]/foo) [ERROR] flag passed ...", ) .run(); } #[cargo_test] fn target_applies_to_host_rustdocflags_works() { // Ensures that rustflags are passed to the target when // target_applies_to_host=false let p = project() .file( "src/lib.rs", r#"#[cfg(feature = "flag")] compile_error!("flag passed");"#, ) .build(); // Use RUSTFLAGS to pass an argument that would generate an error // but it is ignored. p.cargo("doc") .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .env("CARGO_TARGET_APPLIES_TO_HOST", "false") .env("RUSTDOCFLAGS", r#"--cfg feature="flag""#) .with_status(101) .with_stderr_data( "[DOCUMENTING] foo v0.0.1 ([ROOT]/foo) [ERROR] flag passed ...", ) .run(); } #[cargo_test] fn host_config_shared_build_dep() { // rust-lang/cargo#14253 Package::new("cc", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bootstrap" edition = "2021" [dependencies] cc = "1.0.0" [build-dependencies] cc = "1.0.0" [profile.dev] debug = 0 "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .file( ".cargo/config.toml", " target-applies-to-host=false [host] rustflags = ['--cfg', 'from_host'] [build] rustflags = ['--cfg', 'from_target'] ", ) .build(); p.cargo("build -v") .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .arg("-Ztarget-applies-to-host") .arg("-Zhost-config") .with_stderr_data( str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] cc v1.0.0 (registry `dummy-registry`) [COMPILING] cc v1.0.0 [RUNNING] `rustc --crate-name cc [..]--cfg from_host[..]` [RUNNING] `rustc --crate-name cc [..]--cfg from_target[..]` [COMPILING] bootstrap v0.0.0 ([ROOT]/foo) [RUNNING] `rustc --crate-name build_script_build [..]--cfg from_host[..]` [RUNNING] `[ROOT]/foo/target/debug/build/bootstrap-[HASH]/build-script-build` [RUNNING] `rustc --crate-name bootstrap[..]--cfg from_target[..]` [FINISHED] `dev` profile [unoptimized] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); } ================================================ FILE: tests/testsuite/rustup.rs ================================================ //! Tests for Cargo's behavior under Rustup. use std::env; use std::env::consts::EXE_EXTENSION; use std::ffi::OsString; use std::fs; use std::path::{Path, PathBuf}; use crate::prelude::*; use crate::utils::cargo_process; use cargo_test_support::paths::{home, root}; use cargo_test_support::{process, project, str}; /// Helper to generate an executable. fn make_exe(dest: &Path, name: &str, contents: &str, env: &[(&str, PathBuf)]) -> PathBuf { let rs_name = format!("{name}.rs"); fs::write( root().join(&rs_name), &format!("fn main() {{ {contents} }}"), ) .unwrap(); let mut pb = process("rustc"); env.iter().for_each(|(key, value)| { pb.env(key, value); }); pb.arg("--edition=2021") .arg(root().join(&rs_name)) .exec() .unwrap(); let exe = Path::new(name).with_extension(EXE_EXTENSION); let output = dest.join(&exe); fs::rename(root().join(&exe), &output).unwrap(); output } fn prepend_path(path: &Path) -> OsString { let mut paths = vec![path.to_path_buf()]; paths.extend(env::split_paths(&env::var_os("PATH").unwrap_or_default())); env::join_paths(paths).unwrap() } struct RustupEnvironment { /// Path for ~/.cargo/bin cargo_bin: PathBuf, /// Path for ~/.rustup rustup_home: PathBuf, /// Path to the cargo executable in the toolchain directory /// (~/.rustup/toolchain/test-toolchain/bin/cargo.exe). cargo_toolchain_exe: PathBuf, } /// Creates an executable which prints a message and then runs the *real* rustc. fn real_rustc_wrapper(bin_dir: &Path, message: &str) -> PathBuf { let real_rustc = cargo_util::paths::resolve_executable("rustc".as_ref()).unwrap(); // The toolchain rustc needs to call the real rustc. In order to do that, // it needs to restore or clear the RUSTUP environment variables so that // if rustup is installed, it will call the correct rustc. let rustup_toolchain_setup = match std::env::var_os("RUSTUP_TOOLCHAIN") { Some(t) => format!( ".env(\"RUSTUP_TOOLCHAIN\", \"{}\")", t.into_string().unwrap() ), None => format!(".env_remove(\"RUSTUP_TOOLCHAIN\")"), }; let mut env = vec![("CARGO_RUSTUP_TEST_real_rustc", real_rustc)]; let rustup_home_setup = match std::env::var_os("RUSTUP_HOME") { Some(h) => { env.push(("CARGO_RUSTUP_TEST_RUSTUP_HOME", h.into())); format!(".env(\"RUSTUP_HOME\", env!(\"CARGO_RUSTUP_TEST_RUSTUP_HOME\"))") } None => format!(".env_remove(\"RUSTUP_HOME\")"), }; make_exe( bin_dir, "rustc", &format!( r#" eprintln!("{message}"); let r = std::process::Command::new(env!("CARGO_RUSTUP_TEST_real_rustc")) .args(std::env::args_os().skip(1)) {rustup_toolchain_setup} {rustup_home_setup} .status(); std::process::exit(r.unwrap().code().unwrap_or(2)); "# ), &env, ) } /// Creates a simulation of a rustup environment with `~/.cargo/bin` and /// `~/.rustup` directories populated with some executables that simulate /// rustup. fn simulated_rustup_environment() -> RustupEnvironment { // Set up ~/.rustup/toolchains/test-toolchain/bin with a custom rustc and cargo. let rustup_home = home().join(".rustup"); let toolchain_bin = rustup_home .join("toolchains") .join("test-toolchain") .join("bin"); toolchain_bin.mkdir_p(); let rustc_toolchain_exe = real_rustc_wrapper(&toolchain_bin, "real rustc running"); let cargo_toolchain_exe = make_exe( &toolchain_bin, "cargo", r#"panic!("cargo toolchain should not be called");"#, &[], ); // Set up ~/.cargo/bin with a typical set of rustup proxies. let cargo_bin = home().join(".cargo").join("bin"); cargo_bin.mkdir_p(); let rustc_proxy = make_exe( &cargo_bin, "rustc", &format!( r#" match std::env::args().next().unwrap().as_ref() {{ "rustc" => {{}} arg => panic!("proxy only supports rustc, got {{arg:?}}"), }} eprintln!("rustc proxy running"); let r = std::process::Command::new(env!("CARGO_RUSTUP_TEST_rustc_toolchain_exe")) .args(std::env::args_os().skip(1)) .status(); std::process::exit(r.unwrap().code().unwrap_or(2)); "# ), &[("CARGO_RUSTUP_TEST_rustc_toolchain_exe", rustc_toolchain_exe)], ); fs::hard_link( &rustc_proxy, cargo_bin.join("cargo").with_extension(EXE_EXTENSION), ) .unwrap(); fs::hard_link( &rustc_proxy, cargo_bin.join("rustup").with_extension(EXE_EXTENSION), ) .unwrap(); RustupEnvironment { cargo_bin, rustup_home, cargo_toolchain_exe, } } #[cargo_test] fn typical_rustup() { // Test behavior under a typical rustup setup with a normal toolchain. let RustupEnvironment { cargo_bin, rustup_home, cargo_toolchain_exe, } = simulated_rustup_environment(); // Set up a project and run a normal cargo build. let p = project().file("src/lib.rs", "").build(); // The path is modified so that cargo will call `rustc` from // `~/.cargo/bin/rustc to use our custom rustup proxies. let path = prepend_path(&cargo_bin); p.cargo("check") .env("RUSTUP_TOOLCHAIN", "test-toolchain") .env("RUSTUP_HOME", &rustup_home) .env("PATH", &path) .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) real rustc running [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); // Do a similar test, but with a toolchain link that does not have cargo // (which normally would do a fallback to nightly/beta/stable). cargo_toolchain_exe.rm_rf(); p.build_dir().rm_rf(); p.cargo("check") .env("RUSTUP_TOOLCHAIN", "test-toolchain") .env("RUSTUP_HOME", &rustup_home) .env("PATH", &path) .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) real rustc running [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } // This doesn't work on Windows because Cargo forces the PATH to contain the // sysroot_libdir, which is actually `bin`, preventing the test from // overriding the bin directory. #[cargo_test(ignore_windows = "PATH can't be overridden on Windows")] fn custom_calls_other_cargo() { // Test behavior when a custom subcommand tries to manipulate PATH to use // a different toolchain. let RustupEnvironment { cargo_bin, rustup_home, cargo_toolchain_exe: _, } = simulated_rustup_environment(); // Create a directory with a custom toolchain (outside of the rustup universe). let custom_bin = root().join("custom-bin"); custom_bin.mkdir_p(); // `cargo` points to the real cargo. let cargo_exe = crate::utils::cargo_exe(); fs::hard_link(&cargo_exe, custom_bin.join(cargo_exe.file_name().unwrap())).unwrap(); // `rustc` executes the real rustc. real_rustc_wrapper(&custom_bin, "custom toolchain rustc running"); // A project that cargo-custom will try to build. let p = project().file("src/lib.rs", "").build(); // Create a custom cargo subcommand. // This will modify PATH to a custom toolchain and call cargo from that. make_exe( &cargo_bin, "cargo-custom", r#" use std::env; use std::process::Command; eprintln!("custom command running"); let mut paths = vec![std::path::PathBuf::from(env!("CARGO_RUSTUP_TEST_custom_bin"))]; paths.extend(env::split_paths(&env::var_os("PATH").unwrap_or_default())); let path = env::join_paths(paths).unwrap(); let status = Command::new("cargo") .arg("check") .current_dir(env!("CARGO_RUSTUP_TEST_project_dir")) .env("PATH", path) .status() .unwrap(); assert!(status.success()); "#, &[ ("CARGO_RUSTUP_TEST_custom_bin", custom_bin), ("CARGO_RUSTUP_TEST_project_dir", p.root()), ], ); cargo_process("custom") // Set these to simulate what would happen when running under rustup. // We want to make sure that cargo-custom does not try to use the // rustup proxies. .env("RUSTUP_TOOLCHAIN", "test-toolchain") .env("RUSTUP_HOME", &rustup_home) .with_stderr_data(str![[r#" custom command running [CHECKING] foo v0.0.1 ([ROOT]/foo) custom toolchain rustc running [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } ================================================ FILE: tests/testsuite/sbom.rs ================================================ //! Tests for cargo-sbom precursor files. use std::path::PathBuf; use crate::prelude::*; use cargo_test_support::basic_bin_manifest; use cargo_test_support::cargo_test; use cargo_test_support::compare::assert_e2e; use cargo_test_support::project; use cargo_test_support::registry::Package; use snapbox::IntoData; const SBOM_FILE_EXTENSION: &str = ".cargo-sbom.json"; fn append_sbom_suffix(link: &PathBuf) -> PathBuf { let mut link_buf = link.clone().into_os_string(); link_buf.push(SBOM_FILE_EXTENSION); PathBuf::from(link_buf) } #[cargo_test] fn warn_without_passing_unstable_flag() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file( "src/main.rs", r#"fn main() { eprintln!("{:?}", option_env!("CARGO_SBOM_PATH")); }"#, ) .build(); p.cargo("run") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["sbom"]) .with_stderr_data(snapbox::str![[r#" [WARNING] ignoring 'sbom' config, pass `-Zsbom` to enable it [COMPILING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` None "#]]) .run(); let file = append_sbom_suffix(&p.bin("foo")); assert!(!file.exists()); } #[cargo_test] fn simple() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/main.rs", r#"fn main() {}"#) .build(); p.cargo("build -Zsbom") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["sbom"]) .run(); let file = append_sbom_suffix(&p.bin("foo")); let output = std::fs::read_to_string(file).unwrap(); // The expected test does contain the "rustc" section // but other tests omit them for brevity. assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [], "features": [], "id": "path+[ROOTURL]/foo#0.5.0", "kind": [ "bin" ] } ], "root": 0, "rustc": { "commit_hash": "{...}", "host": "[HOST_TARGET]", "verbose_version": "{...}", "version": "{...}", "workspace_wrapper": null, "wrapper": null }, "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } #[cargo_test] fn with_multiple_crate_types() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "1.2.3" [lib] crate-type = ["dylib", "rlib"] "#, ) .file("src/main.rs", r#"fn main() { let _i = foo::give_five(); }"#) .file("src/lib.rs", r#"pub fn give_five() -> i32 { 5 }"#) .build(); p.cargo("build -Zsbom") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["sbom"]) .run(); assert_eq!( 3, p.glob(p.target_debug_dir().join("*.cargo-sbom.json")) .count() ); let sbom_path = append_sbom_suffix(&p.dylib("foo")); assert!(sbom_path.is_file()); let output = std::fs::read_to_string(sbom_path).unwrap(); assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [], "features": [], "id": "path+[ROOTURL]/foo#1.2.3", "kind": [ "dylib", "rlib" ] } ], "root": 0, "rustc": "{...}", "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } #[cargo_test] fn with_simple_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" build = "build.rs" "#, ) .file("src/main.rs", "#[cfg(foo)] fn main() {}") .file( "build.rs", r#"fn main() { println!("cargo::rustc-check-cfg=cfg(foo)"); println!("cargo::rustc-cfg=foo"); }"#, ) .build(); p.cargo("build -Zsbom") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["sbom"]) .run(); let path = append_sbom_suffix(&p.bin("foo")); assert!(path.is_file()); let output = std::fs::read_to_string(path).unwrap(); assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [ { "index": 1, "kind": "build" } ], "features": [], "id": "path+[ROOTURL]/foo#0.0.1", "kind": [ "bin" ] }, { "dependencies": [], "features": [], "id": "path+[ROOTURL]/foo#0.0.1", "kind": [ "custom-build" ] } ], "root": 0, "rustc": "{...}", "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } #[cargo_test] fn with_build_dependencies() { Package::new("baz", "0.1.0").publish(); Package::new("bar", "0.1.0") .build_dep("baz", "0.1.0") .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" build = "build.rs" [build-dependencies] baz = "0.1.0" "#, ) .file("src/lib.rs", "pub fn bar() -> i32 { 2 }") .file( "build.rs", r#"fn main() { println!("cargo::rustc-check-cfg=cfg(foo)"); println!("cargo::rustc-cfg=foo"); }"#, ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" [dependencies] bar = "0.1.0" "#, ) .file("src/main.rs", "fn main() { let _i = bar::bar(); }") .build(); p.cargo("build -Zsbom") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["sbom"]) .run(); let path = append_sbom_suffix(&p.bin("foo")); let output = std::fs::read_to_string(path).unwrap(); assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [ { "index": 1, "kind": "build" } ], "features": [], "id": "registry+https://github.com/rust-lang/crates.io-index#bar@0.1.0", "kind": [ "lib" ] }, { "dependencies": [ { "index": 2, "kind": "normal" } ], "features": [], "id": "registry+https://github.com/rust-lang/crates.io-index#bar@0.1.0", "kind": [ "custom-build" ] }, { "dependencies": [], "features": [], "id": "registry+https://github.com/rust-lang/crates.io-index#baz@0.1.0", "kind": [ "lib" ] }, { "dependencies": [ { "index": 0, "kind": "normal" } ], "features": [], "id": "path+[ROOTURL]/foo#0.0.1", "kind": [ "bin" ] } ], "root": 3, "rustc": "{...}", "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } #[cargo_test] fn crate_uses_different_features_for_build_and_normal_dependencies() { let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.1.0" edition = "2021" [dependencies] b = { path = "b/", features = ["f1"] } [build-dependencies] b = { path = "b/", features = ["f2"] } "#, ) .file( "src/main.rs", r#" fn main() { b::f1(); } "#, ) .file( "build.rs", r#" fn main() { b::f2(); } "#, ) .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.0.1" edition = "2021" [features] f1 = [] f2 = [] "#, ) .file( "b/src/lib.rs", r#" #[cfg(feature = "f1")] pub fn f1() {} #[cfg(feature = "f2")] pub fn f2() {} "#, ) .build(); p.cargo("build -Zsbom") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["sbom"]) .run(); let path = append_sbom_suffix(&p.bin("a")); assert!(path.is_file()); let output = std::fs::read_to_string(path).unwrap(); assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [ { "index": 1, "kind": "build" }, { "index": 3, "kind": "normal" } ], "features": [], "id": "path+[ROOTURL]/foo#a@0.1.0", "kind": [ "bin" ] }, { "dependencies": [ { "index": 2, "kind": "normal" } ], "features": [], "id": "path+[ROOTURL]/foo#a@0.1.0", "kind": [ "custom-build" ] }, { "dependencies": [], "features": [ "f2" ], "id": "path+[ROOTURL]/foo/b#0.0.1", "kind": [ "lib" ] }, { "dependencies": [], "features": [ "f1" ], "id": "path+[ROOTURL]/foo/b#0.0.1", "kind": [ "lib" ] } ], "root": 0, "rustc": "{...}", "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } #[cargo_test] fn artifact_dep() { Package::new("bar", "0.5.0") .file("src/main.rs", "fn main() {}") .file("Cargo.toml", &basic_bin_manifest("bar")) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.0" edition = "2021" [lib] crate-type = ["dylib"] [dependencies] bar = { version = "0.5.0", artifact = "bin" } [build-dependencies] bar = { version = "0.5.0", artifact = "bin" } "#, ) .file("src/lib.rs", "") .file("build.rs", r#" fn main() { let bar: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR").expect("CARGO_BIN_FILE_BAR").into(); assert!(&bar.is_file()); }"#) .build(); p.cargo("build -Z bindeps -Z sbom") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["bindeps", "sbom"]) .run(); let output = std::fs::read_to_string(append_sbom_suffix(&p.dylib("foo"))).unwrap(); assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [], "features": [], "id": "registry+https://github.com/rust-lang/crates.io-index#bar@0.5.0", "kind": [ "bin" ] }, { "dependencies": [ { "index": 0, "kind": "normal" }, { "index": 0, "kind": "build" }, { "index": 2, "kind": "build" } ], "features": [], "id": "path+[ROOTURL]/foo#0.0.0", "kind": [ "dylib" ] }, { "dependencies": [], "features": [], "id": "path+[ROOTURL]/foo#0.0.0", "kind": [ "custom-build" ] } ], "root": 1, "rustc": "{...}", "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } #[cargo_test] fn proc_macro() { Package::new("noop", "0.0.1") .file( "Cargo.toml", r#" [package] name = "noop" version = "0.0.1" edition = "2021" [lib] proc-macro = true "#, ) .file( "src/lib.rs", r#" extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_derive(Noop)] pub fn noop(_input: TokenStream) -> TokenStream { "".parse().unwrap() } "#, ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2021" [dependencies] noop = "0.0.1" "#, ) .file( "src/main.rs", r#" #[macro_use] extern crate noop; #[derive(Noop)] struct X; fn main() {} "#, ) .build(); p.cargo("build -Z sbom") .env("CARGO_BUILD_SBOM", "true") .masquerade_as_nightly_cargo(&["sbom"]) .run(); let output = std::fs::read_to_string(append_sbom_suffix(&p.bin("foo"))).unwrap(); assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [ { "index": 1, "kind": "build" } ], "features": [], "id": "path+[ROOTURL]/foo#0.0.1", "kind": [ "bin" ] }, { "dependencies": [], "features": [], "id": "registry+https://github.com/rust-lang/crates.io-index#noop@0.0.1", "kind": [ "proc-macro" ] } ], "root": 0, "rustc": "{...}", "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } #[cargo_test] fn workspace_wrapper() { let wrapper = project() .at("wrapper") .file("Cargo.toml", &basic_bin_manifest("wrapper")) .file( "src/main.rs", r#" fn main() { let mut args = std::env::args().skip(1); if let Some(sbom) = std::env::var_os("CARGO_SBOM_PATH") { for sbom in std::env::split_paths(&sbom) { eprintln!("found sbom"); assert!(sbom.exists()); } } let status = std::process::Command::new(&args.next().unwrap()) .args(args).status().unwrap(); std::process::exit(status.code().unwrap_or(1)); } "#, ) .build(); wrapper.cargo("build").run(); let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/main.rs", r#"fn main() {}"#) .build(); p.cargo("build -Zsbom") .env("CARGO_BUILD_SBOM", "true") .env("RUSTC_WRAPPER", wrapper.bin("wrapper")) .masquerade_as_nightly_cargo(&["sbom"]) .with_stderr_data(snapbox::str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) found sbom [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); let file = append_sbom_suffix(&p.bin("foo")); let output = std::fs::read_to_string(file).unwrap(); assert_e2e().eq( output, snapbox::str![[r#" { "crates": [ { "dependencies": [], "features": [], "id": "path+[ROOTURL]/foo#0.5.0", "kind": [ "bin" ] } ], "root": 0, "rustc": "{...}", "target": "[HOST_TARGET]", "version": 1 } "#]] .is_json(), ); } ================================================ FILE: tests/testsuite/script/cargo.rs ================================================ use crate::prelude::*; use cargo_test_support::basic_manifest; use cargo_test_support::registry::Package; use cargo_test_support::str; const ECHO_SCRIPT: &str = r#"#!/usr/bin/env cargo fn main() { let current_exe = std::env::current_exe().unwrap().to_str().unwrap().to_owned(); let mut args = std::env::args_os(); let arg0 = args.next().unwrap().to_str().unwrap().to_owned(); let args = args.collect::>(); println!("current_exe: {current_exe}"); println!("arg0: {arg0}"); println!("args: {args:?}"); } #[test] fn test () {} "#; #[cfg(unix)] fn path() -> Vec { std::env::split_paths(&std::env::var_os("PATH").unwrap_or_default()).collect() } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn basic_rs() { let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript -v echo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] echo v0.0.0 ([ROOT]/foo/echo.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE]` "#]]) .run(); } #[cfg(unix)] #[cargo_test(nightly, reason = "-Zscript is unstable")] fn arg0() { let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript -v echo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE] arg0: [ROOT]/foo/echo.rs args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] echo v0.0.0 ([ROOT]/foo/echo.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE]` "#]]) .run(); } #[cfg(windows)] #[cargo_test(nightly, reason = "-Zscript is unstable")] fn arg0() { let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript -v echo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE] arg0: [ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] echo v0.0.0 ([ROOT]/foo/echo.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn basic_path() { let p = cargo_test_support::project() .file("echo", ECHO_SCRIPT) .build(); p.cargo("-Zscript -v ./echo") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] echo v0.0.0 ([ROOT]/foo/echo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn path_required() { let p = cargo_test_support::project() .file("echo", ECHO_SCRIPT) .build(); p.cargo("-Zscript -v echo") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] no such command: `echo` [HELP] a command with a similar name exists: `bench` [HELP] view all installed commands with `cargo --list` [HELP] find a package to install `echo` with `cargo search cargo-echo` [HELP] to run the file `echo`, provide a relative path like `./echo` "#]]) .run(); } #[cfg(unix)] #[cargo_test(nightly, reason = "-Zscript is unstable")] fn manifest_precedence_over_plugins() { let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .executable(std::path::Path::new("path-test").join("cargo-echo.rs"), "") .build(); // With path - fmt is there with known description let mut path = path(); path.push(p.root().join("path-test")); let path = std::env::join_paths(path.iter()).unwrap(); p.cargo("-Zscript -v echo.rs") .env("PATH", &path) .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] echo v0.0.0 ([ROOT]/foo/echo.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE]` "#]]) .run(); } #[cfg(unix)] #[cargo_test(nightly, reason = "-Zscript is unstable")] fn warn_when_plugin_masks_manifest_on_stable() { let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .executable(std::path::Path::new("path-test").join("cargo-echo.rs"), "") .build(); let mut path = path(); path.push(p.root().join("path-test")); let path = std::env::join_paths(path.iter()).unwrap(); p.cargo("-v echo.rs") .env("PATH", &path) .with_stdout_data("") .with_stderr_data(str![[r#" [WARNING] external subcommand `echo.rs` has the appearance of a manifest-command | = [NOTE] this was previously accepted but will be phased out when `-Zscript` is stabilized; see "#]]) .run(); } #[cargo_test] fn requires_nightly() { let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .build(); p.cargo("-v echo.rs") .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] running the file `echo.rs` requires `-Zscript` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn requires_z_flag() { let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .build(); p.cargo("-v echo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] running the file `echo.rs` requires `-Zscript` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn manifest_parse_error() { // Exaggerate the newlines to make it more obvious if the error's line number is off let script = r#"#!/usr/bin/env cargo --- [dependencies] bar = 3 --- fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data(str![""]) .with_stderr_data(str![[r#" [ERROR] invalid type: integer `3`, expected a version string like "0.9.8" or a detailed dependency like { version = "0.9.8" } --> script.rs:9:7 | 9 | bar = 3 | ^ "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn clean_output_with_edition() { let script = r#"#!/usr/bin/env cargo --- [package] edition = "2018" --- fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" Hello world! "#]]) .with_stderr_data(str![[r#" [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn warning_without_edition() { let script = r#"#!/usr/bin/env cargo --- [package] --- fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" Hello world! "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn rebuild() { let script = r#"#!/usr/bin/env cargo-eval fn main() { let msg = option_env!("_MESSAGE").unwrap_or("undefined"); println!("msg = {}", msg); }"#; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" msg = undefined "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); // Verify we don't rebuild p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" msg = undefined "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); // Verify we do rebuild p.cargo("-Zscript -v script.rs") .env("_MESSAGE", "hello") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" msg = hello "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn use_script_config() { let script = ECHO_SCRIPT; let _ = cargo_test_support::project() .at("script") .file("script.rs", script) .build(); let p = cargo_test_support::project() .file( ".cargo/config.toml", r#" [build] rustc = "non-existent-rustc" "#, ) .file("script.rs", script) .build(); // Verify the config is bad p.cargo("-Zscript script.rs -NotAnArg") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] could not execute process `non-existent-rustc -vV` (never executed) Caused by: [NOT_FOUND] "#]]) .run(); // Verify that the config isn't used p.cargo("-Zscript ../script/script.rs -NotAnArg") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: ["-NotAnArg"] "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn default_programmatic_verbosity() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript script.rs -NotAnArg") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: ["-NotAnArg"] "#]]) .with_stderr_data("") .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn quiet() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -q script.rs -NotAnArg") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: ["-NotAnArg"] "#]]) .with_stderr_data("") .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_line_numbering_preserved() { let script = r#"#!/usr/bin/env cargo fn main() { println!("line: {}", line!()); } "#; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" line: 4 "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_escaped_hyphen_arg() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v -- script.rs -NotAnArg") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: ["-NotAnArg"] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] -NotAnArg` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_unescaped_hyphen_arg() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs -NotAnArg") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: ["-NotAnArg"] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] -NotAnArg` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_same_flags() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs --help") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: ["--help"] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] --help` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_has_weird_chars() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("s-h.w§c!.rs", script) .build(); p.cargo("-Zscript -v s-h.w§c!.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/s-h-w-c-[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] s-h-w-c- v0.0.0 ([ROOT]/foo/s-h.w§c!.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/s-h-w-c-[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_has_leading_number() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("42answer.rs", script) .build(); p.cargo("-Zscript -v 42answer.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/answer[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] answer v0.0.0 ([ROOT]/foo/42answer.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/answer[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_is_number() { let script = ECHO_SCRIPT; let p = cargo_test_support::project().file("42.rs", script).build(); p.cargo("-Zscript -v 42.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/package[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] package v0.0.0 ([ROOT]/foo/42.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/package[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[cfg(not(windows))] fn test_name_is_windows_reserved_name() { let script = ECHO_SCRIPT; let p = cargo_test_support::project().file("con", script).build(); p.cargo("-Zscript -v ./con") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/con[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] con v0.0.0 ([ROOT]/foo/con) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/con[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_is_sysroot_package_name() { let script = ECHO_SCRIPT; let p = cargo_test_support::project().file("test", script).build(); p.cargo("-Zscript -v ./test") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/test[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] test v0.0.0 ([ROOT]/foo/test) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/test[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_is_keyword() { let script = ECHO_SCRIPT; let p = cargo_test_support::project().file("self", script).build(); p.cargo("-Zscript -v ./self") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/self[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] self v0.0.0 ([ROOT]/foo/self) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/self[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_is_deps_dir_implicit() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("deps.rs", script) .build(); p.cargo("-Zscript -v deps.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data(str![""]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [ERROR] failed to parse manifest at `[ROOT]/foo/deps.rs` Caused by: the binary target name `deps` is forbidden, it conflicts with cargo's build directory names "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_is_deps_dir_explicit() { let script = r#"#!/usr/bin/env cargo --- package.name = "deps" --- fn main() { let current_exe = std::env::current_exe().unwrap().to_str().unwrap().to_owned(); let mut args = std::env::args_os(); let arg0 = args.next().unwrap().to_str().unwrap().to_owned(); let args = args.collect::>(); println!("current_exe: {current_exe}"); println!("arg0: {arg0}"); println!("args: {args:?}"); } #[test] fn test () {} "#; let p = cargo_test_support::project() .file("deps.rs", script) .build(); p.cargo("-Zscript -v deps.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data(str![""]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [ERROR] failed to parse manifest at `[ROOT]/foo/deps.rs` Caused by: the binary target name `deps` is forbidden, it conflicts with cargo's build directory names "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn script_like_dir() { let p = cargo_test_support::project() .file("foo.rs/foo", "something") .build(); p.cargo("-Zscript -v foo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] no such file or subcommand `foo.rs`: `foo.rs` is a directory "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn non_existent_rs() { let p = cargo_test_support::project().build(); p.cargo("-Zscript -v foo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] no such file or subcommand `foo.rs` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn non_existent_rs_stable() { let p = cargo_test_support::project().build(); p.cargo("-v foo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] no such subcommand `foo.rs` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn did_you_mean_file() { let p = cargo_test_support::project() .file("food.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript -v foo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] no such file or subcommand `foo.rs` [HELP] there is a script with a similar name: `./food.rs` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn did_you_mean_file_stable() { let p = cargo_test_support::project() .file("food.rs", ECHO_SCRIPT) .build(); p.cargo("-v foo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] no such subcommand `foo.rs` [HELP] there is a script with a similar name: `./food.rs` (requires `-Zscript`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn did_you_mean_command() { let p = cargo_test_support::project().build(); p.cargo("-Zscript -v build--manifest-path=./Cargo.toml") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] no such file or subcommand `build--manifest-path=./Cargo.toml` [HELP] there is a command with a similar name: `build --manifest-path=./Cargo.toml` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn did_you_mean_command_stable() { let p = cargo_test_support::project().build(); p.cargo("-v build--manifest-path=./Cargo.toml") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] no such subcommand `build--manifest-path=./Cargo.toml` [HELP] there is a command with a similar name: `build --manifest-path=./Cargo.toml` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_name_same_as_dependency() { Package::new("script", "1.0.0").publish(); let script = r#"#!/usr/bin/env cargo --- [dependencies] script = "1.0.0" --- fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs --help") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" Hello world! "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest Rust [..] compatible version [DOWNLOADING] crates ... [DOWNLOADED] script v1.0.0 (registry `dummy-registry`) [COMPILING] script v1.0.0 [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] --help` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_path_dep() { let script = r#"#!/usr/bin/env cargo --- [dependencies] bar.path = "./bar" --- fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .file("src/lib.rs", "pub fn foo() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) .file("bar/src/lib.rs", "pub fn bar() {}") .build(); p.cargo("-Zscript -v script.rs --help") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" Hello world! "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [LOCKING] 1 package to latest Rust [..] compatible version [COMPILING] bar v0.0.1 ([ROOT]/foo/bar) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] --help` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_no_build_rs() { let script = r#"#!/usr/bin/env cargo fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .file("build.rs", "broken") .build(); p.cargo("-Zscript -v script.rs --help") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" Hello world! "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] --help` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_no_autobins() { let script = r#"#!/usr/bin/env cargo fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .file("src/bin/not-script/main.rs", "fn main() {}") .build(); p.cargo("-Zscript -v script.rs --help") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" Hello world! "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] --help` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn test_no_autolib() { let script = r#"#!/usr/bin/env cargo fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .file("src/lib.rs", r#"compile_error!{"must not be built"}"#) .build(); p.cargo("-Zscript -v script.rs --help") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" Hello world! "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] --help` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_workspace() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- package.edition = "2021" [workspace] ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `workspace` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn workspace_members_mentions_script() { let p = cargo_test_support::project() .file( "Cargo.toml", r#" [workspace] members = ["scripts/nop.rs"] "#, ) .file( "scripts/nop.rs", r#" ---- package.edition = "2021" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript check") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] manifest path `[ROOT]/foo` contains no package: The manifest is virtual, and the workspace has no members. "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn workspace_members_glob_matches_script() { let p = cargo_test_support::project() .file( "Cargo.toml", r#" [workspace] members = ["scripts/*"] "#, ) .file( "scripts/nop.rs", r#" ---- package.edition = "2021" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript check") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] manifest path `[ROOT]/foo` contains no package: The manifest is virtual, and the workspace has no members. "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn package_workspace() { let p = cargo_test_support::project() .file( "Cargo.toml", r#" [workspace] members = ["scripts/*"] [package] name = "foo" "#, ) .file( "scripts/nop.rs", r#" ---- package.edition = "2021" package.workspace = "../" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript ./scripts/nop.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/scripts/nop.rs` Caused by: `package.workspace` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_lib() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- package.edition = "2021" [lib] name = "script" path = "script.rs" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `lib` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_bin() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- package.edition = "2021" [[bin]] name = "script" path = "script.rs" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `bin` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_example() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- package.edition = "2021" [[example]] name = "script" path = "script.rs" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `example` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_test() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- package.edition = "2021" [[test]] name = "script" path = "script.rs" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `test` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_bench() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- package.edition = "2021" [[bench]] name = "script" path = "script.rs" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `bench` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_package_build() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- [package] edition = "2021" build = "script.rs" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `package.build` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_package_links() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- [package] edition = "2021" links = "script" ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `package.links` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_package_autolib() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- [package] edition = "2021" autolib = true ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `package.autolib` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_package_autobins() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- [package] edition = "2021" autobins = true ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `package.autobins` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_package_autoexamples() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- [package] edition = "2021" autoexamples = true ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `package.autoexamples` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_package_autotests() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- [package] edition = "2021" autotests = true ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `package.autotests` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn disallow_explicit_package_autobenches() { let p = cargo_test_support::project() .file( "script.rs", r#" ---- [package] edition = "2021" autobenches = true ---- fn main() {} "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/script.rs` Caused by: `package.autobenches` is not allowed in embedded manifests "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn implicit_target_dir() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn no_local_lockfile() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); let local_lockfile_path = p.root().join("Cargo.lock"); assert!(!local_lockfile_path.exists()); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); assert!(!local_lockfile_path.exists()); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_check_requires_nightly() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("check --manifest-path script.rs") .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] embedded manifest `[ROOT]/foo/script.rs` requires `-Zscript` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_check_requires_z_flag() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("check --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] embedded manifest `[ROOT]/foo/script.rs` requires `-Zscript` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_check_with_embedded() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript check --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data("") .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [CHECKING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_check_with_missing_script_rs() { let p = cargo_test_support::project().build(); p.cargo("-Zscript check --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] manifest path `script.rs` does not exist "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_check_with_missing_script() { let p = cargo_test_support::project().build(); p.cargo("-Zscript check --manifest-path script") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stdout_data("") .with_stderr_data(str![[r#" [ERROR] manifest path `script` does not exist "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_build_with_embedded() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript build --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data("") .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_test_with_embedded() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript test --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests script.rs ([ROOT]/home/.cargo/build/[HASH]/debug/deps/script-[HASH][EXE]) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_clean_with_embedded() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); // Ensure there is something to clean p.cargo("-Zscript script.rs") .masquerade_as_nightly_cargo(&["script"]) .run(); p.cargo("-Zscript clean --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data("") .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [REMOVED] [FILE_NUM] files, [FILE_SIZE]B total "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_generate_lockfile_with_embedded() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript generate-lockfile --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data("") .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_metadata_with_embedded() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript metadata --manifest-path script.rs --format-version=1") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data( str![[r#" { "metadata": null, "packages": [ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script.rs#script@0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script.rs", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script.rs", "test": true } ], "version": "0.0.0" } ], "resolve": { "nodes": [ { "dependencies": [], "deps": [], "features": [], "id": "path+[ROOTURL]/foo/script.rs#script@0.0.0" } ], "root": "path+[ROOTURL]/foo/script.rs#script@0.0.0" }, "target_directory": "[ROOT]/home/.cargo/build/[HASH]/target", "build_directory": "[ROOT]/home/.cargo/build/[HASH]", "version": 1, "workspace_default_members": [ "path+[ROOTURL]/foo/script.rs#script@0.0.0" ], "workspace_members": [ "path+[ROOTURL]/foo/script.rs#script@0.0.0" ], "workspace_root": "[ROOT]/foo" } "#]] .is_json(), ) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_read_manifest_with_embedded() { let script = ECHO_SCRIPT; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript read-manifest --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data( str![[r#" { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script.rs#script@0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script.rs", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script.rs", "test": true } ], "version": "0.0.0" } "#]] .is_json(), ) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_run_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript run --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_tree_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript tree --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" script v0.0.0 ([ROOT]/foo/script.rs) "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_update_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript update --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data("") .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_verify_project_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript verify-project --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data( str![[r#" { "success": "true" } "#]] .is_json(), ) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_pkgid_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript script.rs") .masquerade_as_nightly_cargo(&["script"]) .run(); p.cargo("-Zscript pkgid --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" path+[ROOTURL]/foo/script.rs#script@0.0.0 "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_pkgid_with_embedded_no_lock_file() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript pkgid --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [ERROR] a Cargo.lock must exist for this command "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_pkgid_with_embedded_dep() { Package::new("dep", "1.0.0").publish(); let script = r#"#!/usr/bin/env cargo --- [dependencies] dep = "1.0.0" --- fn main() { println!("Hello world!"); }"#; let p = cargo_test_support::project() .file("script.rs", script) .build(); p.cargo("-Zscript script.rs") .masquerade_as_nightly_cargo(&["script"]) .run(); p.cargo("-Zscript pkgid --manifest-path script.rs -p dep") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" registry+https://github.com/rust-lang/crates.io-index#dep@1.0.0 "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn script_as_dep() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .file("src/lib.rs", "pub fn foo() {}") .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] script.path = "script.rs" "#, ) .build(); p.cargo("build") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to `2015` while the latest is `[..]` [ERROR] failed to get `script` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `script` Caused by: unable to update [ROOT]/foo/script.rs Caused by: single file packages cannot be used as dependencies "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_install_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript install --path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] `[ROOT]/foo/script.rs` is not a directory. --path must point to a directory containing a Cargo.toml file. "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_package_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript package --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [ERROR] [ROOT]/foo/script.rs is unsupported by `cargo package` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn cmd_publish_with_embedded() { let p = cargo_test_support::project() .file("script.rs", ECHO_SCRIPT) .build(); p.cargo("-Zscript publish --manifest-path script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [ERROR] [ROOT]/foo/script.rs is unsupported by `cargo publish` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn manifest_path_env() { let p = cargo_test_support::project() .file( "script.rs", r#"#!/usr/bin/env cargo fn main() { let path = env!("CARGO_MANIFEST_PATH"); println!("CARGO_MANIFEST_PATH: {}", path); } "#, ) .build(); p.cargo("-Zscript -v script.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" CARGO_MANIFEST_PATH: [ROOT]/foo/script.rs "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] script v0.0.0 ([ROOT]/foo/script.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/script[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] fn ignore_surrounding_workspace() { let p = cargo_test_support::project() .file( std::path::Path::new(".cargo").join("config.toml"), r#" [registries.test-reg] index = "https://github.com/rust-lang/crates.io-index" "#, ) .file( std::path::Path::new("inner").join("Cargo.toml"), r#" [package] name = "inner" version = "0.1.0" [dependencies] serde = { version = "1.0", registry = "test-reg" } "#, ) .file(std::path::Path::new("inner").join("src").join("lib.rs"), "") .file(std::path::Path::new("script").join("echo.rs"), ECHO_SCRIPT) .file( "Cargo.toml", r#" [workspace] members = [ "inner", ] "#, ) .build(); p.cargo("-Zscript -v script/echo.rs") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE] arg0: [..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] echo v0.0.0 ([ROOT]/foo/script/echo.rs) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/echo[EXE]` "#]]) .run(); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[cfg(target_os = "linux")] fn memfd_script() { use std::io::Write; use std::os::fd::AsRawFd; let fd = memfd::MemfdOptions::new() .close_on_exec(false) .create("otkeep-script") .unwrap(); let mut file = fd.into_file(); file.write_all(ECHO_SCRIPT.as_bytes()).unwrap(); file.flush().unwrap(); let raw_fd = file.as_raw_fd(); let p = cargo_test_support::project() .file("echo.rs", ECHO_SCRIPT) .build(); p.cargo(&format!("-Zscript -v /proc/self/fd/{raw_fd}")) .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(str![[r#" current_exe: [ROOT]/home/.cargo/build/[HASH]/target/debug/package arg0: /proc/self/fd/[..] args: [] "#]]) .with_stderr_data(str![[r#" [WARNING] `package.edition` is unspecified, defaulting to the latest edition (currently `[..]`) [COMPILING] package v0.0.0 (/proc/self/fd/[..]) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/home/.cargo/build/[HASH]/target/debug/package` "#]]) .run(); } ================================================ FILE: tests/testsuite/script/mod.rs ================================================ mod cargo; mod rustc; ================================================ FILE: tests/testsuite/script/rustc.rs ================================================ use std::collections::BTreeMap; use snapbox::assert_data_eq; use crate::prelude::*; #[test] fn ensure_all_fixtures_have_tests() { let mut code_gen_divider = "//".to_owned(); code_gen_divider.push_str(" START CODE GENERATION"); let self_path = snapbox::utils::current_rs!(); let self_source = std::fs::read_to_string(&self_path).unwrap(); let (header, _) = self_source .split_once(&code_gen_divider) .expect("code-gen divider is present"); let header = header.trim(); let fixture_root = snapbox::utils::current_dir!().join("rustc_fixtures"); let mut fixtures = BTreeMap::new(); for entry in std::fs::read_dir(fixture_root).unwrap() { let entry = entry.unwrap(); let path = entry.path(); let fn_name = file_to_fn(&path); fixtures .entry(fn_name.clone()) .or_insert_with(|| Fixture::new(fn_name)) .add_path(path); } let fixtures = fixtures .into_values() .filter(Fixture::is_valid) .map(|f| f.to_string()) .collect::(); let actual = format!( "{header} {code_gen_divider} {fixtures}" ); assert_data_eq!(actual, snapbox::Data::read_from(&self_path, None).raw()); } fn file_to_fn(path: &std::path::Path) -> String { let name = path.file_stem().unwrap().to_str().unwrap(); name.replace("-", "_") } fn sanitize_path(path: &std::path::Path) -> String { path.strip_prefix(env!("CARGO_MANIFEST_DIR")) .unwrap() .as_os_str() .to_string_lossy() .replace("\\", "/") } struct Fixture { fn_name: String, input: std::path::PathBuf, output: Option, } impl Fixture { fn new(fn_name: String) -> Self { Self { fn_name, input: Default::default(), output: Default::default(), } } fn is_valid(&self) -> bool { !self.input.as_os_str().is_empty() } fn add_path(&mut self, path: std::path::PathBuf) { if path.extension().map(|ext| ext.to_str().unwrap()) == Some("rs") { assert!( self.input.as_os_str().is_empty(), "similarly named fixtures:/n{}/n{}", self.input.display(), path.display() ); self.input = path; } else { assert!( self.output.is_none(), "conflicting assertions:/n{}/n{}", self.output.as_ref().unwrap().display(), path.display() ); self.output = Some(path); } } } impl std::fmt::Display for Fixture { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let fn_name = &self.fn_name; let fixture_path = sanitize_path(&self.input); match self .output .as_ref() .map(|path| path.extension().unwrap().to_str().unwrap()) { Some("stderr") => { let assertion_path = sanitize_path(self.output.as_ref().unwrap()); write!( fmt, r#" #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn {fn_name}() {{ let fixture_path = {fixture_path:?}; let assertion_path = {assertion_path:?}; assert_failure(fixture_path, assertion_path); }} "# ) } Some("stdout") | None => { let mut backup_path = self.input.clone(); backup_path.set_extension("stdout"); let assertion_path = sanitize_path(self.output.as_ref().unwrap_or(&backup_path)); write!( fmt, r#" #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn {fn_name}() {{ let fixture_path = {fixture_path:?}; let assertion_path = {assertion_path:?}; assert_success(fixture_path, assertion_path); }} "# ) } Some(_) => { panic!( "unsupported assertiong: {}", self.output.as_ref().unwrap().display() ) } } } } #[track_caller] fn assert_success(fixture_path: &str, assertion_path: &str) { let p = cargo_test_support::project() .file("script", &std::fs::read_to_string(fixture_path).unwrap()) .build(); // `read-manifest` to validate frontmatter content without processing deps, compiling p.cargo("-Zscript read-manifest --manifest-path script") .masquerade_as_nightly_cargo(&["script"]) .with_stdout_data(snapbox::Data::read_from( std::path::Path::new(assertion_path), Some(snapbox::data::DataFormat::Json), )) .run(); } #[track_caller] fn assert_failure(fixture_path: &str, assertion_path: &str) { let p = cargo_test_support::project() .file("script", &std::fs::read_to_string(fixture_path).unwrap()) .build(); // `read-manifest` to validate frontmatter content without processing deps, compiling p.cargo("-Zscript read-manifest --manifest-path script") .masquerade_as_nightly_cargo(&["script"]) .with_status(101) .with_stderr_data(snapbox::Data::read_from( std::path::Path::new(assertion_path), None, )) .run(); } // START CODE GENERATION #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn content_contains_whitespace() { let fixture_path = "tests/testsuite/script/rustc_fixtures/content-contains-whitespace.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/content-contains-whitespace.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn content_non_lexible_tokens() { let fixture_path = "tests/testsuite/script/rustc_fixtures/content-non-lexible-tokens.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/content-non-lexible-tokens.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn escape_hyphens_leading() { let fixture_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-leading.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-leading.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn escape_hyphens_nonleading_1() { let fixture_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-1.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-1.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn escape_hyphens_nonleading_2() { let fixture_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-2.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-2.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn escape_hyphens_nonleading_3() { let fixture_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-3.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-3.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_close_extra_after() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-close-extra-after.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-close-extra-after.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_indented() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-indented.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-indented.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_indented_mismatch() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-indented-mismatch.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-indented-mismatch.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_mismatch_1() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-mismatch-1.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-mismatch-1.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_mismatch_2() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-mismatch-2.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-mismatch-2.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_too_many_dashes() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-too-many-dashes.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-too-many-dashes.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_unclosed_1() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-1.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-1.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_unclosed_2() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-2.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-2.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_unclosed_3() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-3.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-3.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_unclosed_4() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-4.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-4.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_unclosed_5() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-5.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-5.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_unclosed_6() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-6.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-unclosed-6.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_whitespace_trailing_1() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-1.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-1.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn fence_whitespace_trailing_2() { let fixture_path = "tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-2.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-2.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn frontmatter_crlf() { let fixture_path = "tests/testsuite/script/rustc_fixtures/frontmatter-crlf.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/frontmatter-crlf.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn infostring_comma() { let fixture_path = "tests/testsuite/script/rustc_fixtures/infostring-comma.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/infostring-comma.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn infostring_dot_leading() { let fixture_path = "tests/testsuite/script/rustc_fixtures/infostring-dot-leading.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/infostring-dot-leading.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn infostring_dot_nonleading() { let fixture_path = "tests/testsuite/script/rustc_fixtures/infostring-dot-nonleading.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/infostring-dot-nonleading.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn infostring_hyphen_leading() { let fixture_path = "tests/testsuite/script/rustc_fixtures/infostring-hyphen-leading.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/infostring-hyphen-leading.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn infostring_hyphen_nonleading() { let fixture_path = "tests/testsuite/script/rustc_fixtures/infostring-hyphen-nonleading.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/infostring-hyphen-nonleading.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn infostring_space() { let fixture_path = "tests/testsuite/script/rustc_fixtures/infostring-space.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/infostring-space.stderr"; assert_failure(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn location_after_shebang() { let fixture_path = "tests/testsuite/script/rustc_fixtures/location-after-shebang.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/location-after-shebang.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn location_after_tokens() { let fixture_path = "tests/testsuite/script/rustc_fixtures/location-after-tokens.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/location-after-tokens.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn location_include_in_expr_ctxt() { let fixture_path = "tests/testsuite/script/rustc_fixtures/location-include-in-expr-ctxt.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/location-include-in-expr-ctxt.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn location_include_in_item_ctxt() { let fixture_path = "tests/testsuite/script/rustc_fixtures/location-include-in-item-ctxt.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/location-include-in-item-ctxt.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn location_proc_macro_observer() { let fixture_path = "tests/testsuite/script/rustc_fixtures/location-proc-macro-observer.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/location-proc-macro-observer.stdout"; assert_success(fixture_path, assertion_path); } #[cargo_test(nightly, reason = "-Zscript is unstable")] #[rustfmt::skip] // code-generated fn multifrontmatter() { let fixture_path = "tests/testsuite/script/rustc_fixtures/multifrontmatter.rs"; let assertion_path = "tests/testsuite/script/rustc_fixtures/multifrontmatter.stderr"; assert_failure(fixture_path, assertion_path); } ================================================ FILE: tests/testsuite/script/rustc_fixtures/README.md ================================================ Canonical home for these tests is https://github.com/rust-lang/rust/tree/master/tests/ui/frontmatter To update 1. Sync changes to this directory 2. Run `SNAPSHOTS=overwrite cargo test --test testsuite -- script::rustc` to register new test cases 2. Run `SNAPSHOTS=overwrite cargo test --test testsuite -- script::rustc` to update snapshots for new test cases Note: - A `.stderr` file is assumed that the test fill fail - A `.stdout` file is assumed that the test fill succeed ================================================ FILE: tests/testsuite/script/rustc_fixtures/auxiliary/expr.rs ================================================ --- - --- 1 ================================================ FILE: tests/testsuite/script/rustc_fixtures/auxiliary/lib.rs ================================================ ---something --- pub fn foo(x: i32) -> i32 { -x } ================================================ FILE: tests/testsuite/script/rustc_fixtures/auxiliary/makro.rs ================================================ extern crate proc_macro; use proc_macro::{Literal, TokenStream}; #[proc_macro] pub fn check(_: TokenStream) -> TokenStream { // In the following test cases, the `---` may look like the start of frontmatter but it is not! // That's because it would be backward incompatible to interpret them as such in the latest // stable edition. That's not only the case due to the feature gate error but also due to the // fact that we "eagerly" emit errors on malformed frontmatter. // issue: _ = "---".parse::(); // Just a sequence of regular Rust punctuation tokens. assert_eq!(6, "---\n---".parse::().unwrap().into_iter().count()); // issue: assert!("---".parse::().is_err()); Default::default() } ================================================ FILE: tests/testsuite/script/rustc_fixtures/content-contains-whitespace.rs ================================================ #!/usr/bin/env -S cargo -Zscript ---cargo # Beware editing: it has numerous whitespace characters which are important. # It contains one ranges from the 'PATTERN_WHITE_SPACE' property outlined in # https://unicode.org/Public/UNIDATA/PropList.txt # # The characters in the first expression of the assertion can be generated # from: "4\u{0C}+\n\t\r7\t*\u{20}2\u{85}/\u{200E}3\u{200F}*\u{2028}2\u{2029}" package.description = """ 4 + 7 * 2…/‎3‏*
2 """ --- //@ check-pass // Ensure the frontmatter can contain any whitespace // CARGO(fail): However, TOML cannot #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/content-contains-whitespace.stderr ================================================ [ERROR] invalid multi-line basic string, expected `/`, characters --> script:10:2 | 10 | 4␌+ | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/content-non-lexible-tokens.rs ================================================ --- cargo package.description = """ 🏳️‍⚧️ """ --- //@ check-pass #![feature(frontmatter)] // check that frontmatter blocks can have tokens that are otherwise not accepted by // the lexer as Rust code. fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/content-non-lexible-tokens.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": "🏳️‍⚧️\n", "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-leading.rs ================================================ ---- package.description = """ --- """ ---- //@ check-pass // This test checks that longer dashes for opening and closing can be used to // escape sequences such as three dashes inside the frontmatter block. #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-leading.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": "\n---\n\n", "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-1.rs ================================================ --- package.description = """ --- --- """ --- // hyphens only need to be escaped when at the start of a line //@ check-pass #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-1.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": " ---\n\n ---\n", "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-2.rs ================================================ --- package.description = """ x ---🚧️ """ --- // Regression test for #141483 //@check-pass #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-2.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": "x ---🚧️\n", "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-3.rs ================================================ --- package.description = """ x ---y """ --- // Test that hyphens are allowed inside frontmatters if there is some // non-whitespace character preceding them. //@check-pass #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/escape-hyphens-nonleading-3.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": "x ---y\n", "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-close-extra-after.rs ================================================ --- ---cargo //~^ ERROR: extra characters after frontmatter close are not allowed #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-close-extra-after.stderr ================================================ [ERROR] unexpected characters after frontmatter close --> script:2:4 | 1 | --- 2 | ---cargo | ^^^^^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-indented-mismatch.rs ================================================ ---cargo //~^ ERROR: unclosed frontmatter //@ compile-flags: --crate-type lib #![feature(frontmatter)] fn foo(x: i32) -> i32 { ---x //~^ WARNING: use of a double negation [double_negations] } // this test is for the weird case that valid Rust code can have three dashes // within them and get treated as a frontmatter close. ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-indented-mismatch.stderr ================================================ [ERROR] unclosed frontmatter; expected `---` --> script:14:56 | 1 | ---cargo ... 14 | // within them and get treated as a frontmatter close. | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-indented.rs ================================================ --- //~^ ERROR: invalid preceding whitespace for frontmatter opening //~^^ ERROR: unclosed frontmatter --- #![feature(frontmatter)] // check that whitespaces should not precede the frontmatter opening or close. // CARGO(pass): not technitcally a frontmatter, so defer to rustc to error fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-indented.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-mismatch-1.rs ================================================ ---cargo //~^ ERROR: frontmatter close does not match the opening ---- // there must be the same number of dashes for both the opening and the close // of the frontmatter. #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-mismatch-1.stderr ================================================ [ERROR] closing code fence has 1 more `-` than the opening fence --> script:3:4 | 1 | ---cargo 2 | //~^ ERROR: frontmatter close does not match the opening 3 | ---- | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-mismatch-2.rs ================================================ ----cargo //~^ ERROR: frontmatter close does not match the opening ---cargo //~^ ERROR: extra characters after frontmatter close are not allowed #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-mismatch-2.stderr ================================================ [ERROR] closing code fence has 1 less `-` than the opening fence --> script:3:1 | 1 | ----cargo 2 | //~^ ERROR: frontmatter close does not match the opening 3 | ---cargo | ^^^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-too-many-dashes.rs ================================================ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [dependencies] ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #![feature(frontmatter)] // check that we limit fence lengths fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-too-many-dashes.stderr ================================================ [ERROR] too many `-` symbols: frontmatter openings may be delimited by up to 255 `-` symbols, but found 256 --> script:1:1 | 1 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-1.rs ================================================ ----cargo //~^ ERROR: unclosed frontmatter // This test checks that the #! characters can help us recover a frontmatter // close. There should not be a "missing `main` function" error as the rest // are properly parsed. #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-1.stderr ================================================ [ERROR] unclosed frontmatter; expected `----` --> script:10:14 | 1 | ----cargo ... 10 | fn main() {} | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-2.rs ================================================ ----cargo //~^ ERROR: unclosed frontmatter //~| ERROR: frontmatters are experimental //@ compile-flags: --crate-type lib // Leading whitespace on the feature line prevents recovery. However // the dashes quoted will not be used for recovery and the entire file // should be treated as within the frontmatter block. #![feature(frontmatter)] fn foo() -> &str { "----" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-2.stderr ================================================ [ERROR] unclosed frontmatter; expected `----` --> script:15:3 | 1 | ----cargo ... 15 | } | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-3.rs ================================================ ----cargo //~^ ERROR: frontmatter close does not match the opening //@ compile-flags: --crate-type lib // Unfortunate recovery situation. Not really preventable with improving the // recovery strategy, but this type of code is rare enough already. #![feature(frontmatter)] fn foo(x: i32) -> i32 { ---x //~^ ERROR: invalid preceding whitespace for frontmatter close //~| ERROR: extra characters after frontmatter close are not allowed } //~^ ERROR: unexpected closing delimiter: `}` ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-3.stderr ================================================ [ERROR] unclosed frontmatter; expected `----` --> script:16:47 | 1 | ----cargo ... 16 | //~^ ERROR: unexpected closing delimiter: `}` | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-4.rs ================================================ ----cargo //~^ ERROR: unclosed frontmatter //! Similarly, a module-level content should allow for recovery as well (as //! per unclosed-1.rs) #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-4.stderr ================================================ [ERROR] unclosed frontmatter; expected `----` --> script:9:14 | 1 | ----cargo ... 9 | fn main() {} | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-5.rs ================================================ ----cargo //~^ ERROR: unclosed frontmatter //~| ERROR: frontmatters are experimental // Similarly, a use statement should allow for recovery as well (as // per unclosed-1.rs) use std::env; fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-5.stderr ================================================ [ERROR] unclosed frontmatter; expected `----` --> script:10:14 | 1 | ----cargo ... 10 | fn main() {} | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-6.rs ================================================ --- //~^ ERROR unclosed frontmatter 🦀--- --- // This test checks the location of the --- recovered by the parser is not // incorrectly tracked during the less fortunate recovery case and multiple // candidates are found, as seen with #146847 #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-unclosed-6.stderr ================================================ [ERROR] unclosed frontmatter; expected `---` --> script:12:14 | 1 | --- ... 12 | fn main() {} | ^ ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-1.rs ================================================ ---cargo --- // please note the whitespace characters after the first four lines. // This ensures that we accept whitespaces before the frontmatter, after // the frontmatter opening and the frontmatter close. //@ check-pass // ignore-tidy-end-whitespace // ignore-tidy-leading-newlines // ignore-tidy-tab #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-1.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-2.rs ================================================ --- cargo --- //@ check-pass // ignore-tidy-tab // A frontmatter infostring can have leading whitespace. #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/fence-whitespace-trailing-2.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/frontmatter-crlf.rs ================================================ #!/usr/bin/env -S cargo -Zscript --- [dependencies] clap = "4" --- //@ check-pass // ignore-tidy-cr // crlf line endings should be accepted #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/frontmatter-crlf.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [ { "features": [], "kind": null, "name": "clap", "optional": false, "registry": null, "rename": null, "req": "^4", "source": "registry+https://github.com/rust-lang/crates.io-index", "target": null, "uses_default_features": true } ], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-comma.rs ================================================ ---cargo,clippy //~^ ERROR: invalid infostring for frontmatter --- // infostrings can only be a single identifier. #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-comma.stderr ================================================ [ERROR] unsupported frontmatter infostring attributes: `clippy` --> script:1:4 | 1 | ---cargo,clippy | ^^^^^^^^^^^^ 2 | //~^ ERROR: invalid infostring for frontmatter 3 | --- | ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-dot-leading.rs ================================================ ---.toml //~^ ERROR: invalid infostring for frontmatter --- // infostrings cannot have leading dots #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-dot-leading.stderr ================================================ [ERROR] unsupported frontmatter infostring `.toml`; specify `cargo` for embedding a manifest --> script:1:4 | 1 | ---.toml | ^^^^^ 2 | //~^ ERROR: invalid infostring for frontmatter 3 | --- | ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-dot-nonleading.rs ================================================ ---Cargo.toml --- // infostrings can contain dots as long as a dot isn't the first character. //@ check-pass // CARGO(fail): unsupported infostring #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-dot-nonleading.stderr ================================================ [ERROR] unsupported frontmatter infostring `Cargo.toml`; specify `cargo` for embedding a manifest --> script:1:4 | 1 | ---Cargo.toml | ^^^^^^^^^^ 2 | --- | ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-hyphen-leading.rs ================================================ --- -toml //~^ ERROR: invalid infostring for frontmatter --- // infostrings cannot have leading hyphens #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-hyphen-leading.stderr ================================================ [ERROR] unsupported frontmatter infostring `-toml`; specify `cargo` for embedding a manifest --> script:1:5 | 1 | --- -toml | ^^^^^ 2 | //~^ ERROR: invalid infostring for frontmatter 3 | --- | ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-hyphen-nonleading.rs ================================================ --- Cargo-toml --- // infostrings can contain hyphens as long as a hyphen isn't the first character. //@ check-pass // CARGO(fail): unsupported infostring #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-hyphen-nonleading.stderr ================================================ [ERROR] unsupported frontmatter infostring `Cargo-toml`; specify `cargo` for embedding a manifest --> script:1:5 | 1 | --- Cargo-toml | ^^^^^^^^^^ 2 | --- | ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-space.rs ================================================ --- cargo clippy //~^ ERROR: invalid infostring for frontmatter --- // infostrings cannot have spaces #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/infostring-space.stderr ================================================ [ERROR] unsupported frontmatter infostring `cargo clippy`; specify `cargo` for embedding a manifest --> script:1:5 | 1 | --- cargo clippy | ^^^^^^^^^^^^ 2 | //~^ ERROR: invalid infostring for frontmatter 3 | --- | ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-after-shebang.rs ================================================ #!/usr/bin/env -S cargo -Zscript --- [dependencies] clap = "4" --- //@ check-pass // Shebangs on a file can precede a frontmatter. #![feature(frontmatter)] fn main () {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-after-shebang.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [ { "features": [], "kind": null, "name": "clap", "optional": false, "registry": null, "rename": null, "req": "^4", "source": "registry+https://github.com/rust-lang/crates.io-index", "target": null, "uses_default_features": true } ], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-after-tokens.rs ================================================ #![feature(frontmatter)] --- //~^ ERROR: expected item, found `-` // FIXME(frontmatter): make this diagnostic better --- // frontmatters must be at the start of a file. This test ensures that. // CARGO(pass): not technitcally a frontmatter, so defer to rustc to error fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-after-tokens.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-include-in-expr-ctxt.rs ================================================ // Check that an expr-ctxt `include` doesn't try to parse frontmatter and instead // treats it as a regular Rust token sequence. //@ check-pass #![expect(double_negations)] fn main() { // issue: const _: () = assert!(-1 == include!("auxiliary/expr.rs")); } ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-include-in-expr-ctxt.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-include-in-item-ctxt.rs ================================================ // Ensure that in item ctxts we can `include` files that contain frontmatter. //@ check-pass #![feature(frontmatter)] include!("auxiliary/lib.rs"); fn main() { foo(1); } ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-include-in-item-ctxt.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-proc-macro-observer.rs ================================================ //@ check-pass //@ proc-macro: makro.rs //@ edition: 2021 //@ ignore-backends: gcc // Check that a proc-macro doesn't try to parse frontmatter and instead treats // it as a regular Rust token sequence. See `auxiliary/makro.rs` for details. makro::check!(); fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/location-proc-macro-observer.stdout ================================================ { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2024", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo/script#0.0.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/script", "metadata": null, "name": "script", "publish": [], "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "bin" ], "doc": true, "doctest": false, "edition": "2024", "kind": [ "bin" ], "name": "script", "src_path": "[ROOT]/foo/script", "test": true } ], "version": "0.0.0" } ================================================ FILE: tests/testsuite/script/rustc_fixtures/multifrontmatter.rs ================================================ --- --- --- //~^ ERROR: expected item, found `-` // FIXME(frontmatter): make this diagnostic better --- // test that we do not parse another frontmatter block after the first one. #![feature(frontmatter)] fn main() {} ================================================ FILE: tests/testsuite/script/rustc_fixtures/multifrontmatter.stderr ================================================ [ERROR] only one frontmatter is supported --> script:4:1 | 1 | --- 2 | --- 3 | 4 | --- | ^^^ ================================================ FILE: tests/testsuite/search.rs ================================================ //! Tests for the `cargo search` command. use crate::prelude::*; use crate::utils::cargo_process; use cargo_test_support::registry::{RegistryBuilder, Response}; use cargo_test_support::str; const SEARCH_API_RESPONSE: &[u8] = br#" { "crates": [{ "created_at": "2014-11-16T20:17:35Z", "description": "Design by contract style assertions for Rust", "documentation": null, "downloads": 2, "homepage": null, "id": "hoare", "keywords": [], "license": null, "links": { "owners": "/api/v1/crates/hoare/owners", "reverse_dependencies": "/api/v1/crates/hoare/reverse_dependencies", "version_downloads": "/api/v1/crates/hoare/downloads", "versions": "/api/v1/crates/hoare/versions" }, "max_version": "0.1.1", "name": "hoare", "repository": "https://github.com/nick29581/libhoare", "updated_at": "2014-11-20T21:49:21Z", "versions": null }, { "id": "postgres", "name": "postgres", "updated_at": "2020-05-01T23:17:54.335921+00:00", "versions": null, "keywords": null, "categories": null, "badges": [ { "badge_type": "circle-ci", "attributes": { "repository": "sfackler/rust-postgres", "branch": null } } ], "created_at": "2014-11-24T02:34:44.756689+00:00", "downloads": 535491, "recent_downloads": 88321, "max_version": "0.17.3", "newest_version": "0.17.3", "description": "A native, synchronous PostgreSQL client", "homepage": null, "documentation": null, "repository": "https://github.com/sfackler/rust-postgres", "links": { "version_downloads": "/api/v1/crates/postgres/downloads", "versions": "/api/v1/crates/postgres/versions", "owners": "/api/v1/crates/postgres/owners", "owner_team": "/api/v1/crates/postgres/owner_team", "owner_user": "/api/v1/crates/postgres/owner_user", "reverse_dependencies": "/api/v1/crates/postgres/reverse_dependencies" }, "exact_match": true } ], "meta": { "total": 2 } }"#; const SEARCH_RESULTS: &str = "\ hoare = \"0.1.1\" # Design by contract style assertions for Rust postgres = \"0.17.3\" # A native, synchronous PostgreSQL client "; #[must_use] fn setup() -> RegistryBuilder { RegistryBuilder::new() .http_api() .add_responder("/api/v1/crates", |_, _| Response { code: 200, headers: vec![], body: SEARCH_API_RESPONSE.to_vec(), }) } #[cargo_test] fn not_update() { let registry = setup().build(); cargo_process("search postgres") .replace_crates_io(registry.index_url()) .with_stdout_data(SEARCH_RESULTS) .with_stderr_data(str![[r#" [UPDATING] crates.io index [NOTE] to learn more about a package, run `cargo info ` "#]]) .run(); cargo_process("search postgres") .replace_crates_io(registry.index_url()) .with_stdout_data(SEARCH_RESULTS) // without "Updating ... index" .with_stderr_data(str![[r#" [NOTE] to learn more about a package, run `cargo info ` "#]]) .run(); } #[cargo_test] fn replace_default() { let registry = setup().build(); cargo_process("search postgres") .replace_crates_io(registry.index_url()) .with_stdout_data(SEARCH_RESULTS) .with_stderr_data(str![[r#" [UPDATING] crates.io index [NOTE] to learn more about a package, run `cargo info ` "#]]) .run(); } #[cargo_test] fn simple() { let registry = setup().build(); cargo_process("search postgres --index") .arg(registry.index_url().as_str()) .with_stdout_data(SEARCH_RESULTS) .with_stderr_data(str![[r#" [UPDATING] `[ROOT]/registry` index [NOTE] to learn more about a package, run `cargo info ` "#]]) .run(); } #[cargo_test] fn multiple_query_params() { let registry = setup().build(); cargo_process("search postgres sql --index") .arg(registry.index_url().as_str()) .with_stdout_data(SEARCH_RESULTS) .with_stderr_data(str![[r#" [UPDATING] `[ROOT]/registry` index [NOTE] to learn more about a package, run `cargo info ` "#]]) .run(); } #[cargo_test] fn ignore_quiet() { let registry = setup().build(); cargo_process("search -q postgres") .replace_crates_io(registry.index_url()) .with_stdout_data(SEARCH_RESULTS) .run(); } #[cargo_test] fn colored_results() { let registry = setup().build(); cargo_process("search --color=never postgres") .replace_crates_io(registry.index_url()) .with_stdout_does_not_contain("[..]\x1b[[..]") .run(); cargo_process("search --color=always postgres") .replace_crates_io(registry.index_url()) .with_stdout_data( "\ ... [..]\x1b[[..] ... ", ) .run(); } #[cargo_test] fn auth_required_failure() { let server = setup().auth_required().no_configure_token().build(); cargo_process("search postgres") .replace_crates_io(server.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index [ERROR] no token found, please run `cargo login` or use environment variable CARGO_REGISTRY_TOKEN "#]]) .run(); } #[cargo_test] fn auth_required() { let server = setup().auth_required().build(); cargo_process("search postgres") .replace_crates_io(server.index_url()) .with_stdout_data(SEARCH_RESULTS) .run(); } ================================================ FILE: tests/testsuite/shell_quoting.rs ================================================ //! This file tests that when the commands being run are shown //! in the output, their arguments are quoted properly //! so that the command can be run in a terminal. use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::str; #[cargo_test] fn features_are_quoted() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" authors = ["mikeyhew@example.com"] edition = "2015" [features] some_feature = [] default = ["some_feature"] "#, ) .file("src/main.rs", "fn main() {error}") .build(); p.cargo("check -v") .env("MSYSTEM", "1") .with_status(101) .with_stderr_data(str![[r#" [CHECKING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] --cfg 'feature="default"' --cfg 'feature="some_feature"' [..]` ... [ERROR] could not compile `foo` (bin "foo") due to 1 previous error Caused by: process didn't exit successfully: [..] --cfg 'feature="default"' --cfg 'feature="some_feature"' [..] "#]]) .run(); } ================================================ FILE: tests/testsuite/source_replacement.rs ================================================ //! Tests for `[source]` table (source replacement). use std::fs; use crate::prelude::*; use crate::utils::cargo_process; use cargo_test_support::registry::{Package, RegistryBuilder, TestRegistry}; use cargo_test_support::{paths, project, str, t}; fn setup_replacement(config: &str) -> TestRegistry { let crates_io = RegistryBuilder::new() .no_configure_registry() .http_api() .build(); let root = paths::root(); t!(fs::create_dir(&root.join(".cargo"))); t!(fs::write(root.join(".cargo/config.toml"), config,)); crates_io } #[cargo_test] fn crates_io_token_not_sent_to_replacement() { // verifies that the crates.io token is not sent to a replacement registry during publish. let crates_io = setup_replacement( r#" [source.crates-io] replace-with = 'alternative' "#, ); let _alternative = RegistryBuilder::new() .alternative() .http_api() .no_configure_token() .build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish --no-verify --registry crates-io") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index ... "#]]) .run(); } #[cargo_test] fn token_sent_to_correct_registry() { // verifies that the crates.io token is not sent to a replacement registry during yank. let crates_io = setup_replacement( r#" [source.crates-io] replace-with = 'alternative' "#, ); let _alternative = RegistryBuilder::new().alternative().http_api().build(); cargo_process("yank foo@0.0.1 --registry crates-io") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [YANK] foo@0.0.1 "#]]) .run(); cargo_process("yank foo@0.0.1 --registry alternative") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [YANK] foo@0.0.1 "#]]) .run(); } #[cargo_test] fn ambiguous_registry() { // verifies that an error is issued when a source-replacement is configured // and no --registry argument is given. let crates_io = setup_replacement( r#" [source.crates-io] replace-with = 'alternative' "#, ); let _alternative = RegistryBuilder::new() .alternative() .http_api() .no_configure_token() .build(); cargo_process("yank foo@0.0.1") .replace_crates_io(crates_io.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] crates-io is replaced with remote registry alternative; include `--registry alternative` or `--registry crates-io` "#]]) .run(); } #[cargo_test] fn yank_with_default_crates_io() { // verifies that no error is given when registry.default is used. let crates_io = setup_replacement( r#" [source.crates-io] replace-with = 'alternative' [registry] default = 'crates-io' "#, ); let _alternative = RegistryBuilder::new().alternative().http_api().build(); cargo_process("yank foo@0.0.1") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [YANK] foo@0.0.1 "#]]) .run(); } #[cargo_test] fn yank_with_default_alternative() { // verifies that no error is given when registry.default is an alt registry. let crates_io = setup_replacement( r#" [source.crates-io] replace-with = 'alternative' [registry] default = 'alternative' "#, ); let _alternative = RegistryBuilder::new().alternative().http_api().build(); cargo_process("yank foo@0.0.1") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [YANK] foo@0.0.1 "#]]) .run(); } #[cargo_test] fn publish_with_replacement() { // verifies that the crates.io token is not sent to a replacement registry during publish. let crates_io = setup_replacement( r#" [source.crates-io] replace-with = 'alternative' "#, ); let _alternative = RegistryBuilder::new() .alternative() .http_api() .no_configure_token() .build(); // Publish bar only to alternative. This tests that the publish verification build // does uses the source replacement. Package::new("bar", "1.0.0").alternative(true).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] license = "MIT" description = "foo" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); // Verifies that the crates.io index is used to find the publishing endpoint // and that the crate is sent to crates.io. The source replacement is only used // for the verification step. p.cargo("publish --registry crates-io") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [WARNING] manifest has no documentation, homepage or repository | = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info [PACKAGING] foo v0.0.1 ([ROOT]/foo) [UPDATING] `alternative` index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `alternative`) [COMPILING] bar v1.0.0 [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.0.1 ([ROOT]/foo) [UPLOADED] foo v0.0.1 to registry `crates-io` [NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.0.1 at registry `crates-io` "#]]) .run(); } #[cargo_test] fn undefined_default() { // verifies that no error is given when registry.default is used. let crates_io = setup_replacement( r#" [registry] default = 'undefined' "#, ); cargo_process("yank foo@0.0.1") .replace_crates_io(crates_io.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] registry index was not found in any configuration: `undefined` "#]]) .run(); } #[cargo_test] fn source_replacement_with_registry_url() { let alternative = RegistryBuilder::new().alternative().http_api().build(); Package::new("bar", "0.0.1").alternative(true).publish(); let crates_io = setup_replacement(&format!( r#" [source.crates-io] replace-with = 'using-registry-url' [source.using-registry-url] registry = '{}' "#, alternative.index_url() )); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies.bar] version = "0.0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .replace_crates_io(crates_io.index_url()) .with_stderr_data(str![[r#" [UPDATING] `using-registry-url` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `using-registry-url`) [CHECKING] bar v0.0.1 [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn source_replacement_with_no_package_in_directory() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2021" [dependencies] bar = { version = "^0.8.9" } "#, ) .file("src/lib.rs", "") .build(); let root = paths::root(); t!(fs::create_dir(&root.join("vendor"))); let crates_io = setup_replacement(&format!( r#" [source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "vendor" "# )); p.cargo("build") .replace_crates_io(crates_io.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] no matching package named `bar` found location searched: directory source `[ROOT]/vendor` (which is replacing registry `crates-io`) required by package `foo v0.1.0 ([ROOT]/foo)` "#]]) .run(); } ================================================ FILE: tests/testsuite/ssh.rs ================================================ //! Network tests for SSH connections. //! //! Note that these tests will generally require setting `CARGO_CONTAINER_TESTS` //! or `CARGO_PUBLIC_NETWORK_TESTS`. //! //! NOTE: The container tests almost certainly won't work on Windows. use std::fs; use std::io::Write; use std::path::PathBuf; use crate::prelude::*; use cargo_test_support::containers::{Container, ContainerHandle, MkFile}; use cargo_test_support::git::cargo_uses_gitoxide; use cargo_test_support::{Project, paths, process, project, str}; fn ssh_repo_url(container: &ContainerHandle, name: &str) -> String { let port = container.port_mappings[&22]; format!("ssh://testuser@127.0.0.1:{port}/repos/{name}.git") } /// The path to the client's private key. fn key_path() -> PathBuf { paths::home().join(".ssh/id_ed25519") } /// Generates the SSH keys for authenticating into the container. fn gen_ssh_keys() -> String { let path = key_path(); process("ssh-keygen") .args(&["-t", "ed25519", "-N", "", "-f"]) .arg(&path) .exec_with_output() .unwrap(); let pub_key = path.with_extension("pub"); fs::read_to_string(pub_key).unwrap() } /// Handler for running ssh-agent for SSH authentication. /// /// Be sure to set `SSH_AUTH_SOCK` when running a process in order to use the /// agent. Keys will need to be copied into the container with the /// `authorized_keys()` method. struct Agent { sock: PathBuf, pid: String, ssh_dir: PathBuf, pub_key: String, } impl Agent { fn launch() -> Agent { let ssh_dir = paths::home().join(".ssh"); fs::create_dir(&ssh_dir).unwrap(); let pub_key = gen_ssh_keys(); let sock = paths::root().join("agent"); let output = process("ssh-agent") .args(&["-s", "-a"]) .arg(&sock) .exec_with_output() .unwrap(); let stdout = std::str::from_utf8(&output.stdout).unwrap(); let start = stdout.find("SSH_AGENT_PID=").unwrap() + 14; let end = &stdout[start..].find(';').unwrap(); let pid = (&stdout[start..start + end]).to_string(); eprintln!("SSH_AGENT_PID={pid}"); process("ssh-add") .arg(key_path()) .env("SSH_AUTH_SOCK", &sock) .exec_with_output() .unwrap(); Agent { sock, pid, ssh_dir, pub_key, } } /// Returns a `MkFile` which can be passed into the `Container` builder to /// copy an `authorized_keys` file containing this agent's public key. fn authorized_keys(&self) -> MkFile { MkFile::path("home/testuser/.ssh/authorized_keys") .contents(self.pub_key.as_bytes()) .mode(0o600) .uid(100) .gid(101) } } impl Drop for Agent { fn drop(&mut self) { if let Err(e) = process("ssh-agent") .args(&["-k", "-a"]) .arg(&self.sock) .env("SSH_AGENT_PID", &self.pid) .exec_with_output() { eprintln!("failed to stop ssh-agent: {e:?}"); } } } /// Common project used for several tests. fn foo_bar_project(url: &str) -> Project { project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = {{ git = "{url}" }} "# ), ) .file("src/lib.rs", "") .build() } #[cargo_test(container_test)] fn no_known_host() { // When host is not known, it should show an error. let sshd = Container::new("sshd").launch(); let url = ssh_repo_url(&sshd, "bar"); let p = foo_bar_project(&url); p.cargo("fetch") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git` [ERROR] failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `bar` Caused by: unable to update ssh://testuser@127.0.0.1:[..]/repos/bar.git Caused by: failed to clone into: [ROOT]/home/.cargo/git/db/bar-[HASH] Caused by: [ERROR] unknown SSH host key The SSH host key for `[127.0.0.1]:[..]` is not known and cannot be validated. To resolve this issue, add the host key to the `net.ssh.known-hosts` array in your Cargo configuration (such as [ROOT]/home/.cargo/config.toml) or in your OpenSSH known_hosts file at [ROOT]/home/.ssh/known_hosts The key to add is: [127.0.0.1]:[..] ecdsa-sha2-nistp256 AAAA[..] The ECDSA key fingerprint is: SHA256:[..] This fingerprint should be validated with the server administrator that it is correct. See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts for more information. "#]]) .run(); } #[cargo_test(container_test)] fn known_host_works() { // The key displayed in the error message should work when added to known_hosts. let agent = Agent::launch(); let sshd = Container::new("sshd") .file(agent.authorized_keys()) .launch(); let url = ssh_repo_url(&sshd, "bar"); let p = foo_bar_project(&url); let output = p .cargo("fetch") .env("SSH_AUTH_SOCK", &agent.sock) .build_command() .output() .unwrap(); let stderr = std::str::from_utf8(&output.stderr).unwrap(); // Validate the fingerprint while we're here. let fingerprint = stderr .lines() .find_map(|line| line.strip_prefix(" The ECDSA key fingerprint is: ")) .unwrap() .trim(); let finger_out = sshd.exec(&["ssh-keygen", "-l", "-f", "/etc/ssh/ssh_host_ecdsa_key.pub"]); let gen_finger = std::str::from_utf8(&finger_out.stdout).unwrap(); // let gen_finger = gen_finger.split_whitespace().nth(1).unwrap(); assert_eq!(fingerprint, gen_finger); // Add the key to known_hosts, and try again. let key = stderr .lines() .find(|line| line.starts_with(" [127.0.0.1]:")) .unwrap() .trim(); fs::write(agent.ssh_dir.join("known_hosts"), key).unwrap(); p.cargo("fetch") .env("SSH_AUTH_SOCK", &agent.sock) .with_stderr_data(str![[r#" [UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git` [LOCKING] 1 package to latest compatible version "#]]) .run(); } #[cargo_test(container_test)] fn same_key_different_hostname() { // The error message should mention if an identical key was found. let agent = Agent::launch(); let sshd = Container::new("sshd").launch(); let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub"); let known_hosts = format!("example.com {hostkey}"); fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap(); let url = ssh_repo_url(&sshd, "bar"); let p = foo_bar_project(&url); p.cargo("fetch") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git` [ERROR] failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `bar` Caused by: unable to update ssh://testuser@127.0.0.1:[..]/repos/bar.git Caused by: failed to clone into: [ROOT]/home/.cargo/git/db/bar-[HASH] Caused by: [ERROR] unknown SSH host key The SSH host key for `[127.0.0.1]:[..]` is not known and cannot be validated. To resolve this issue, add the host key to the `net.ssh.known-hosts` array in your Cargo configuration (such as [ROOT]/home/.cargo/config.toml) or in your OpenSSH known_hosts file at [ROOT]/home/.ssh/known_hosts The key to add is: [127.0.0.1]:[..] ecdsa-sha2-nistp256 AAAA[..] The ECDSA key fingerprint is: SHA256:[..] This fingerprint should be validated with the server administrator that it is correct. Note: This host key was found, but is associated with a different host: [ROOT]/home/.ssh/known_hosts line 1: example.com See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts for more information. "#]]) .run(); } #[cargo_test(container_test)] fn known_host_without_port() { // A known_host entry without a port should match a connection to a non-standard port. let agent = Agent::launch(); let sshd = Container::new("sshd") .file(agent.authorized_keys()) .launch(); let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub"); // The important part of this test is that this line does not have a port. let known_hosts = format!("127.0.0.1 {hostkey}"); fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap(); let url = ssh_repo_url(&sshd, "bar"); let p = foo_bar_project(&url); p.cargo("fetch") .env("SSH_AUTH_SOCK", &agent.sock) .with_stderr_data(str![[r#" [UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git` [LOCKING] 1 package to latest compatible version "#]]) .run(); } #[cargo_test(container_test)] fn hostname_case_insensitive() { // hostname checking should be case-insensitive. let agent = Agent::launch(); let sshd = Container::new("sshd") .file(agent.authorized_keys()) .launch(); // Consider using `gethostname-rs` instead? let hostname = process("hostname").exec_with_output().unwrap(); let hostname = std::str::from_utf8(&hostname.stdout).unwrap().trim(); let inv_hostname = if hostname.chars().any(|c| c.is_lowercase()) { hostname.to_uppercase() } else { // There should be *some* chars in the name. assert!(hostname.chars().any(|c| c.is_uppercase())); hostname.to_lowercase() }; eprintln!("converted {hostname} to {inv_hostname}"); let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub"); let known_hosts = format!("{inv_hostname} {hostkey}"); fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap(); let port = sshd.port_mappings[&22]; let url = format!("ssh://testuser@{hostname}:{port}/repos/bar.git"); let p = foo_bar_project(&url); p.cargo("fetch") .env("SSH_AUTH_SOCK", &agent.sock) .with_stderr_data(&format!( "\ [UPDATING] git repository `ssh://testuser@{hostname}:{port}/repos/bar.git` [LOCKING] 1 package to latest compatible version " )) .run(); } #[cargo_test(container_test)] fn invalid_key_error() { // An error when a known_host value doesn't match. let agent = Agent::launch(); let sshd = Container::new("sshd") .file(agent.authorized_keys()) .launch(); let port = sshd.port_mappings[&22]; let known_hosts = format!( "[127.0.0.1]:{port} ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLqLMclVr7MDuaVsm3sEnnq2OrGxTFiHSw90wd6N14BU8xVC9cZldC3rJ58Wmw6bEVKPjk7foNG0lHwS5bCKX+U=\n" ); fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap(); let url = ssh_repo_url(&sshd, "bar"); let p = foo_bar_project(&url); p.cargo("fetch") .env("SSH_AUTH_SOCK", &agent.sock) .with_status(101) .with_stderr_data(&format!("\ [UPDATING] git repository `ssh://testuser@127.0.0.1:{port}/repos/bar.git` [ERROR] failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `bar` Caused by: unable to update ssh://testuser@127.0.0.1:{port}/repos/bar.git Caused by: failed to clone into: [ROOT]/home/.cargo/git/db/bar-[HASH] Caused by: [ERROR] SSH host key has changed for `[127.0.0.1]:{port}` ********************************* * WARNING: HOST KEY HAS CHANGED * ********************************* This may be caused by a man-in-the-middle attack, or the server may have changed its host key. The ECDSA fingerprint for the key from the remote host is: SHA256:[..] You are strongly encouraged to contact the server administrator for `[127.0.0.1]:{port}` to verify that this new key is correct. If you can verify that the server has a new key, you can resolve this error by removing the old ecdsa-sha2-nistp256 key for `[127.0.0.1]:{port}` located at [ROOT]/home/.ssh/known_hosts line 1, and adding the new key to the `net.ssh.known-hosts` array in your Cargo configuration (such as [ROOT]/home/.cargo/config.toml) or in your OpenSSH known_hosts file at [ROOT]/home/.ssh/known_hosts The key provided by the remote host is: [127.0.0.1]:{port} ecdsa-sha2-nistp256 [..] See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts for more information. ")) .run(); // Add the key, it should work even with the old key left behind. let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub"); let known_hosts_path = agent.ssh_dir.join("known_hosts"); let mut f = fs::OpenOptions::new() .append(true) .open(known_hosts_path) .unwrap(); write!(f, "[127.0.0.1]:{port} {hostkey}").unwrap(); drop(f); p.cargo("fetch") .env("SSH_AUTH_SOCK", &agent.sock) .with_stderr_data(str![[r#" [UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git` [LOCKING] 1 package to latest compatible version "#]]) .run(); } // For unknown reasons, this test occasionally fails on Windows with a // LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE error: // failed to start SSH session: Unable to exchange encryption keys; class=Ssh (23) #[cargo_test(public_network_test, ignore_windows = "test is flaky on windows")] fn invalid_github_key() { // A key for github.com in known_hosts should override the built-in key. // This uses a bogus key which should result in an error. let ssh_dir = paths::home().join(".ssh"); fs::create_dir(&ssh_dir).unwrap(); let known_hosts = "\ github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLqLMclVr7MDuaVsm3sEnnq2OrGxTFiHSw90wd6N14BU8xVC9cZldC3rJ58Wmw6bEVKPjk7foNG0lHwS5bCKX+U=\n\ github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDgi+8rMcyFCBq5y7BXrb2aaYGhMjlU3QDy7YDvtNL5KSecYOsaqQHaXr87Bbx0EEkgbhK4kVMkmThlCoNITQS9Vc3zIMQ+Tg6+O4qXx719uCzywl50Tb5tDqPGMj54jcq3VUiu/dvse0yeehyvzoPNWewgGWLx11KI4A4wOwMnc6guhculEWe9DjGEjUQ34lPbmdfu/Hza7ZVu/RhgF/wc43uzXWB2KpMEqtuY1SgRlCZqTASoEtfKZi0AuM7AEdOwE5aTotS4CQZHWimb1bMFpF4DAq92CZ8Jhrm4rWETbO29WmjviCJEA3KNQyd3oA7H9AE9z/22PJaVEmjiZZ+wyLgwyIpOlsnHYNEdGeQMQ4SgLRkARLwcnKmByv1AAxsBW4LI3Os4FpwxVPdXHcBebydtvxIsbtUVkkq99nbsIlnSRFSTvb0alrdzRuKTdWpHtN1v9hagFqmeCx/kJfH76NXYBbtaWZhSOnxfEbhLYuOb+IS4jYzHAIkzy9FjVuk=\n\ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEeMB6BUAW6FfvfLxRO3kGASe0yXnrRT4kpqncsup2b2\n"; fs::write(ssh_dir.join("known_hosts"), known_hosts).unwrap(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bitflags = { git = "ssh://git@github.com/rust-lang/bitflags.git", tag = "1.3.2" } "#, ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_status(101) .with_stderr_data(if cargo_uses_gitoxide() { str![[r#" ... git@github.com: Permission denied (publickey). ... "#]] } else { str![[r#" ... [ERROR] SSH host key has changed for `github.com` ... "#]] }) .run(); } // For unknown reasons, this test occasionally fails on Windows with a // LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE error: // failed to start SSH session: Unable to exchange encryption keys; class=Ssh (23) #[cargo_test(public_network_test, ignore_windows = "test is flaky on windows")] fn bundled_github_works() { // The bundled key for github.com works. // // Use a bogus auth sock to force an authentication error. // On Windows, if the agent service is running, it could allow a // successful authentication. // // If the bundled hostkey did not work, it would result in an "unknown SSH // host key" instead. let bogus_auth_sock = paths::home().join("ssh_auth_sock"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bitflags = { git = "ssh://git@github.com/rust-lang/bitflags.git", tag = "1.3.2" } "#, ) .file("src/lib.rs", "") .build(); let expected = if cargo_uses_gitoxide() { str![[r#" [UPDATING] git repository `ssh://git@github.com/rust-lang/bitflags.git` [ERROR] failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `bitflags` Caused by: unable to update ssh://git@github.com/rust-lang/bitflags.git?tag=1.3.2 Caused by: failed to clone into: [ROOT]/home/.cargo/git/db/bitflags-[HASH] Caused by: failed to authenticate when downloading repository * attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect if the git CLI succeeds then `net.git-fetch-with-cli` may help here https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli Caused by: [CREDENTIAL]s provided for "ssh://git@github.com/rust-lang/bitflags.git" were not accepted by the remote Caused by: git@github.com: Permission denied (publickey). "#]] } else { str![[r#" [UPDATING] git repository `ssh://git@github.com/rust-lang/bitflags.git` [ERROR] failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `bitflags` Caused by: unable to update ssh://git@github.com/rust-lang/bitflags.git?tag=1.3.2 Caused by: failed to clone into: [ROOT]/home/.cargo/git/db/bitflags-[HASH] Caused by: failed to authenticate when downloading repository * attempted ssh-agent authentication, but no usernames succeeded: `git` if the git CLI succeeds then `net.git-fetch-with-cli` may help here https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli Caused by: no authentication methods succeeded "#]] }; p.cargo("fetch") .env("SSH_AUTH_SOCK", &bogus_auth_sock) .with_status(101) .with_stderr_data(expected) .run(); let expected = if cargo_uses_gitoxide() { str![[r#" [UPDATING] git repository `ssh://git@github.com:22/rust-lang/bitflags.git` [ERROR] failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `bitflags` Caused by: unable to update ssh://git@github.com:22/rust-lang/bitflags.git?tag=1.3.2 Caused by: failed to clone into: [ROOT]/home/.cargo/git/db/bitflags-[HASH] Caused by: failed to authenticate when downloading repository * attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect if the git CLI succeeds then `net.git-fetch-with-cli` may help here https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli Caused by: [CREDENTIAL]s provided for "ssh://git@github.com:22/rust-lang/bitflags.git" were not accepted by the remote Caused by: git@github.com: Permission denied (publickey). "#]] } else { str![[r#" [UPDATING] git repository `ssh://git@github.com:22/rust-lang/bitflags.git` [ERROR] failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `bitflags` Caused by: unable to update ssh://git@github.com:22/rust-lang/bitflags.git?tag=1.3.2 Caused by: failed to clone into: [ROOT]/home/.cargo/git/db/bitflags-[HASH] Caused by: failed to authenticate when downloading repository * attempted ssh-agent authentication, but no usernames succeeded: `git` if the git CLI succeeds then `net.git-fetch-with-cli` may help here https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli Caused by: no authentication methods succeeded "#]] }; // Explicit :22 should also work with bundled. p.change_file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bitflags = { git = "ssh://git@github.com:22/rust-lang/bitflags.git", tag = "1.3.2" } "#, ); p.cargo("fetch") .env("SSH_AUTH_SOCK", &bogus_auth_sock) .with_status(101) .with_stderr_data(expected) .run(); } #[cargo_test(container_test)] fn ssh_key_in_config() { // known_host in config works. let agent = Agent::launch(); let sshd = Container::new("sshd") .file(agent.authorized_keys()) .launch(); let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub"); let url = ssh_repo_url(&sshd, "bar"); let p = foo_bar_project(&url); p.change_file( ".cargo/config.toml", &format!( r#" [net.ssh] known-hosts = ['127.0.0.1 {}'] "#, hostkey.trim() ), ); p.cargo("fetch") .env("SSH_AUTH_SOCK", &agent.sock) .with_stderr_data(str![[r#" [UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git` [LOCKING] 1 package to latest compatible version "#]]) .run(); } ================================================ FILE: tests/testsuite/standard_lib.rs ================================================ //! Tests for building the standard library (-Zbuild-std). //! //! These tests all use a "mock" standard library so that we don't have to //! rebuild the real one. There is a separate integration test `build-std` //! which builds the real thing, but that should be avoided if possible. use std::path::{Path, PathBuf}; use crate::prelude::*; use cargo_test_support::ProjectBuilder; use cargo_test_support::cross_compile; use cargo_test_support::registry::{Dependency, Package}; use cargo_test_support::{Execs, paths, project, rustc_host, str}; struct Setup { rustc_wrapper: PathBuf, real_sysroot: String, } fn setup() -> Setup { // Our mock sysroot requires a few packages from crates.io, so make sure // they're "published" to crates.io. Also edit their code a bit to make sure // that they have access to our custom crates with custom apis. Package::new("registry-dep-using-core", "1.0.0") .file( "src/lib.rs", " #![no_std] #[cfg(feature = \"mockbuild\")] pub fn custom_api() { } #[cfg(not(feature = \"mockbuild\"))] pub fn non_sysroot_api() { core::custom_api(); } ", ) .add_dep(Dependency::new("rustc-std-workspace-core", "*").optional(true)) .feature("mockbuild", &["rustc-std-workspace-core"]) .publish(); Package::new("registry-dep-using-alloc", "1.0.0") .file( "src/lib.rs", " #![no_std] extern crate alloc; #[cfg(feature = \"mockbuild\")] pub fn custom_api() { } #[cfg(not(feature = \"mockbuild\"))] pub fn non_sysroot_api() { core::custom_api(); alloc::custom_api(); } ", ) .add_dep(Dependency::new("rustc-std-workspace-core", "*").optional(true)) .add_dep(Dependency::new("rustc-std-workspace-alloc", "*").optional(true)) .feature( "mockbuild", &["rustc-std-workspace-core", "rustc-std-workspace-alloc"], ) .publish(); Package::new("registry-dep-using-std", "1.0.0") .file( "src/lib.rs", " #[cfg(feature = \"mockbuild\")] pub fn custom_api() { } #[cfg(not(feature = \"mockbuild\"))] pub fn non_sysroot_api() { std::custom_api(); } ", ) .add_dep(Dependency::new("rustc-std-workspace-std", "*").optional(true)) .feature("mockbuild", &["rustc-std-workspace-std"]) .publish(); let p = ProjectBuilder::new(paths::root().join("rustc-wrapper")) .file( "src/main.rs", &r#" use std::process::Command; use std::env; fn main() { let mut args = env::args().skip(1).collect::>(); let is_sysroot_crate = env::var_os("RUSTC_BOOTSTRAP").is_some(); if is_sysroot_crate { args.push("--sysroot".to_string()); args.push(env::var("REAL_SYSROOT").unwrap()); } else if let Some(pos) = args.iter().position(|arg| arg == "--target") { // build-std target unit // Set --sysroot only when the target is host if args.iter().nth(pos + 1) == Some(&"__HOST_TARGET__".to_string()) { // This `--sysroot` is here to disable the sysroot lookup, // to ensure nothing is required. // See https://github.com/rust-lang/wg-cargo-std-aware/issues/31 // for more information on this. args.push("--sysroot".to_string()); args.push("/path/to/nowhere".to_string()); } } else { // host unit, do not use sysroot } let ret = Command::new(&args[0]).args(&args[1..]).status().unwrap(); std::process::exit(ret.code().unwrap_or(1)); } "# .replace("__HOST_TARGET__", rustc_host()), ) .build(); p.cargo("build").run(); Setup { rustc_wrapper: p.bin("foo"), real_sysroot: paths::sysroot(), } } fn enable_build_std(e: &mut Execs, setup: &Setup) { // First up, force Cargo to use our "mock sysroot" which mimics what // libstd looks like upstream. let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); e.env("__CARGO_TESTS_ONLY_SRC_ROOT", &root); e.masquerade_as_nightly_cargo(&["build-std"]); // We do various shenanigans to ensure our "mock sysroot" actually links // with the real sysroot, so we don't have to actually recompile std for // each test. Perform all that logic here, namely: // // * RUSTC_WRAPPER - uses our shim executable built above to control rustc // * REAL_SYSROOT - used by the shim executable to swap out to the real // sysroot temporarily for some compilations // * RUST{,DOC}FLAGS - an extra `-L` argument to ensure we can always load // crates from the sysroot, but only indirectly through other crates. e.env("RUSTC_WRAPPER", &setup.rustc_wrapper); e.env("REAL_SYSROOT", &setup.real_sysroot); let libdir = format!("/lib/rustlib/{}/lib", rustc_host()); e.env( "RUSTFLAGS", format!("-Ldependency={}{}", setup.real_sysroot, libdir), ); e.env( "RUSTDOCFLAGS", format!("-Ldependency={}{}", setup.real_sysroot, libdir), ); } // Helper methods used in the tests below trait BuildStd: Sized { fn build_std(&mut self, setup: &Setup) -> &mut Self; fn build_std_arg(&mut self, setup: &Setup, arg: &str) -> &mut Self; fn target_host(&mut self) -> &mut Self; } impl BuildStd for Execs { fn build_std(&mut self, setup: &Setup) -> &mut Self { enable_build_std(self, setup); self.arg("-Zbuild-std"); self } fn build_std_arg(&mut self, setup: &Setup, arg: &str) -> &mut Self { enable_build_std(self, setup); self.arg(format!("-Zbuild-std={}", arg)); self } fn target_host(&mut self) -> &mut Self { self.arg("--target").arg(rustc_host()); self } } #[cargo_test(build_std_mock)] fn basic() { let setup = setup(); let p = project() .file( "src/main.rs", " fn main() { std::custom_api(); foo::f(); } #[test] fn smoke_bin_unit() { std::custom_api(); foo::f(); } ", ) .file( "src/lib.rs", " extern crate alloc; extern crate proc_macro; /// ``` /// foo::f(); /// ``` pub fn f() { core::custom_api(); std::custom_api(); alloc::custom_api(); proc_macro::custom_api(); } #[test] fn smoke_lib_unit() { std::custom_api(); f(); } ", ) .file( "tests/smoke.rs", " #[test] fn smoke_integration() { std::custom_api(); foo::f(); } ", ) .build(); p.cargo("check -v").build_std(&setup).target_host().run(); p.cargo("build").build_std(&setup).target_host().run(); p.cargo("run").build_std(&setup).target_host().run(); p.cargo("test").build_std(&setup).target_host().run(); } #[cargo_test(build_std_mock)] fn shared_std_dependency_rebuild() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let setup = setup(); let p = project() .file( "Cargo.toml", format!( " [package] name = \"foo\" version = \"0.1.0\" edition = \"2021\" [build-dependencies] dep_test = {{ path = \"{}/tests/testsuite/mock-std/dep_test\" }} ", manifest_dir.replace('\\', "/") ) .as_str(), ) .file( "src/main.rs", r#" fn main() { println!("Hello, World!"); } "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rerun-if-changed=build.rs"); } "#, ) .build(); p.cargo("build -v") .build_std(&setup) .target_host() .with_stderr_data(str![[r#" ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... "#]]) .run(); p.cargo("build -v") .build_std(&setup) .with_stderr_does_not_contain(str![[r#" ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... "#]]) .run(); } #[cargo_test(build_std_mock)] fn simple_lib_std() { let setup = setup(); let p = project().file("src/lib.rs", "").build(); p.cargo("build -v") .build_std(&setup) .target_host() .with_stderr_data(str![[r#" ... [RUNNING] `[..] rustc --crate-name std [..]` ... "#]]) .run(); // Check freshness. p.change_file("src/lib.rs", " "); p.cargo("build -v") .build_std(&setup) .target_host() .with_stderr_data(str![[r#" ... [FRESH] std v0.1.0 ([..]/tests/testsuite/mock-std/library/std) ... "#]]) .run(); } #[cargo_test(build_std_mock)] fn simple_bin_std() { let setup = setup(); let p = project().file("src/main.rs", "fn main() {}").build(); p.cargo("run -v").build_std(&setup).target_host().run(); } #[cargo_test(build_std_mock)] fn lib_nostd() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" #![no_std] pub fn foo() { assert_eq!(u8::MIN, 0); } "#, ) .build(); p.cargo("build -v --lib") .build_std_arg(&setup, "core") .target_host() .with_stderr_does_not_contain("[..]libstd[..]") .run(); } #[cargo_test(build_std_mock)] fn check_core() { let setup = setup(); let p = project() .file("src/lib.rs", "#![no_std] fn unused_fn() {}") .build(); p.cargo("check -v") .build_std_arg(&setup, "core") .target_host() .with_stderr_data(str![[r#" ... [WARNING] function `unused_fn` is never used ... "#]]) .run(); } #[cargo_test(build_std_mock)] fn build_std_with_no_arg_for_core_only_target() { let target = "aarch64-unknown-none"; if !cross_compile::requires_target_installed(target) { return; } let setup = setup(); let p = project() .file( "src/lib.rs", r#" #![no_std] pub fn foo() { assert_eq!(u8::MIN, 0); } "#, ) .build(); p.cargo("build -v") .arg("--target") .arg(target) .build_std(&setup) .with_stderr_data( str![[r#" [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] registry-dep-using-std v1.0.0 (registry `dummy-registry`) [DOWNLOADED] registry-dep-using-core v1.0.0 (registry `dummy-registry`) [DOWNLOADED] registry-dep-using-alloc v1.0.0 (registry `dummy-registry`) [COMPILING] compiler_builtins v0.1.0 ([..]/library/compiler_builtins) [COMPILING] core v0.1.0 ([..]/library/core) [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `[..] rustc --crate-name build_script_build [..]/compiler_builtins/build.rs [..]` [RUNNING] `[ROOT]/foo/target/debug/build/compiler_builtins-[HASH]/build-script-build` [RUNNING] `[..] rustc --crate-name compiler_builtins [..]--target aarch64-unknown-none[..]` [RUNNING] `[..] rustc --crate-name core [..]--target aarch64-unknown-none[..]` [RUNNING] `[..] rustc --crate-name foo [..]--target aarch64-unknown-none[..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); p.cargo("clean").run(); // Also work for a mix of std and core-only targets, // though not sure how common it is... // // Note that we don't download std dependencies for the second call // because `-Zbuild-std` downloads them all also when building for core only. p.cargo("build -v") .arg("--target") .arg(target) .target_host() .build_std(&setup) .with_stderr_data( str![[r#" [UPDATING] `dummy-registry` index [COMPILING] core v0.1.0 ([..]/library/core) [COMPILING] dep_test v0.1.0 ([..]/dep_test) [COMPILING] compiler_builtins v0.1.0 ([..]/library/compiler_builtins) [COMPILING] proc_macro v0.1.0 ([..]/library/proc_macro) [COMPILING] panic_unwind v0.1.0 ([..]/library/panic_unwind) [COMPILING] rustc-std-workspace-core v1.9.0 ([..]/library/rustc-std-workspace-core) [COMPILING] foo v0.0.1 ([ROOT]/foo) [COMPILING] registry-dep-using-core v1.0.0 [COMPILING] alloc v0.1.0 ([..]/library/alloc) [COMPILING] rustc-std-workspace-alloc v1.9.0 ([..]/library/rustc-std-workspace-alloc) [COMPILING] registry-dep-using-alloc v1.0.0 [COMPILING] std v0.1.0 ([..]/library/std) [RUNNING] `[..] rustc --crate-name build_script_build [..]/compiler_builtins/build.rs [..]` [RUNNING] `[ROOT]/foo/target/debug/build/compiler_builtins-[HASH]/build-script-build` [RUNNING] `[ROOT]/foo/target/debug/build/compiler_builtins-[HASH]/build-script-build` [RUNNING] `[..]rustc --crate-name compiler_builtins [..]--target aarch64-unknown-none[..]` [RUNNING] `[..]rustc --crate-name core [..]--target aarch64-unknown-none[..]` [RUNNING] `[..]rustc --crate-name foo [..]--target aarch64-unknown-none[..]` [RUNNING] `[..]rustc --crate-name core [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name dep_test [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name proc_macro [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name panic_unwind [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name compiler_builtins [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name rustc_std_workspace_core [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name registry_dep_using_core [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name alloc [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name rustc_std_workspace_alloc [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name registry_dep_using_alloc [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name std [..]--target [HOST_TARGET][..]` [RUNNING] `[..]rustc --crate-name foo [..]--target [HOST_TARGET][..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); } #[cargo_test(build_std_mock)] fn depend_same_as_std() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" pub fn f() { registry_dep_using_core::non_sysroot_api(); registry_dep_using_alloc::non_sysroot_api(); registry_dep_using_std::non_sysroot_api(); } "#, ) .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [dependencies] registry-dep-using-core = "1.0" registry-dep-using-alloc = "1.0" registry-dep-using-std = "1.0" "#, ) .build(); p.cargo("build -v").build_std(&setup).target_host().run(); } #[cargo_test(build_std_mock)] fn test() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } } "#, ) .build(); p.cargo("test -v") .build_std(&setup) .target_host() .with_stdout_data(str![[r#" running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s ... "#]]) .run(); } #[cargo_test(build_std_mock)] fn target_proc_macro() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" extern crate proc_macro; pub fn f() { let _ts = proc_macro::TokenStream::new(); } "#, ) .build(); p.cargo("build -v").build_std(&setup).target_host().run(); } #[cargo_test(build_std_mock)] fn bench() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" #![feature(test)] extern crate test; #[bench] fn b1(b: &mut test::Bencher) { b.iter(|| ()) } "#, ) .build(); p.cargo("bench -v").build_std(&setup).target_host().run(); } #[cargo_test(build_std_mock)] fn doc() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" /// Doc pub fn f() -> Result<(), ()> {Ok(())} "#, ) .build(); p.cargo("doc -v").build_std(&setup).target_host().run(); } #[cargo_test(build_std_mock)] fn check_std() { let setup = setup(); let p = project() .file( "src/lib.rs", " extern crate core; extern crate alloc; extern crate proc_macro; pub fn f() {} ", ) .file("src/main.rs", "fn main() {}") .file( "tests/t1.rs", r#" #[test] fn t1() { assert_eq!(1, 2); } "#, ) .build(); p.cargo("check -v --all-targets") .build_std(&setup) .target_host() .run(); p.cargo("check -v --all-targets --profile=test") .build_std(&setup) .target_host() .run(); } #[cargo_test(build_std_mock)] fn doctest() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" /// Doc /// ``` /// std::custom_api(); /// ``` pub fn f() {} "#, ) .build(); p.cargo("test --doc -v") .build_std(&setup) .with_stdout_data(str![[r#" running 1 test test src/lib.rs - f (line 3) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .target_host() .run(); } #[cargo_test(build_std_mock)] fn no_implicit_alloc() { // Demonstrate that alloc is not implicitly in scope. let setup = setup(); let p = project() .file( "src/lib.rs", r#" pub fn f() { let _: Vec = alloc::vec::Vec::new(); } "#, ) .build(); p.cargo("build -v") .build_std(&setup) .target_host() .with_stderr_data(str![[r#" ... error[E0433]: cannot find module or crate `alloc` in this scope ... "#]]) .with_status(101) .run(); } #[cargo_test(build_std_mock)] fn macro_expanded_shadow() { // This tests a bug caused by the previous use of `--extern` to directly // load sysroot crates. This necessitated the switch to `--sysroot` to // retain existing behavior. See // https://github.com/rust-lang/wg-cargo-std-aware/issues/40 for more // detail. let setup = setup(); let p = project() .file( "src/lib.rs", r#" macro_rules! a { () => (extern crate std as alloc;) } a!(); "#, ) .build(); p.cargo("build -v").build_std(&setup).target_host().run(); } #[cargo_test(build_std_mock)] fn ignores_incremental() { // Incremental is not really needed for std, make sure it is disabled. // Incremental also tends to have bugs that affect std libraries more than // any other crate. let setup = setup(); let p = project().file("src/lib.rs", "").build(); p.cargo("build") .env("CARGO_INCREMENTAL", "1") .build_std(&setup) .target_host() .run(); let incremental: Vec<_> = p .glob(format!("target/{}/debug/incremental/*", rustc_host())) .map(|e| e.unwrap()) .collect(); assert_eq!(incremental.len(), 1); assert!( incremental[0] .file_name() .unwrap() .to_str() .unwrap() .starts_with("foo-") ); } #[cargo_test(build_std_mock)] fn cargo_config_injects_compiler_builtins() { let setup = setup(); let p = project() .file( "src/lib.rs", r#" #![no_std] pub fn foo() { assert_eq!(u8::MIN, 0); } "#, ) .file( ".cargo/config.toml", r#" [unstable] build-std = ['core'] "#, ) .build(); let mut build = p.cargo("build -v --lib"); enable_build_std(&mut build, &setup); build .target_host() .with_stderr_does_not_contain("[..]libstd[..]") .run(); } #[cargo_test(build_std_mock)] fn different_features() { let setup = setup(); let p = project() .file( "src/lib.rs", " pub fn foo() { std::conditional_function(); } ", ) .build(); p.cargo("build") .build_std(&setup) .arg("-Zbuild-std-features=feature1") .target_host() .run(); } #[cargo_test(build_std_mock)] fn no_roots() { // Checks for a bug where it would panic if there are no roots. let setup = setup(); let p = project().file("tests/t1.rs", "").build(); p.cargo("build") .build_std(&setup) .target_host() .with_stderr_data(str![[r#" ... [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(build_std_mock)] fn proc_macro_only() { // Checks for a bug where it would panic if building a proc-macro only let setup = setup(); let p = project() .file( "Cargo.toml", r#" [package] name = "pm" version = "0.1.0" [lib] proc-macro = true "#, ) .file("src/lib.rs", "") .build(); p.cargo("build") .build_std(&setup) .target_host() .with_stderr_data(str![[r#" ... [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test(build_std_mock)] fn fetch() { let setup = setup(); let p = project().file("src/main.rs", "fn main() {}").build(); p.cargo("fetch") .build_std(&setup) .target_host() .with_stderr_contains("[DOWNLOADED] [..]") .run(); p.cargo("build") .build_std(&setup) .target_host() .with_stderr_does_not_contain("[DOWNLOADED] [..]") .run(); } #[cargo_test(build_std_mock)] fn std_build_script_metadata_propagate_to_user() { let setup = setup(); let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert_eq!(std::env::var("DEP_COMPILER_RT_COMPILER_RT").unwrap(), "foo"); } "#, ) .build(); p.cargo("check").build_std(&setup).target_host().run(); } ================================================ FILE: tests/testsuite/test.rs ================================================ //! Tests for the `cargo test` command. use std::fs; use crate::prelude::*; use crate::utils::cargo_exe; use cargo_test_support::registry::Package; use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, basic_manifest, project, str}; use cargo_test_support::{cross_compile, paths}; use cargo_test_support::{rustc_host, rustc_host_env, sleep_ms}; use cargo_util::paths::dylib_path_envvar; use crate::utils::cross_compile::can_run_on_host as cross_compile_can_run_on_host; #[cargo_test] fn cargo_test_simple() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file( "src/main.rs", r#" fn hello() -> &'static str { "hello" } pub fn main() { println!("{}", hello()) } #[test] fn test_hello() { assert_eq!(hello(), "hello") } "#, ) .build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); p.process(&p.bin("foo")) .with_stdout_data(str![[r#" hello "#]]) .run(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/main.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test_hello ... ok ... "#]]) .run(); } #[cargo_test] fn cargo_test_release() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" authors = [] version = "0.1.0" edition = "2015" [dependencies] bar = { path = "bar" } "#, ) .file( "src/lib.rs", r#" extern crate bar; pub fn foo() { bar::bar(); } #[test] fn test() { foo(); } "#, ) .file( "tests/test.rs", r#" extern crate foo; #[test] fn test() { foo::foo(); } "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) .file("bar/src/lib.rs", "pub fn bar() {}") .build(); p.cargo("test -v --release") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] bar v0.0.1 ([ROOT]/foo/bar) [RUNNING] `rustc [..]-C opt-level=3 [..]` [COMPILING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..]-C opt-level=3 [..]` [RUNNING] `rustc [..]-C opt-level=3 [..]` [RUNNING] `rustc [..]-C opt-level=3 [..]` [FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/foo/target/release/deps/foo-[HASH][EXE]` [RUNNING] `[ROOT]/foo/target/release/deps/test-[HASH][EXE]` [DOCTEST] foo [RUNNING] `rustdoc [..]--test src/lib.rs[..]` "#]]) .with_stdout_data( str![[r#" test test ... ok test test ... ok running 0 tests ... "#]] .unordered(), ) .run(); } #[cargo_test] fn cargo_test_overflow_checks() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = [] [[bin]] name = "foo" [profile.release] overflow-checks = true "#, ) .file( "src/foo.rs", r#" use std::panic; pub fn main() { let r = panic::catch_unwind(|| { [1, i32::MAX].iter().sum::(); }); assert!(r.is_err()); } "#, ) .build(); p.cargo("build --release").run(); assert!(p.release_bin("foo").is_file()); p.process(&p.release_bin("foo")).with_stdout_data("").run(); } #[cargo_test] fn cargo_test_quiet_with_harness() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [[test]] name = "foo" path = "src/foo.rs" harness = true "#, ) .file( "src/foo.rs", r#" fn main() {} #[test] fn test_hello() {} "#, ) .build(); p.cargo("test -q") .with_stdout_data(str![[r#" running 1 test . test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .with_stderr_data("") .run(); } #[cargo_test] fn cargo_test_quiet_no_harness() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [[bin]] name = "foo" test = false [[test]] name = "foo" path = "src/main.rs" harness = false "#, ) .file( "src/main.rs", r#" fn main() {} #[test] fn test_hello() {} "#, ) .build(); p.cargo("test -q") .with_stdout_data("") .with_stderr_data("") .run(); } #[cargo_test] fn cargo_doc_test_quiet() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] "#, ) .file( "src/lib.rs", r#" /// ``` /// let result = foo::add(2, 3); /// assert_eq!(result, 5); /// ``` pub fn add(a: i32, b: i32) -> i32 { a + b } /// ``` /// let result = foo::div(10, 2); /// assert_eq!(result, 5); /// ``` /// /// # Panics /// /// The function panics if the second argument is zero. /// /// ```rust,should_panic /// // panics on division by zero /// foo::div(10, 0); /// ``` pub fn div(a: i32, b: i32) -> i32 { if b == 0 { panic!("Divide-by-zero error"); } a / b } #[test] fn test_hello() {} "#, ) .build(); p.cargo("test -q") .with_stdout_data(str![[r#" running 1 test . test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s running 3 tests ... test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .with_stderr_data("") .run(); } #[cargo_test] fn cargo_test_verbose() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file( "src/main.rs", r#" fn main() {} #[test] fn test_hello() {} "#, ) .build(); p.cargo("test -v hello") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc [..] src/main.rs [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE] hello` "#]]) .with_stdout_data(str![[r#" ... test test_hello ... ok ... "#]]) .run(); } #[cargo_test] fn many_similar_names() { let p = project() .file( "src/lib.rs", " pub fn foo() {} #[test] fn lib_test() {} ", ) .file( "src/main.rs", " extern crate foo; fn main() {} #[test] fn bin_test() { foo::foo() } ", ) .file( "tests/foo.rs", r#" extern crate foo; #[test] fn test_test() { foo::foo() } "#, ) .build(); p.cargo("test -v") .with_stdout_data( str![[r#" test bin_test ... ok test lib_test ... ok test test_test ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn cargo_test_failing_test_in_bin() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file( "src/main.rs", r#" fn hello() -> &'static str { "hello" } pub fn main() { println!("{}", hello()) } #[test] fn test_hello() { assert_eq!(hello(), "nope", "NOPE!") } "#, ) .build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); p.process(&p.bin("foo")) .with_stdout_data(str![[r#" hello "#]]) .run(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/main.rs (target/debug/deps/foo-[HASH][EXE]) [ERROR] test failed, to rerun pass `--bin foo` "#]]) .with_stdout_data("...\n[..]NOPE![..]\n...") .with_status(101) .run(); } #[cargo_test] fn cargo_test_failing_test_in_test() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/main.rs", r#"pub fn main() { println!("hello"); }"#) .file( "tests/footest.rs", r#"#[test] fn test_hello() { assert!(false, "FALSE!") }"#, ) .build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); p.process(&p.bin("foo")) .with_stdout_data(str![[r#" hello "#]]) .run(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/main.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] tests/footest.rs (target/debug/deps/footest-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test footest` "#]]) .with_stdout_data( str![[r#" ... running 0 tests ... running 1 test test test_hello ... FAILED ... [..]FALSE![..] ... "#]] .unordered(), ) .with_status(101) .run(); } #[cargo_test] fn cargo_test_failing_test_in_lib() { let p = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file( "src/lib.rs", r#"#[test] fn test_hello() { assert!(false, "FALSE!") }"#, ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [ERROR] test failed, to rerun pass `--lib` "#]]) .with_stdout_data(str![[r#" ... test test_hello ... FAILED ... [..]FALSE![..] ... "#]]) .with_status(101) .run(); } #[cargo_test] fn test_with_lib_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "baz" path = "src/main.rs" "#, ) .file( "src/lib.rs", r#" /// /// ```rust /// extern crate foo; /// fn main() { /// println!("{:?}", foo::foo()); /// } /// ``` /// pub fn foo(){} #[test] fn lib_test() {} "#, ) .file( "src/main.rs", " #[allow(unused_extern_crates)] extern crate foo; fn main() {} #[test] fn bin_test() {} ", ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] unittests src/main.rs (target/debug/deps/baz-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test lib_test ... ok test bin_test ... ok test [..] ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn test_with_deep_lib_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.bar] path = "../bar" "#, ) .file( "src/lib.rs", " #[cfg(test)] extern crate bar; /// ``` /// foo::foo(); /// ``` pub fn foo() {} #[test] fn bar_test() { bar::bar(); } ", ) .build(); let _p2 = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) .file("src/lib.rs", "pub fn bar() {} #[test] fn foo_test() {}") .build(); p.cargo("test") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] bar v0.0.1 ([ROOT]/bar) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test bar_test ... ok test [..] ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn external_test_explicit() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[test]] name = "test" path = "src/test.rs" "#, ) .file( "src/lib.rs", r#" pub fn get_hello() -> &'static str { "Hello" } #[test] fn internal_test() {} "#, ) .file( "src/test.rs", r#" extern crate foo; #[test] fn external_test() { assert_eq!(foo::get_hello(), "Hello") } "#, ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] src/test.rs (target/debug/deps/test-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test internal_test ... ok test external_test ... ok running 0 tests ... "#]] .unordered(), ) .run(); } #[cargo_test] fn external_test_named_test() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[test]] name = "test" "#, ) .file("src/lib.rs", "") .file("tests/test.rs", "#[test] fn foo() {}") .build(); p.cargo("test").run(); } #[cargo_test] fn external_test_implicit() { let p = project() .file( "src/lib.rs", r#" pub fn get_hello() -> &'static str { "Hello" } #[test] fn internal_test() {} "#, ) .file( "tests/external.rs", r#" extern crate foo; #[test] fn external_test() { assert_eq!(foo::get_hello(), "Hello") } "#, ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] tests/external.rs (target/debug/deps/external-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test internal_test ... ok test external_test ... ok running 0 tests ... "#]] .unordered(), ) .run(); } #[cargo_test] fn dont_run_examples() { let p = project() .file("src/lib.rs", "") .file( "examples/dont-run-me-i-will-fail.rs", r#" fn main() { panic!("Examples should not be run by 'cargo test'"); } "#, ) .build(); p.cargo("test").run(); } #[cargo_test] fn pass_through_escaped() { let p = project() .file( "src/lib.rs", " /// ```rust /// assert!(foo::foo()); /// ``` pub fn foo() -> bool { true } /// ```rust /// assert!(!foo::bar()); /// ``` pub fn bar() -> bool { false } #[test] fn test_foo() { assert!(foo()); } #[test] fn test_bar() { assert!(!bar()); } ", ) .build(); p.cargo("test -- bar") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data(str![[r#" ... running 1 test test test_bar ... ok ... "#]]) .run(); p.cargo("test -- foo") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data(str![[r#" ... running 1 test test test_foo ... ok ... "#]]) .run(); p.cargo("test -- foo bar") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" running 2 tests test test_foo ... ok test test_bar ... ok ... "#]] .unordered(), ) .run(); } // Unlike `pass_through_escaped`, doctests won't run when using `testname` as an optimization #[cargo_test] fn pass_through_testname() { let p = project() .file( "src/lib.rs", " /// ```rust /// assert!(foo::foo()); /// ``` pub fn foo() -> bool { true } /// ```rust /// assert!(!foo::bar()); /// ``` pub fn bar() -> bool { false } #[test] fn test_foo() { assert!(foo()); } #[test] fn test_bar() { assert!(!bar()); } ", ) .build(); p.cargo("test bar") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... running 1 test test test_bar ... ok ... "#]]) .run(); p.cargo("test foo") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... running 1 test test test_foo ... ok ... "#]]) .run(); p.cargo("test foo -- bar") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data( str![[r#" running 2 tests test test_bar ... ok test test_foo ... ok ... "#]] .unordered(), ) .run(); } // Regression test for running cargo-test twice with // tests in an rlib #[cargo_test] fn cargo_test_twice() { let p = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file( "src/foo.rs", r#" #![crate_type = "rlib"] #[test] fn dummy_test() { } "#, ) .build(); for _ in 0..2 { p.cargo("test").run(); } } #[cargo_test] fn lib_bin_same_name() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [lib] name = "foo" [[bin]] name = "foo" "#, ) .file("src/lib.rs", "#[test] fn lib_test() {}") .file( "src/main.rs", " #[allow(unused_extern_crates)] extern crate foo; #[test] fn bin_test() {} ", ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] unittests src/main.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test lib_test ... ok test bin_test ... ok running 0 tests ... "#]] .unordered(), ) .run(); } #[cargo_test] fn lib_with_standard_name() { let p = project() .file("Cargo.toml", &basic_manifest("syntax", "0.0.1")) .file( "src/lib.rs", " /// ``` /// syntax::foo(); /// ``` pub fn foo() {} #[test] fn foo_test() {} ", ) .file( "tests/test.rs", " extern crate syntax; #[test] fn test() { syntax::foo() } ", ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] syntax v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/syntax-[HASH][EXE]) [RUNNING] tests/test.rs (target/debug/deps/test-[HASH][EXE]) [DOCTEST] syntax "#]]) .with_stdout_data( str![[r#" test foo_test ... ok test test ... ok test [..] ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn lib_with_standard_name2() { let p = project() .file( "Cargo.toml", r#" [package] name = "syntax" version = "0.0.1" edition = "2015" authors = [] [lib] name = "syntax" test = false doctest = false "#, ) .file("src/lib.rs", "pub fn foo() {}") .file( "src/main.rs", " extern crate syntax; fn main() {} #[test] fn test() { syntax::foo() } ", ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] syntax v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/main.rs (target/debug/deps/syntax-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test ... ok ... "#]]) .run(); } #[cargo_test] fn lib_without_name() { let p = project() .file( "Cargo.toml", r#" [package] name = "syntax" version = "0.0.1" edition = "2015" authors = [] [lib] test = false doctest = false "#, ) .file("src/lib.rs", "pub fn foo() {}") .file( "src/main.rs", " extern crate syntax; fn main() {} #[test] fn test() { syntax::foo() } ", ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] syntax v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/main.rs (target/debug/deps/syntax-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test ... ok ... "#]]) .run(); } #[cargo_test] fn bin_without_name() { let p = project() .file( "Cargo.toml", r#" [package] name = "syntax" version = "0.0.1" edition = "2015" authors = [] [lib] test = false doctest = false [[bin]] path = "src/main.rs" "#, ) .file("src/lib.rs", "pub fn foo() {}") .file( "src/main.rs", " extern crate syntax; fn main() {} #[test] fn test() { syntax::foo() } ", ) .build(); p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: binary target bin.name is required "#]]) .run(); } #[cargo_test] fn bench_without_name() { let p = project() .file( "Cargo.toml", r#" [package] name = "syntax" version = "0.0.1" edition = "2015" authors = [] [lib] test = false doctest = false [[bench]] path = "src/bench.rs" "#, ) .file("src/lib.rs", "pub fn foo() {}") .file( "src/main.rs", " extern crate syntax; fn main() {} #[test] fn test() { syntax::foo() } ", ) .file( "src/bench.rs", " #![feature(test)] extern crate syntax; extern crate test; #[bench] fn external_bench(_b: &mut test::Bencher) {} ", ) .build(); p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: benchmark target bench.name is required "#]]) .run(); } #[cargo_test] fn test_without_name() { let p = project() .file( "Cargo.toml", r#" [package] name = "syntax" version = "0.0.1" edition = "2015" authors = [] [lib] test = false doctest = false [[test]] path = "src/test.rs" "#, ) .file( "src/lib.rs", r#" pub fn foo() {} pub fn get_hello() -> &'static str { "Hello" } "#, ) .file( "src/main.rs", " extern crate syntax; fn main() {} #[test] fn test() { syntax::foo() } ", ) .file( "src/test.rs", r#" extern crate syntax; #[test] fn external_test() { assert_eq!(syntax::get_hello(), "Hello") } "#, ) .build(); p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: test target test.name is required "#]]) .run(); } #[cargo_test] fn example_without_name() { let p = project() .file( "Cargo.toml", r#" [package] name = "syntax" version = "0.0.1" edition = "2015" authors = [] [lib] test = false doctest = false [[example]] path = "examples/example.rs" "#, ) .file("src/lib.rs", "pub fn foo() {}") .file( "src/main.rs", " extern crate syntax; fn main() {} #[test] fn test() { syntax::foo() } ", ) .file( "examples/example.rs", r#" extern crate syntax; fn main() { println!("example1"); } "#, ) .build(); p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: example target example.name is required "#]]) .run(); } #[cargo_test] fn bin_there_for_integration() { let p = project() .file( "src/main.rs", " fn main() { std::process::exit(101); } #[test] fn main_test() {} ", ) .file( "tests/foo.rs", r#" use std::process::Command; #[test] fn test_test() { let status = Command::new("target/debug/foo").status().unwrap(); assert_eq!(status.code(), Some(101)); } "#, ) .build(); p.cargo("test -v") .with_stdout_data( str![[r#" test main_test ... ok test test_test ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn test_dylib() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [lib] name = "foo" crate-type = ["dylib"] [dependencies.bar] path = "bar" "#, ) .file( "src/lib.rs", r#" extern crate bar as the_bar; pub fn bar() { the_bar::baz(); } #[test] fn foo() { bar(); } "#, ) .file( "tests/test.rs", r#" extern crate foo as the_foo; #[test] fn foo() { the_foo::bar(); } "#, ) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [lib] name = "bar" crate-type = ["dylib"] "#, ) .file("bar/src/lib.rs", "pub fn baz() {}") .build(); p.cargo("test") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] bar v0.0.1 ([ROOT]/foo/bar) [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] tests/test.rs (target/debug/deps/test-[HASH][EXE]) "#]]) .with_stdout_data( str![[r#" test foo ... ok test foo ... ok ... "#]] .unordered(), ) .run(); p.root().move_into_the_past(); p.cargo("test") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] tests/test.rs (target/debug/deps/test-[HASH][EXE]) "#]]) .with_stdout_data( str![[r#" test foo ... ok test foo ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn test_twice_with_build_cmd() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] build = "build.rs" "#, ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "#[test] fn foo() {}") .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test foo ... ok running 0 tests ... "#]] .unordered(), ) .run(); p.cargo("test") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test foo ... ok running 0 tests ... "#]] .unordered(), ) .run(); } #[cargo_test] fn test_then_build() { let p = project().file("src/lib.rs", "#[test] fn foo() {}").build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" test foo ... ok running 0 tests ... "#]] .unordered(), ) .run(); p.cargo("build") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn test_no_run() { let p = project() .file("src/lib.rs", "#[test] fn foo() { panic!() }") .build(); p.cargo("test --no-run") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [EXECUTABLE] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .run(); } #[cargo_test] fn test_no_run_emit_json() { let p = project() .file("src/lib.rs", "#[test] fn foo() { panic!() }") .build(); p.cargo("test --no-run --message-format json") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn test_run_specific_bin_target() { let prj = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name="bin1" path="src/bin1.rs" [[bin]] name="bin2" path="src/bin2.rs" "#, ) .file("src/bin1.rs", "#[test] fn test1() { }") .file("src/bin2.rs", "#[test] fn test2() { }") .build(); prj.cargo("test --bin bin2") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/bin2.rs (target/debug/deps/bin2-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test2 ... ok ... "#]]) .run(); } #[cargo_test] fn test_run_implicit_bin_target() { let prj = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name="mybin" path="src/mybin.rs" "#, ) .file( "src/mybin.rs", "#[test] fn test_in_bin() { } fn main() { panic!(\"Don't execute me!\"); }", ) .file("tests/mytest.rs", "#[test] fn test_in_test() { }") .file("benches/mybench.rs", "#[test] fn test_in_bench() { }") .file( "examples/myexm.rs", "#[test] fn test_in_exm() { } fn main() { panic!(\"Don't execute me!\"); }", ) .build(); prj.cargo("test --bins") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/mybin.rs (target/debug/deps/mybin-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test_in_bin ... ok ... "#]]) .run(); } #[cargo_test] fn test_run_specific_test_target() { let prj = project() .file("src/bin/a.rs", "fn main() { }") .file("src/bin/b.rs", "#[test] fn test_b() { } fn main() { }") .file("tests/a.rs", "#[test] fn test_a() { }") .file("tests/b.rs", "#[test] fn test_b() { }") .build(); prj.cargo("test --test b") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/b.rs (target/debug/deps/b-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test_b ... ok ... "#]]) .run(); } #[cargo_test] fn test_run_implicit_test_target() { let prj = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name="mybin" path="src/mybin.rs" "#, ) .file( "src/mybin.rs", "#[test] fn test_in_bin() { } fn main() { panic!(\"Don't execute me!\"); }", ) .file("tests/mytest.rs", "#[test] fn test_in_test() { }") .file("benches/mybench.rs", "#[test] fn test_in_bench() { }") .file( "examples/myexm.rs", "fn main() { compile_error!(\"Don't build me!\"); }", ) .build(); prj.cargo("test --tests") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/mybin.rs (target/debug/deps/mybin-[HASH][EXE]) [RUNNING] tests/mytest.rs (target/debug/deps/mytest-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test_in_test ... ok ... "#]]) .run(); } #[cargo_test] fn test_run_implicit_bench_target() { let prj = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name="mybin" path="src/mybin.rs" "#, ) .file( "src/mybin.rs", "#[test] fn test_in_bin() { } fn main() { panic!(\"Don't execute me!\"); }", ) .file("tests/mytest.rs", "#[test] fn test_in_test() { }") .file("benches/mybench.rs", "#[test] fn test_in_bench() { }") .file( "examples/myexm.rs", "fn main() { compile_error!(\"Don't build me!\"); }", ) .build(); prj.cargo("test --benches") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/mybin.rs (target/debug/deps/mybin-[HASH][EXE]) [RUNNING] benches/mybench.rs (target/debug/deps/mybench-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... test test_in_bench ... ok ... "#]]) .run(); } #[cargo_test] fn test_run_implicit_example_target() { let prj = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "mybin" path = "src/mybin.rs" [[example]] name = "myexm1" [[example]] name = "myexm2" test = true [profile.test] panic = "abort" # this should be ignored by default Cargo targets set. "#, ) .file( "src/mybin.rs", "#[test] fn test_in_bin() { } fn main() { panic!(\"Don't execute me!\"); }", ) .file("tests/mytest.rs", "#[test] fn test_in_test() { }") .file("benches/mybench.rs", "#[test] fn test_in_bench() { }") .file( "examples/myexm1.rs", "#[test] fn test_in_exm() { } fn main() { panic!(\"Don't execute me!\"); }", ) .file( "examples/myexm2.rs", "#[test] fn test_in_exm() { } fn main() { panic!(\"Don't execute me!\"); }", ) .build(); // Compiles myexm1 as normal binary (without --test), but does not run it. prj.cargo("test -v") .with_stderr_contains("[RUNNING] `rustc [..]myexm1.rs [..]--crate-type bin[..]") .with_stderr_contains("[RUNNING] `rustc [..]myexm2.rs [..]--test[..]") .with_stderr_does_not_contain("[RUNNING] [..]myexm1-[..]") // profile.test panic settings shouldn't be applied even to myexm1 .with_stderr_line_without(&["[RUNNING] `rustc --crate-name myexm1"], &["panic=abort"]) .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm2-[..]") .run(); // Only tests myexm2. prj.cargo("test --tests") .with_stderr_does_not_contain("[RUNNING] [..]myexm1-[..]") .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm2-[..]") .run(); // Tests all examples. prj.cargo("test --examples") .with_stderr_data(str![[r#" ... [RUNNING] unittests examples/myexm1.rs (target/debug/examples/myexm1-[HASH][EXE]) [RUNNING] unittests examples/myexm2.rs (target/debug/examples/myexm2-[HASH][EXE]) ... "#]]) .run(); // Test an example, even without `test` set. prj.cargo("test --example myexm1") .with_stderr_data(str![[r#" ... [RUNNING] unittests examples/myexm1.rs (target/debug/examples/myexm1-[HASH][EXE]) ... "#]]) .run(); // Tests all examples. prj.cargo("test --all-targets") .with_stderr_data(str![[r#" ... [RUNNING] unittests examples/myexm1.rs (target/debug/examples/myexm1-[HASH][EXE]) [RUNNING] unittests examples/myexm2.rs (target/debug/examples/myexm2-[HASH][EXE]) ... "#]]) .run(); } #[cargo_test] fn test_filtered_excludes_compiling_examples() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "mybin" test = false "#, ) .file( "src/lib.rs", "#[cfg(test)] mod tests { #[test] fn test_in_lib() { } }", ) .file( "src/bin/mybin.rs", "#[test] fn test_in_bin() { } fn main() { panic!(\"Don't execute me!\"); }", ) .file("tests/mytest.rs", "#[test] fn test_in_test() { }") .file( "benches/mybench.rs", "#[test] fn test_in_bench() { assert!(false) }", ) .file( "examples/myexm1.rs", "#[test] fn test_in_exm() { assert!(false) } fn main() { panic!(\"Don't execute me!\"); }", ) .build(); p.cargo("test -v test_in_") .with_stdout_data(str![[r#" running 1 test test tests::test_in_lib ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s running 1 test test test_in_test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .with_stderr_data( str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..] --crate-type lib [..]` [RUNNING] `rustc --crate-name foo --edition=2015 src/lib.rs [..] --test [..]` [RUNNING] `rustc --crate-name mybin --edition=2015 src/bin/mybin.rs [..] --crate-type bin [..]` [RUNNING] `rustc --crate-name mytest --edition=2015 tests/mytest.rs [..] --test [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE] test_in_` [RUNNING] `[ROOT]/foo/target/debug/deps/mytest-[HASH][EXE] test_in_` "#]] .unordered(), ) .with_stderr_does_not_contain("[RUNNING][..]rustc[..]myexm1[..]") .with_stderr_does_not_contain("[RUNNING][..]deps/mybin-[..] test_in_") .run(); } #[cargo_test] fn test_no_harness() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [[bin]] name = "foo" test = false [[test]] name = "bar" path = "foo.rs" harness = false "#, ) .file("src/main.rs", "fn main() {}") .file("foo.rs", "fn main() {}") .build(); p.cargo("test -- --no-capture") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] foo.rs (target/debug/deps/bar-[HASH][EXE]) "#]]) .run(); } #[cargo_test] fn selective_testing() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.d1] path = "d1" [dependencies.d2] path = "d2" [lib] name = "foo" doctest = false "#, ) .file("src/lib.rs", "") .file( "d1/Cargo.toml", r#" [package] name = "d1" version = "0.0.1" edition = "2015" authors = [] [lib] name = "d1" doctest = false "#, ) .file("d1/src/lib.rs", "") .file( "d1/src/main.rs", "#[allow(unused_extern_crates)] extern crate d1; fn main() {}", ) .file( "d2/Cargo.toml", r#" [package] name = "d2" version = "0.0.1" edition = "2015" authors = [] [lib] name = "d2" doctest = false "#, ) .file("d2/src/lib.rs", "") .file( "d2/src/main.rs", "#[allow(unused_extern_crates)] extern crate d2; fn main() {}", ); let p = p.build(); println!("d1"); p.cargo("test -p d1") .with_stderr_data(str![[r#" [LOCKING] 2 packages to latest compatible versions [COMPILING] d1 v0.0.1 ([ROOT]/foo/d1) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/d1-[HASH][EXE]) [RUNNING] unittests src/main.rs (target/debug/deps/d1-[HASH][EXE]) "#]]) .with_stdout_data( str![[r#" running 0 tests running 0 tests ... "#]] .unordered(), ) .run(); println!("d2"); p.cargo("test -p d2") .with_stderr_data(str![[r#" [COMPILING] d2 v0.0.1 ([ROOT]/foo/d2) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/d2-[HASH][EXE]) [RUNNING] unittests src/main.rs (target/debug/deps/d2-[HASH][EXE]) "#]]) .with_stdout_data( str![[r#" running 0 tests running 0 tests ... "#]] .unordered(), ) .run(); println!("whole"); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... running 0 tests ... "#]]) .run(); } #[cargo_test] fn almost_cyclic_but_not_quite() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dev-dependencies.b] path = "b" [dev-dependencies.c] path = "c" "#, ) .file( "src/lib.rs", r#" #[cfg(test)] extern crate b; #[cfg(test)] extern crate c; "#, ) .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.0.1" edition = "2015" authors = [] [dependencies.foo] path = ".." "#, ) .file( "b/src/lib.rs", r#" #[allow(unused_extern_crates)] extern crate foo; "#, ) .file("c/Cargo.toml", &basic_manifest("c", "0.0.1")) .file("c/src/lib.rs", "") .build(); p.cargo("build").run(); p.cargo("test").run(); } #[cargo_test] fn build_then_selective_test() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.b] path = "b" "#, ) .file( "src/lib.rs", "#[allow(unused_extern_crates)] extern crate b;", ) .file( "src/main.rs", r#" #[allow(unused_extern_crates)] extern crate b; #[allow(unused_extern_crates)] extern crate foo; fn main() {} "#, ) .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) .file("b/src/lib.rs", "") .build(); p.cargo("build").run(); p.root().move_into_the_past(); p.cargo("test -p b").run(); } #[cargo_test] fn example_dev_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dev-dependencies.bar] path = "bar" "#, ) .file("src/lib.rs", "") .file("examples/e1.rs", "extern crate bar; fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) .file( "bar/src/lib.rs", r#" // make sure this file takes awhile to compile macro_rules! f0( () => (1) ); macro_rules! f1( () => ({(f0!()) + (f0!())}) ); macro_rules! f2( () => ({(f1!()) + (f1!())}) ); macro_rules! f3( () => ({(f2!()) + (f2!())}) ); macro_rules! f4( () => ({(f3!()) + (f3!())}) ); macro_rules! f5( () => ({(f4!()) + (f4!())}) ); macro_rules! f6( () => ({(f5!()) + (f5!())}) ); macro_rules! f7( () => ({(f6!()) + (f6!())}) ); macro_rules! f8( () => ({(f7!()) + (f7!())}) ); pub fn bar() { f8!(); } "#, ) .build(); p.cargo("test").run(); p.cargo("run --example e1 --release -v").run(); } #[cargo_test] fn selective_testing_with_docs() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.d1] path = "d1" "#, ) .file( "src/lib.rs", r#" /// ``` /// not valid rust /// ``` pub fn foo() {} "#, ) .file( "d1/Cargo.toml", r#" [package] name = "d1" version = "0.0.1" edition = "2015" authors = [] [lib] name = "d1" path = "d1.rs" "#, ) .file("d1/d1.rs", ""); let p = p.build(); p.cargo("test -p d1") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] d1 v0.0.1 ([ROOT]/foo/d1) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests d1.rs (target/debug/deps/d1-[HASH][EXE]) [DOCTEST] d1 "#]]) .with_stdout_data( str![[r#" running 0 tests running 0 tests ... "#]] .unordered(), ) .run(); } #[cargo_test] fn example_bin_same_name() { let p = project() .file("src/bin/foo.rs", r#"fn main() { println!("bin"); }"#) .file("examples/foo.rs", r#"fn main() { println!("example"); }"#) .build(); p.cargo("test --no-run -v") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [RUNNING] `rustc [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` "#]]) .run(); assert!(!p.bin("foo").is_file()); assert!(p.bin("examples/foo").is_file()); p.process(&p.bin("examples/foo")) .with_stdout_data(str![[r#" example "#]]) .run(); p.cargo("run") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]]) .with_stdout_data(str![[r#" bin "#]]) .run(); assert!(p.bin("foo").is_file()); } #[cargo_test] fn test_with_example_twice() { let p = project() .file("src/bin/foo.rs", r#"fn main() { println!("bin"); }"#) .file("examples/foo.rs", r#"fn main() { println!("example"); }"#) .build(); println!("first"); p.cargo("test -v").run(); assert!(p.bin("examples/foo").is_file()); println!("second"); p.cargo("test -v").run(); assert!(p.bin("examples/foo").is_file()); } #[cargo_test] fn example_with_dev_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [lib] name = "foo" test = false doctest = false [dev-dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "examples/ex.rs", "#[allow(unused_extern_crates)] extern crate a; fn main() {}", ) .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) .file("a/src/lib.rs", "") .build(); p.cargo("test -v") .with_stderr_data( str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] foo v0.0.1 ([ROOT]/foo) [COMPILING] a v0.0.1 ([ROOT]/foo/a) [RUNNING] `rustc --crate-name foo [..]` [RUNNING] `rustc --crate-name a [..]` [RUNNING] `rustc --crate-name ex [..] --extern a=[..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); } #[cargo_test] fn bin_is_preserved() { let p = project() .file("src/lib.rs", "") .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v").run(); assert!(p.bin("foo").is_file()); println!("test"); p.cargo("test -v").run(); assert!(p.bin("foo").is_file()); } #[cargo_test] fn bad_example() { let p = project().file("src/lib.rs", ""); let p = p.build(); p.cargo("run --example foo") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no example target named `foo` in default-run packages "#]]) .run(); p.cargo("run --bin foo") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no bin target named `foo` in default-run packages "#]]) .run(); } #[cargo_test] fn doctest_feature() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [features] bar = [] "#, ) .file( "src/lib.rs", r#" /// ```rust /// assert_eq!(foo::foo(), 1); /// ``` #[cfg(feature = "bar")] pub fn foo() -> i32 { 1 } "#, ) .build(); p.cargo("test --features bar") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" running 0 tests test [..] ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn dashes_to_underscores() { let p = project() .file("Cargo.toml", &basic_manifest("foo-bar", "0.0.1")) .file( "src/lib.rs", r#" /// ``` /// assert_eq!(foo_bar::foo(), 1); /// ``` pub fn foo() -> i32 { 1 } "#, ) .build(); p.cargo("test -v").run(); } #[cargo_test] fn doctest_dev_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dev-dependencies] b = { path = "b" } "#, ) .file( "src/lib.rs", r#" /// ``` /// extern crate b; /// ``` pub fn foo() {} "#, ) .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) .file("b/src/lib.rs", "") .build(); p.cargo("test -v").run(); } #[cargo_test] fn doctest_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] b = { path = "b" } "#, ) .file( "src/lib.rs", r#" /// ``` /// foo::foo(); /// ``` pub fn foo() { b::bar(); } "#, ) .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) .file("b/src/lib.rs", "pub fn bar() {}") .build(); p.cargo("test -v").run(); } #[cargo_test] fn doctest_dep_new_layout() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] b = { path = "b" } "#, ) .file( "src/lib.rs", r#" /// ``` /// foo::foo(); /// ``` pub fn foo() { b::bar(); } "#, ) .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) .file("b/src/lib.rs", "pub fn bar() {}") .build(); p.cargo("-Zbuild-dir-new-layout test") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .run(); } #[cargo_test] fn filter_no_doc_tests() { let p = project() .file( "src/lib.rs", r#" /// ``` /// extern crate b; /// ``` pub fn foo() {} "#, ) .file("tests/foo.rs", "") .build(); p.cargo("test --test=foo") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/foo.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" ... running 0 tests ... "#]]) .run(); } #[cargo_test] fn dylib_doctest() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [lib] name = "foo" crate-type = ["rlib", "dylib"] test = false "#, ) .file( "src/lib.rs", r#" /// ``` /// foo::foo(); /// ``` pub fn foo() {} "#, ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [DOCTEST] foo "#]]) .with_stdout_data(str![[r#" ... test [..] ... ok ... "#]]) .run(); } #[cargo_test] fn dylib_doctest2() { // Can't doc-test dylibs, as they're statically linked together. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [lib] name = "foo" crate-type = ["dylib"] test = false "#, ) .file( "src/lib.rs", r#" /// ``` /// foo::foo(); /// ``` pub fn foo() {} "#, ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn cyclic_dev_dep_doc_test() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dev-dependencies] bar = { path = "bar" } "#, ) .file( "src/lib.rs", r#" //! ``` //! extern crate bar; //! ``` "#, ) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] foo = { path = ".." } "#, ) .file( "bar/src/lib.rs", r#" #[allow(unused_extern_crates)] extern crate foo; "#, ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] foo v0.0.1 ([ROOT]/foo) [COMPILING] bar v0.0.1 ([ROOT]/foo/bar) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data( str![[r#" running 0 tests test [..] ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn dev_dep_with_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dev-dependencies] bar = { path = "bar" } "#, ) .file("src/lib.rs", "") .file("examples/foo.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] build = "build.rs" "#, ) .file("bar/src/lib.rs", "") .file("bar/build.rs", "fn main() {}") .build(); p.cargo("test").run(); } #[cargo_test] fn no_fail_fast() { let p = project() .file( "src/lib.rs", r#" pub fn add_one(x: i32) -> i32{ x + 1 } /// ```rust /// use foo::sub_one; /// assert_eq!(sub_one(101), 100); /// ``` pub fn sub_one(x: i32) -> i32{ x - 1 } "#, ) .file( "tests/test_add_one.rs", r#" extern crate foo; use foo::*; #[test] fn add_one_test() { assert_eq!(add_one(1), 2); } #[test] fn fail_add_one_test() { assert_eq!(add_one(1), 1); } "#, ) .file( "tests/test_sub_one.rs", r#" extern crate foo; use foo::*; #[test] fn sub_one_test() { assert_eq!(sub_one(1), 0); } "#, ) .build(); p.cargo("test --no-fail-fast") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [RUNNING] tests/test_add_one.rs (target/debug/deps/test_add_one-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test test_add_one` [RUNNING] tests/test_sub_one.rs (target/debug/deps/test_sub_one-[HASH][EXE]) [DOCTEST] foo [ERROR] 1 target failed: `--test test_add_one` "#]]) .with_stdout_data(str![[r#" running 0 tests test add_one_test ... ok test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s test sub_one_test ... ok test [..] ... ok ... "#]].unordered()) .run(); } #[cargo_test] fn test_multiple_packages() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies.d1] path = "d1" [dependencies.d2] path = "d2" [lib] name = "foo" doctest = false "#, ) .file("src/lib.rs", "") .file( "d1/Cargo.toml", r#" [package] name = "d1" version = "0.0.1" edition = "2015" authors = [] [lib] name = "d1" doctest = false "#, ) .file("d1/src/lib.rs", "") .file( "d2/Cargo.toml", r#" [package] name = "d2" version = "0.0.1" edition = "2015" authors = [] [lib] name = "d2" doctest = false "#, ) .file("d2/src/lib.rs", ""); let p = p.build(); p.cargo("test -p d1 -p d2") .with_stderr_data(str![[r#" ... [RUNNING] unittests src/lib.rs (target/debug/deps/d1-[HASH][EXE]) [RUNNING] unittests src/lib.rs (target/debug/deps/d2-[HASH][EXE]) ... "#]]) .with_stdout_data( str![[r#" running 0 tests running 0 tests ... "#]] .unordered(), ) .run(); } #[cargo_test] fn bin_does_not_rebuild_tests() { let p = project() .file("src/lib.rs", "") .file("src/main.rs", "fn main() {}") .file("tests/foo.rs", ""); let p = p.build(); p.cargo("test -v").run(); sleep_ms(1000); fs::write(p.root().join("src/main.rs"), "fn main() { 3; }").unwrap(); p.cargo("test -v --no-run") .with_stderr_data(str![[r#" [DIRTY] foo v0.0.1 ([ROOT]/foo): the file `src/main.rs` has changed ([..]) [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..] src/main.rs [..]` [RUNNING] `rustc [..] src/main.rs [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` [EXECUTABLE] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` "#]]) .run(); } #[cargo_test] fn selective_test_wonky_profile() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [profile.release] opt-level = 2 [dependencies] a = { path = "a" } "#, ) .file("src/lib.rs", "") .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) .file("a/src/lib.rs", ""); let p = p.build(); p.cargo("test -v --no-run --release -p foo -p a").run(); } #[cargo_test] fn selective_test_optional_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] a = { path = "a", optional = true } "#, ) .file("src/lib.rs", "") .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) .file("a/src/lib.rs", ""); let p = p.build(); p.cargo("test -v --no-run --features a -p a") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [COMPILING] a v0.0.1 ([ROOT]/foo/a) [RUNNING] `rustc [..] a/src/lib.rs [..]` [RUNNING] `rustc [..] a/src/lib.rs [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [EXECUTABLE] `[ROOT]/foo/target/debug/deps/a-[HASH][EXE]` "#]]) .run(); } #[cargo_test] fn only_test_docs() { let p = project() .file( "src/lib.rs", r#" #[test] fn foo() { let a: u32 = "hello"; } /// ``` /// foo::bar(); /// println!("ok"); /// ``` pub fn bar() { } "#, ) .file("tests/foo.rs", "this is not rust"); let p = p.build(); p.cargo("test --doc") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [DOCTEST] foo "#]]) .with_stdout_data(str![[r#" ... test [..] ... ok ... "#]]) .run(); } #[cargo_test] fn doctest_with_library_paths() { let p = project(); // Only link search directories within the target output directory are // propagated through to dylib_path_envvar() (see #3366). let dir1 = p.target_debug_dir().join("foo\\backslash"); let dir2 = p.target_debug_dir().join("dir=containing=equal=signs"); let p = p .file("Cargo.toml", &basic_manifest("foo", "0.0.0")) .file( "build.rs", &format!( r##" fn main() {{ println!(r#"cargo::rustc-link-search=native={}"#); println!(r#"cargo::rustc-link-search={}"#); }} "##, dir1.display(), dir2.display() ), ) .file( "src/lib.rs", &format!( r##" /// ``` /// foo::assert_search_path(); /// ``` pub fn assert_search_path() {{ let search_path = std::env::var_os("{}").unwrap(); let paths = std::env::split_paths(&search_path).collect::>(); assert!(paths.contains(&r#"{}"#.into())); assert!(paths.contains(&r#"{}"#.into())); }} "##, dylib_path_envvar(), dir1.display(), dir2.display() ), ) .build(); p.cargo("test --doc").run(); } #[cargo_test] fn test_panic_abort_with_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = { path = "bar" } [profile.dev] panic = 'abort' "#, ) .file( "src/lib.rs", r#" extern crate bar; #[test] fn foo() {} "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) .file("bar/src/lib.rs", "") .build(); p.cargo("test -v").run(); } #[cargo_test] fn cfg_test_even_with_no_harness() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [lib] harness = false doctest = false "#, ) .file( "src/lib.rs", r#"#[cfg(test)] fn main() { println!("hello!"); }"#, ) .build(); p.cargo("test -v") .with_stdout_data(str![[r#" hello! "#]]) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` "#]]) .run(); } #[cargo_test] fn panic_abort_multiple() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] a = { path = "a" } [profile.release] panic = 'abort' "#, ) .file( "src/lib.rs", "#[allow(unused_extern_crates)] extern crate a;", ) .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) .file("a/src/lib.rs", "") .build(); p.cargo("test --release -v -p foo -p a").run(); } #[cargo_test] fn pass_correct_cfgs_flags_to_rustdoc() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [features] default = ["feature_a/default"] nightly = ["feature_a/nightly"] [dependencies.feature_a] path = "libs/feature_a" default-features = false "#, ) .file( "src/lib.rs", r#" #[cfg(test)] mod tests { #[test] fn it_works() { assert!(true); } } "#, ) .file( "libs/feature_a/Cargo.toml", r#" [package] name = "feature_a" version = "0.1.0" edition = "2015" authors = [] [features] default = ["mock_serde_codegen"] nightly = ["mock_serde_derive"] [dependencies] mock_serde_derive = { path = "../mock_serde_derive", optional = true } [build-dependencies] mock_serde_codegen = { path = "../mock_serde_codegen", optional = true } "#, ) .file( "libs/feature_a/src/lib.rs", r#" #[cfg(feature = "mock_serde_derive")] const MSG: &'static str = "This is safe"; #[cfg(feature = "mock_serde_codegen")] const MSG: &'static str = "This is risky"; pub fn get() -> &'static str { MSG } "#, ) .file( "libs/mock_serde_derive/Cargo.toml", &basic_manifest("mock_serde_derive", "0.1.0"), ) .file("libs/mock_serde_derive/src/lib.rs", "") .file( "libs/mock_serde_codegen/Cargo.toml", &basic_manifest("mock_serde_codegen", "0.1.0"), ) .file("libs/mock_serde_codegen/src/lib.rs", ""); let p = p.build(); p.cargo("test --package feature_a --verbose") .with_stderr_data(str![[r#" ... [DOCTEST] feature_a [RUNNING] `rustdoc [..]--test [..]mock_serde_codegen[..]` ... "#]]) .run(); p.cargo("test --verbose") .with_stderr_data(str![[r#" ... [DOCTEST] foo [RUNNING] `rustdoc [..]--test [..]feature_a[..]` ... "#]]) .run(); } #[cargo_test] fn test_release_ignore_panic() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] a = { path = "a" } [profile.test] panic = 'abort' [profile.release] panic = 'abort' "#, ) .file( "src/lib.rs", "#[allow(unused_extern_crates)] extern crate a;", ) .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) .file("a/src/lib.rs", ""); let p = p.build(); println!("test"); p.cargo("test -v").run(); println!("bench"); p.cargo("bench -v").run(); } #[cargo_test] fn test_many_with_features() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] a = { path = "a" } [features] foo = [] [workspace] "#, ) .file("src/lib.rs", "") .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) .file("a/src/lib.rs", "") .build(); p.cargo("test -v -p a -p foo --features foo").run(); } #[cargo_test] fn test_all_workspace() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = { path = "bar" } [workspace] "#, ) .file("src/main.rs", "#[test] fn foo_test() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] fn bar_test() {}") .build(); p.cargo("test --workspace") .with_stdout_data( str![[r#" test foo_test ... ok test bar_test ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn test_all_exclude() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["bar", "baz"] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] pub fn bar() {}") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", "#[test] pub fn baz() { assert!(false); }") .build(); p.cargo("test --workspace --exclude baz") .with_stdout_data(str![[r#" ... running 1 test test bar ... ok ... "#]]) .run(); } #[cargo_test] fn test_all_exclude_not_found() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] pub fn bar() {}") .build(); p.cargo("test --workspace --exclude baz") .with_stderr_data(str![[r#" ... [WARNING] excluded package(s) `baz` not found in workspace `[ROOT]/foo` ... "#]]) .with_stdout_data(str![[r#" ... running 1 test test bar ... ok ... "#]]) .run(); } #[cargo_test] fn test_all_exclude_glob() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["bar", "baz"] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] pub fn bar() {}") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", "#[test] pub fn baz() { assert!(false); }") .build(); p.cargo("test --workspace --exclude '*z'") .with_stdout_data(str![[r#" ... running 1 test test bar ... ok ... "#]]) .run(); } #[cargo_test] fn test_all_exclude_glob_not_found() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] pub fn bar() {}") .build(); p.cargo("test --workspace --exclude '*z'") .with_stderr_data(str![[r#" ... [WARNING] excluded package pattern(s) `*z` not found in workspace `[ROOT]/foo` ... "#]]) .with_stdout_data(str![[r#" ... running 1 test test bar ... ok ... "#]]) .run(); } #[cargo_test] fn test_all_exclude_broken_glob() { let p = project().file("src/main.rs", "fn main() {}").build(); p.cargo("test --workspace --exclude '[*z'") .with_status(101) .with_stderr_data(str![[r#" ... [ERROR] cannot build glob pattern from `[*z` ... "#]]) .run(); } #[cargo_test] fn test_all_virtual_manifest() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "#[test] fn a() {}") .file("b/Cargo.toml", &basic_manifest("b", "0.1.0")) .file("b/src/lib.rs", "#[test] fn b() {}") .build(); p.cargo("test --workspace") .with_stdout_data( str![[r#" running 1 test test a ... ok running 1 test test b ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn test_virtual_manifest_all_implied() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b"] "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "#[test] fn a() {}") .file("b/Cargo.toml", &basic_manifest("b", "0.1.0")) .file("b/src/lib.rs", "#[test] fn b() {}") .build(); p.cargo("test") .with_stdout_data( str![[r#" running 1 test test a ... ok running 1 test test b ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn test_virtual_manifest_one_project() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar", "baz"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] fn bar() {}") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", "#[test] fn baz() { assert!(false); }") .build(); p.cargo("test -p bar") .with_stdout_contains("running 1 test\ntest bar ... ok") .with_stdout_does_not_contain("running 1 test\ntest baz ... ok") .run(); } #[cargo_test] fn test_virtual_manifest_glob() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar", "baz"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] fn bar() { assert!(false); }") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", "#[test] fn baz() {}") .build(); p.cargo("test -p '*z'") .with_stdout_does_not_contain("running 1 test\ntest bar ... ok") .with_stdout_contains("running 1 test\ntest baz ... ok") .run(); } #[cargo_test] fn test_virtual_manifest_glob_not_found() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] fn bar() {}") .build(); p.cargo("test -p bar -p '*z'") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package pattern(s) `*z` not found in workspace `[ROOT]/foo` ... "#]]) .run(); } #[cargo_test] fn test_virtual_manifest_broken_glob() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "#[test] fn bar() {}") .build(); p.cargo("test -p '[*z'") .with_status(101) .with_stderr_data(str![[r#" [ERROR] cannot build glob pattern from `[*z` ... "#]]) .run(); } #[cargo_test] fn test_all_member_dependency_same_name() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a"] "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.1.0" edition = "2015" [dependencies] a = "0.1.0" "#, ) .file("a/src/lib.rs", "#[test] fn a() {}") .build(); Package::new("a", "0.1.0").publish(); p.cargo("test --workspace") .with_stdout_data(str![[r#" ... test a ... ok ... "#]]) .run(); } #[cargo_test] fn doctest_only_with_dev_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.1.0" edition = "2015" [dev-dependencies] b = { path = "b" } "#, ) .file( "src/lib.rs", r#" /// ``` /// extern crate b; /// /// b::b(); /// ``` pub fn a() {} "#, ) .file("b/Cargo.toml", &basic_manifest("b", "0.1.0")) .file("b/src/lib.rs", "pub fn b() {}") .build(); p.cargo("test --doc -v").run(); } #[cargo_test] fn test_many_targets() { let p = project() .file( "src/bin/a.rs", r#" fn main() {} #[test] fn bin_a() {} "#, ) .file( "src/bin/b.rs", r#" fn main() {} #[test] fn bin_b() {} "#, ) .file( "src/bin/c.rs", r#" fn main() {} #[test] fn bin_c() { panic!(); } "#, ) .file( "examples/a.rs", r#" fn main() {} #[test] fn example_a() {} "#, ) .file( "examples/b.rs", r#" fn main() {} #[test] fn example_b() {} "#, ) .file("examples/c.rs", "#[test] fn example_c() { panic!(); }") .file("tests/a.rs", "#[test] fn test_a() {}") .file("tests/b.rs", "#[test] fn test_b() {}") .file("tests/c.rs", "does not compile") .build(); p.cargo("test --verbose --bin a --bin b --example a --example b --test a --test b") .with_stdout_data( str![[r#" test bin_a ... ok test bin_b ... ok test test_a ... ok test test_b ... ok ... "#]] .unordered(), ) .with_stderr_data( str![[r#" [RUNNING] `rustc --crate-name a --edition=2015 examples/a.rs [..]` [RUNNING] `rustc --crate-name b --edition=2015 examples/b.rs [..]` ... "#]] .unordered(), ) .run(); } #[cargo_test] fn doctest_and_registry() { let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.1.0" edition = "2015" [dependencies] b = { path = "b" } c = { path = "c" } [workspace] "#, ) .file("src/lib.rs", "") .file("b/Cargo.toml", &basic_manifest("b", "0.1.0")) .file( "b/src/lib.rs", " /// ``` /// b::foo(); /// ``` pub fn foo() {} ", ) .file( "c/Cargo.toml", r#" [package] name = "c" version = "0.1.0" edition = "2015" [dependencies] b = "0.1" "#, ) .file("c/src/lib.rs", "") .build(); Package::new("b", "0.1.0").publish(); p.cargo("test --workspace -v").run(); } #[cargo_test] fn cargo_test_env() { let rustc_host = rustc_host(); let src = format!( r#" #![crate_type = "rlib"] #[test] fn env_test() {{ use std::env; eprintln!("{{}}", env::var("{}").unwrap()); }} "#, cargo::CARGO_ENV ); let p = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", &src) .build(); let cargo = format!( "{}[EXE]", cargo_exe() .with_extension("") .to_str() .unwrap() .replace(rustc_host, "[HOST_TARGET]") ); p.cargo("test --lib -- --no-capture") .with_stderr_contains(cargo) .with_stdout_data(str![[r#" ... test env_test ... ok ... "#]]) .run(); // Check that `cargo test` propagates the environment's $CARGO let cargo_exe = cargo_exe(); let other_cargo_path = p.root().join(cargo_exe.file_name().unwrap()); std::fs::hard_link(&cargo_exe, &other_cargo_path).unwrap(); let stderr_other_cargo = format!( "{}[EXE]", other_cargo_path .with_extension("") .to_str() .unwrap() .replace(p.root().parent().unwrap().to_str().unwrap(), "[ROOT]") ); p.process(other_cargo_path) .args(&["test", "--lib", "--", "--no-capture"]) .with_stderr_contains(stderr_other_cargo) .with_stdout_data(str![[r#" ... test env_test ... ok ... "#]]) .run(); } #[cargo_test] fn test_order() { let p = project() .file("src/lib.rs", "#[test] fn test_lib() {}") .file("tests/a.rs", "#[test] fn test_a() {}") .file("tests/z.rs", "#[test] fn test_z() {}") .build(); p.cargo("test --workspace") .with_stdout_data(str![[r#" running 1 test test test_lib ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s running 1 test test test_a ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s running 1 test test test_z ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s ... "#]]) .run(); } #[cargo_test] fn cyclic_dev() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dev-dependencies] foo = { path = "." } "#, ) .file("src/lib.rs", "#[test] fn test_lib() {}") .file("tests/foo.rs", "extern crate foo;") .build(); p.cargo("test --workspace").run(); } #[cargo_test] fn cyclical_dep_with_missing_feature() { // Checks for error handling when a cyclical dev-dependency specify a // feature that doesn't exist. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dev-dependencies] foo = { path = ".", features = ["missing"] } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to select a version for `foo`. ... required by package `foo v0.1.0 ([ROOT]/foo)` versions that meet the requirements `*` are: 0.1.0 package `foo` depends on `foo` with feature `missing` but `foo` does not have that feature. failed to select a version for `foo` which could resolve this conflict "#]]) .run(); } #[cargo_test] fn publish_a_crate_without_tests() { Package::new("testless", "0.1.0") .file( "Cargo.toml", r#" [package] name = "testless" version = "0.1.0" edition = "2015" exclude = ["tests/*"] [[test]] name = "a_test" "#, ) .file("src/lib.rs", "") // In real life, the package will have a test, // which would be excluded from .crate file by the // `exclude` field. Our test harness does not honor // exclude though, so let's just not add the file! // .file("tests/a_test.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] testless = "0.1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("test").run(); p.cargo("test --package testless").run(); } #[cargo_test] fn find_dependency_of_proc_macro_dependency_with_target() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["root", "proc_macro_dep"] "#, ) .file( "root/Cargo.toml", r#" [package] name = "root" version = "0.1.0" edition = "2015" authors = [] [dependencies] proc_macro_dep = { path = "../proc_macro_dep" } "#, ) .file( "root/src/lib.rs", r#" #[macro_use] extern crate proc_macro_dep; #[derive(Noop)] pub struct X; "#, ) .file( "proc_macro_dep/Cargo.toml", r#" [package] name = "proc_macro_dep" version = "0.1.0" edition = "2015" authors = [] [lib] proc-macro = true [dependencies] baz = "^0.1" "#, ) .file( "proc_macro_dep/src/lib.rs", r#" extern crate baz; extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_derive(Noop)] pub fn noop(_input: TokenStream) -> TokenStream { "".parse().unwrap() } "#, ) .build(); Package::new("bar", "0.1.0").publish(); Package::new("baz", "0.1.0") .dep("bar", "0.1") .file("src/lib.rs", "extern crate bar;") .publish(); p.cargo("test --workspace --target").arg(rustc_host()).run(); } #[cargo_test] fn test_hint_not_masked_by_doctest() { let p = project() .file( "src/lib.rs", r#" /// ``` /// assert_eq!(1, 1); /// ``` pub fn this_works() {} "#, ) .file( "tests/integ.rs", r#" #[test] fn this_fails() { panic!(); } "#, ) .build(); p.cargo("test --no-fail-fast") .with_status(101) .with_stdout_data( str![[r#" test this_fails ... FAILED test [..]this_works (line [..]) ... ok ... "#]] .unordered(), ) .with_stderr_data(str![[r#" ... [ERROR] test failed, to rerun pass `--test integ` ... "#]]) .run(); } #[cargo_test] fn test_hint_workspace_virtual() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a", "b", "c"] "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "#[test] fn t1() {}") .file("b/Cargo.toml", &basic_manifest("b", "0.1.0")) .file("b/src/lib.rs", "#[test] fn t1() {assert!(false)}") .file("c/Cargo.toml", &basic_manifest("c", "0.1.0")) .file( "c/src/lib.rs", r#" /// ```rust /// assert_eq!(1, 2); /// ``` pub fn foo() {} "#, ) .file( "c/src/main.rs", r#" fn main() {} #[test] fn from_main() { assert_eq!(1, 2); } "#, ) .file( "c/tests/t1.rs", r#" #[test] fn from_int_test() { assert_eq!(1, 2); } "#, ) .file( "c/examples/ex1.rs", r#" fn main() {} #[test] fn from_example() { assert_eq!(1, 2); } "#, ) // This does not use #[bench] since it is unstable. #[test] works just // the same for our purpose of checking the hint. .file( "c/benches/b1.rs", r#" #[test] fn from_bench() { assert_eq!(1, 2); } "#, ) .build(); // This depends on Units being sorted so that `b` fails first. p.cargo("test") .with_stderr_data( str![[r#" [COMPILING] c v0.1.0 ([ROOT]/foo/c) [COMPILING] a v0.1.0 ([ROOT]/foo/a) [COMPILING] b v0.1.0 ([ROOT]/foo/b) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/a-[HASH][EXE]) [RUNNING] unittests src/lib.rs (target/debug/deps/b-[HASH][EXE]) [ERROR] test failed, to rerun pass `-p b --lib` "#]] .unordered(), ) .with_status(101) .run(); p.cargo("test") .cwd("b") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs ([ROOT]/foo/target/debug/deps/b-[HASH][EXE]) [ERROR] test failed, to rerun pass `--lib` "#]]) .with_status(101) .run(); p.cargo("test --no-fail-fast") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/a-[HASH][EXE]) [RUNNING] unittests src/lib.rs (target/debug/deps/b-[HASH][EXE]) [ERROR] test failed, to rerun pass `-p b --lib` [RUNNING] unittests src/lib.rs (target/debug/deps/c-[HASH][EXE]) [RUNNING] unittests src/main.rs (target/debug/deps/c-[HASH][EXE]) [ERROR] test failed, to rerun pass `-p c --bin c` [RUNNING] tests/t1.rs (target/debug/deps/t1-[HASH][EXE]) [ERROR] test failed, to rerun pass `-p c --test t1` [DOCTEST] a [DOCTEST] b [DOCTEST] c [ERROR] doctest failed, to rerun pass `-p c --doc` [ERROR] 4 targets failed: `-p b --lib` `-p c --bin c` `-p c --test t1` `-p c --doc` "#]]) .with_status(101) .run(); // Check others that are not in the default set. p.cargo("test -p c --examples --benches --no-fail-fast") .with_stderr_data(str![[r#" [COMPILING] c v0.1.0 ([ROOT]/foo/c) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/c-[HASH][EXE]) [RUNNING] unittests src/main.rs (target/debug/deps/c-[HASH][EXE]) [ERROR] test failed, to rerun pass `-p c --bin c` [RUNNING] benches/b1.rs (target/debug/deps/b1-[HASH][EXE]) [ERROR] test failed, to rerun pass `-p c --bench b1` [RUNNING] unittests examples/ex1.rs (target/debug/examples/ex1-[HASH][EXE]) [ERROR] test failed, to rerun pass `-p c --example ex1` [ERROR] 3 targets failed: `-p c --bin c` `-p c --bench b1` `-p c --example ex1` "#]]) .with_status(101) .run(); } #[cargo_test] fn test_hint_workspace_nonvirtual() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["a"] "#, ) .file("src/lib.rs", "") .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", "#[test] fn t1() {assert!(false)}") .build(); p.cargo("test --workspace") .with_stderr_data(str![[r#" ... [ERROR] test failed, to rerun pass `-p a --lib` ... "#]]) .with_status(101) .run(); p.cargo("test -p a") .with_stderr_data(str![[r#" ... [ERROR] test failed, to rerun pass `-p a --lib` ... "#]]) .with_status(101) .run(); } #[cargo_test] fn json_artifact_includes_test_flag() { // Verify that the JSON artifact output includes `test` flag. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [profile.test] opt-level = 1 "#, ) .file("src/lib.rs", "") .build(); p.cargo("test --lib -v --no-run --message-format=json") .with_stdout_data( str![[r#" [ { "executable": "[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]", "features": [], "filenames": "{...}", "fresh": false, "manifest_path": "[ROOT]/foo/Cargo.toml", "package_id": "path+[ROOTURL]/foo#0.0.1", "profile": "{...}", "reason": "compiler-artifact", "target": { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "foo", "src_path": "[ROOT]/foo/src/lib.rs", "test": true } }, { "reason": "build-finished", "success": true } ] "#]] .is_json() .against_jsonlines(), ) .run(); } #[cargo_test] fn json_artifact_includes_executable_for_library_tests() { let p = project() .file("src/main.rs", "fn main() { }") .file("src/lib.rs", r#"#[test] fn lib_test() {}"#) .build(); p.cargo("test --lib -v --no-run --message-format=json") .with_stdout_data( str![[r#" [ { "executable": "[ROOT]/foo/target/debug/deps/foo-[HASH][EXE]", "features": [], "filenames": "{...}", "fresh": false, "manifest_path": "[ROOT]/foo/Cargo.toml", "package_id": "path+[ROOTURL]/foo#0.0.1", "profile": "{...}", "reason": "compiler-artifact", "target": { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "foo", "src_path": "[ROOT]/foo/src/lib.rs", "test": true } }, { "reason": "build-finished", "success": true } ] "#]] .is_json() .against_jsonlines(), ) .run(); } #[cargo_test] fn json_diagnostic_includes_explanation() { let p = project() .file( "src/main.rs", "fn main() { const OH_NO: &'static mut usize = &mut 1; }", ) .build(); p.cargo("check --message-format=json") .with_stdout_data( str![[r#" [ { "manifest_path": "[ROOT]/foo/Cargo.toml", "message": { "$message_type": "diagnostic", "children": "{...}", "code": { "code": "E0764", "explanation": "{...}" }, "level": "error", "message": "{...}", "rendered": "{...}", "spans": "{...}" }, "package_id": "{...}", "reason": "compiler-message", "target": "{...}" }, "{...}" ] "#]] .is_json() .against_jsonlines(), ) .with_status(101) .run(); } #[cargo_test] fn json_artifact_includes_executable_for_integration_tests() { let p = project() .file( "tests/integration_test.rs", r#"#[test] fn integration_test() {}"#, ) .build(); p.cargo("test -v --no-run --message-format=json --test integration_test") .with_stdout_data( str![[r#" [ { "executable": "[ROOT]/foo/target/debug/deps/integration_test-[HASH][EXE]", "features": [], "filenames": "{...}", "fresh": false, "manifest_path": "[ROOT]/foo/Cargo.toml", "package_id": "path+[ROOTURL]/foo#0.0.1", "profile": "{...}", "reason": "compiler-artifact", "target": { "crate_types": [ "bin" ], "doc": false, "doctest": false, "edition": "2015", "kind": [ "test" ], "name": "integration_test", "src_path": "[ROOT]/foo/tests/integration_test.rs", "test": true } }, { "reason": "build-finished", "success": true } ] "#]] .is_json() .against_jsonlines(), ) .run(); } #[cargo_test] fn test_build_script_links() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" links = 'something' [lib] test = false "#, ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); p.cargo("test --no-run").run(); } #[cargo_test] fn doctest_skip_staticlib() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [lib] crate-type = ["staticlib"] "#, ) .file( "src/lib.rs", r#" //! ``` //! assert_eq!(1,2); //! ``` "#, ) .build(); p.cargo("test --doc") .with_status(101) .with_stderr_data(str![[r#" [WARNING] doc tests are not supported for crate type(s) `staticlib` in package `foo` [ERROR] no library targets found in package `foo` "#]]) .run(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .run(); } #[cargo_test] fn can_not_mix_doc_tests_and_regular_tests() { let p = project() .file( "src/lib.rs", "\ /// ``` /// assert_eq!(1, 1) /// ``` pub fn foo() -> u8 { 1 } #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } } ", ) .build(); p.cargo("test") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) [DOCTEST] foo "#]]) .with_stdout_data(str![[r#" running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s running 1 test test src/lib.rs - foo (line 1) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); p.cargo("test --lib") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .with_stdout_data(str![[r#" running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .run(); // This has been modified to attempt to diagnose spurious errors on CI. // For some reason, this is recompiling the lib when it shouldn't. If the // root cause is ever found, the changes here should be reverted. // See https://github.com/rust-lang/cargo/issues/6887 p.cargo("test --doc -vv") .with_stderr_does_not_contain("[COMPILING] foo [..]") .with_stderr_data(str![[r#" ... [DOCTEST] foo ... "#]]) .with_stdout_data(str![[r#" running 1 test test src/lib.rs - foo (line 1) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s "#]]) .env("CARGO_LOG", "cargo=trace") .run(); p.cargo("test --lib --doc") .with_status(101) .with_stderr_data(str![[r#" [ERROR] can't mix --doc with other target selecting options "#]]) .run(); } #[cargo_test] fn can_not_no_run_doc_tests() { let p = project() .file( "src/lib.rs", r#" /// ``` /// let _x = 1 + "foo"; /// ``` pub fn foo() -> u8 { 1 } "#, ) .build(); p.cargo("test --doc --no-run") .with_status(101) .with_stderr_data(str![[r#" [ERROR] can't skip running doc tests with --no-run "#]]) .run(); } #[cargo_test] fn test_all_targets_lib() { let p = project().file("src/lib.rs", "").build(); p.cargo("test --all-targets") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] unittests src/lib.rs (target/debug/deps/foo-[HASH][EXE]) "#]]) .run(); } #[cargo_test] fn test_dep_with_dev() { Package::new("devdep", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] bar = { path = "bar" } "#, ) .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" [dev-dependencies] devdep = "0.1" "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("test -p bar") .with_status(101) .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [ERROR] package `bar` cannot be tested because it requires dev-dependencies and is not a member of the workspace "#]]) .run(); } #[cargo_test] fn cargo_test_doctest_xcompile_ignores() { // Check ignore-TARGET syntax. let p = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file( "src/lib.rs", r#" ///```ignore-x86_64 ///assert!(cfg!(not(target_arch = "x86_64"))); ///``` pub fn foo() -> u8 { 4 } "#, ) .build(); p.cargo("build").run(); #[cfg(not(target_arch = "x86_64"))] p.cargo("test") .with_stdout_data(str![[r#" ... test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s ... "#]]) .run(); #[cfg(target_arch = "x86_64")] p.cargo("test") .with_stdout_data(str![[r#" ... test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s ... "#]]) .run(); } #[cargo_test] fn cargo_test_doctest_xcompile_runner() { if !cross_compile_can_run_on_host() { return; } let runner = project() .file("Cargo.toml", &basic_bin_manifest("runner")) .file( "src/main.rs", r#" pub fn main() { eprintln!("this is a runner"); let args: Vec = std::env::args().collect(); std::process::Command::new(&args[1]).spawn(); } "#, ) .build(); runner.cargo("build").run(); assert!(runner.bin("runner").is_file()); let runner_path = paths::root().join("runner"); fs::copy(&runner.bin("runner"), &runner_path).unwrap(); let config = paths::root().join(".cargo/config.toml"); fs::create_dir_all(config.parent().unwrap()).unwrap(); // Escape Windows backslashes for TOML config. let runner_str = runner_path.to_str().unwrap().replace('\\', "\\\\"); fs::write( config, format!( r#" [target.'cfg(target_arch = "{}")'] runner = "{}" "#, cross_compile::alternate_arch(), runner_str ), ) .unwrap(); let p = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file( "src/lib.rs", &format!( r#" ///``` ///assert!(cfg!(target_arch = "{}")); ///``` pub fn foo() -> u8 {{ 4 }} "#, cross_compile::alternate_arch() ), ) .build(); p.cargo("build").run(); p.cargo(&format!("test --target {}", cross_compile::alternate())) .with_stdout_data(str![[r#" ... running 0 tests ... "#]]) .run(); p.cargo(&format!("test --target {}", cross_compile::alternate())) .with_stdout_data(str![[r#" ... test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s ... "#]]) .with_stderr_data(str![[r#" ... this is a runner ... "#]]) .run(); } #[cargo_test] fn cargo_test_doctest_xcompile_no_runner() { if !cross_compile_can_run_on_host() { return; } let p = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file( "src/lib.rs", &format!( r#" ///``` ///assert!(cfg!(target_arch = "{}")); ///``` pub fn foo() -> u8 {{ 4 }} "#, cross_compile::alternate_arch() ), ) .build(); p.cargo("build").run(); p.cargo(&format!("test --target {}", cross_compile::alternate())) .with_stdout_data(str![[r#" ... running 0 tests ... "#]]) .run(); p.cargo(&format!("test --target {}", cross_compile::alternate())) .with_stdout_data(str![[r#" ... test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [ELAPSED]s ... "#]]) .run(); } #[cargo_test(nightly, reason = "-Zpanic-abort-tests in rustc is unstable")] fn panic_abort_tests() { let p = project() .file( "Cargo.toml", r#" [package] name = 'foo' version = '0.1.0' edition = "2015" [dependencies] a = { path = 'a' } [profile.dev] panic = 'abort' [profile.test] panic = 'abort' "#, ) .file( "src/lib.rs", r#" #[test] fn foo() { a::foo(); } "#, ) .file("a/Cargo.toml", &basic_lib_manifest("a")) .file("a/src/lib.rs", "pub fn foo() {}") .build(); // This uses -j1 because of a race condition. Otherwise it will build the // two copies of `foo` in parallel, and which one is first is random. If // `--test` is first, then the first line with `[..]` will match, and the // second line with `--test` will fail. p.cargo("test -Z panic-abort-tests -v -j1") .with_stderr_data( str![[r#" [RUNNING] `[..]--crate-name a [..]-C panic=abort[..]` [RUNNING] `[..]--crate-name foo [..]-C panic=abort[..]` [RUNNING] `[..]--crate-name foo [..]-C panic=abort[..]--test[..]` ... "#]] .unordered(), ) .masquerade_as_nightly_cargo(&["panic-abort-tests"]) .run(); } #[cargo_test] // Unlike with rustc, `rustdoc --test -Cpanic=abort` already works on stable fn panic_abort_doc_tests() { let p = project() .file( "Cargo.toml", r#" [package] name = 'foo' version = '0.1.0' edition = "2015" [profile.dev] panic = 'abort' "#, ) .file( "src/lib.rs", r#" //! ```should_panic //! panic!(); //! ``` "#, ) .build(); p.cargo("test --doc -Z panic-abort-tests -v") .with_stderr_data( str![[r#" [RUNNING] `[..]rustc[..] --crate-name foo [..]-C panic=abort[..]` [RUNNING] `[..]rustdoc[..] --crate-name foo [..]--test[..]-C panic=abort[..]` ... "#]] .unordered(), ) .masquerade_as_nightly_cargo(&["panic-abort-tests"]) .run(); } #[cargo_test(nightly, reason = "-Zpanic-abort-tests in rustc is unstable")] fn panic_abort_only_test() { let p = project() .file( "Cargo.toml", r#" [package] name = 'foo' version = '0.1.0' edition = "2015" [dependencies] a = { path = 'a' } [profile.test] panic = 'abort' "#, ) .file( "src/lib.rs", r#" #[test] fn foo() { a::foo(); } "#, ) .file("a/Cargo.toml", &basic_lib_manifest("a")) .file("a/src/lib.rs", "pub fn foo() {}") .build(); p.cargo("test -Z panic-abort-tests -v") .with_stderr_data(str![[r#" [WARNING] `panic` setting is ignored for `test` profile ... "#]]) .masquerade_as_nightly_cargo(&["panic-abort-tests"]) .run(); } #[cargo_test(nightly, reason = "-Zpanic-abort-tests in rustc is unstable")] fn panic_abort_test_profile_inherits() { let p = project() .file( "Cargo.toml", r#" [package] name = 'foo' version = '0.1.0' edition = "2015" [dependencies] a = { path = 'a' } [profile.dev] panic = 'abort' "#, ) .file( "src/lib.rs", r#" #[test] fn foo() { a::foo(); } "#, ) .file("a/Cargo.toml", &basic_lib_manifest("a")) .file("a/src/lib.rs", "pub fn foo() {}") .build(); p.cargo("test -Z panic-abort-tests -v") .masquerade_as_nightly_cargo(&["panic-abort-tests"]) .with_status(0) .run(); } #[cargo_test] fn bin_env_for_test() { // Test for the `CARGO_BIN_EXE_` environment variables for tests. // // Note: The Unicode binary uses a `[[bin]]` definition because different // filesystems normalize utf-8 in different ways. For example, HFS uses // "gru\u{308}ßen" and APFS uses "gr\u{fc}ßen". Defining it in TOML forces // one form to be used. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2018" [[bin]] name = 'grüßen' path = 'src/bin/grussen.rs' "#, ) .file("src/bin/foo.rs", "fn main() {}") .file("src/bin/with-dash.rs", "fn main() {}") .file("src/bin/grussen.rs", "fn main() {}") .file( "src/lib.rs", r#" //! ``` //! assert_eq!(option_env!("CARGO_BIN_EXE_foo"), None); //! assert_eq!(option_env!("CARGO_BIN_EXE_with-dash"), None); //! assert_eq!(option_env!("CARGO_BIN_EXE_grüßen"), None); //! assert_eq!(std::env::var("CARGO_BIN_EXE_foo").ok(), None); //! assert_eq!(std::env::var("CARGO_BIN_EXE_with-dash").ok(), None); //! assert_eq!(std::env::var("CARGO_BIN_EXE_grüßen").ok(), None); //! ``` #[test] fn no_bins() { assert_eq!(option_env!("CARGO_BIN_EXE_foo"), None); assert_eq!(option_env!("CARGO_BIN_EXE_with-dash"), None); assert_eq!(option_env!("CARGO_BIN_EXE_grüßen"), None); assert_eq!(std::env::var("CARGO_BIN_EXE_foo").ok(), None); assert_eq!(std::env::var("CARGO_BIN_EXE_with-dash").ok(), None); assert_eq!(std::env::var("CARGO_BIN_EXE_grüßen").ok(), None); } "#, ) .build(); let bin_path = |name| p.bin(name).to_string_lossy().replace("\\", "\\\\"); p.change_file( "tests/check_env.rs", &r#" #[test] fn run_bins() { assert_eq!(env!("CARGO_BIN_EXE_foo"), ""); assert_eq!(env!("CARGO_BIN_EXE_with-dash"), ""); assert_eq!(env!("CARGO_BIN_EXE_grüßen"), ""); assert_eq!(std::env::var("CARGO_BIN_EXE_foo").ok().as_deref(), Some("")); assert_eq!(std::env::var("CARGO_BIN_EXE_with-dash").ok().as_deref(), Some("")); assert_eq!(std::env::var("CARGO_BIN_EXE_grüßen").ok().as_deref(), Some("")); } "# .replace("", &bin_path("foo")) .replace("", &bin_path("with-dash")) .replace("", &bin_path("grüßen")), ); p.cargo("test").run(); p.cargo("check --all-targets").run(); } #[cargo_test] fn test_workspaces_cwd() { // This tests that all the different test types are executed from the // crate directory (manifest_dir), and not from the workspace root. let make_lib_file = |expected| { format!( r#" //! ``` //! assert_eq!("{expected}", std::fs::read_to_string("file.txt").unwrap()); //! assert_eq!("{expected}", include_str!("../file.txt")); //! assert_eq!( //! std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")), //! std::env::current_dir().unwrap(), //! ); //! ``` #[test] fn test_unit_{expected}_cwd() {{ assert_eq!("{expected}", std::fs::read_to_string("file.txt").unwrap()); assert_eq!("{expected}", include_str!("../file.txt")); assert_eq!( std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")), std::env::current_dir().unwrap(), ); }} "#, expected = expected ) }; let make_test_file = |expected| { format!( r#" #[test] fn test_integration_{expected}_cwd() {{ assert_eq!("{expected}", std::fs::read_to_string("file.txt").unwrap()); assert_eq!("{expected}", include_str!("../file.txt")); assert_eq!( std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")), std::env::current_dir().unwrap(), ); }} "#, expected = expected ) }; let p = project() .file( "Cargo.toml", r#" [package] name = "root-crate" version = "0.0.0" [workspace] members = [".", "nested-crate", "very/deeply/nested/deep-crate"] "#, ) .file("file.txt", "root") .file("src/lib.rs", &make_lib_file("root")) .file("tests/integration.rs", &make_test_file("root")) .file( "nested-crate/Cargo.toml", r#" [package] name = "nested-crate" version = "0.0.0" "#, ) .file("nested-crate/file.txt", "nested") .file("nested-crate/src/lib.rs", &make_lib_file("nested")) .file( "nested-crate/tests/integration.rs", &make_test_file("nested"), ) .file( "very/deeply/nested/deep-crate/Cargo.toml", r#" [package] name = "deep-crate" version = "0.0.0" "#, ) .file("very/deeply/nested/deep-crate/file.txt", "deep") .file( "very/deeply/nested/deep-crate/src/lib.rs", &make_lib_file("deep"), ) .file( "very/deeply/nested/deep-crate/tests/integration.rs", &make_test_file("deep"), ) .build(); p.cargo("test --workspace --all") .with_stderr_data( str![[r#" [DOCTEST] root_crate [DOCTEST] nested_crate [DOCTEST] deep_crate ... "#]] .unordered(), ) .with_stdout_data( str![[r#" test test_unit_root_cwd ... ok test test_unit_nested_cwd ... ok test test_unit_deep_cwd ... ok test test_integration_root_cwd ... ok test test_integration_nested_cwd ... ok test test_integration_deep_cwd ... ok ... "#]] .unordered(), ) .run(); p.cargo("test -p root-crate --all") .with_stderr_data(str![[r#" ... [DOCTEST] root_crate ... "#]]) .with_stdout_data( str![[r#" test test_unit_root_cwd ... ok test test_integration_root_cwd ... ok ... "#]] .unordered(), ) .run(); p.cargo("test -p nested-crate --all") .with_stderr_data(str![[r#" ... [DOCTEST] nested_crate ... "#]]) .with_stdout_data( str![[r#" test test_unit_nested_cwd ... ok test test_integration_nested_cwd ... ok ... "#]] .unordered(), ) .run(); p.cargo("test -p deep-crate --all") .with_stderr_data(str![[r#" ... [DOCTEST] deep_crate ... "#]]) .with_stdout_data( str![[r#" test test_unit_deep_cwd ... ok test test_integration_deep_cwd ... ok ... "#]] .unordered(), ) .run(); p.cargo("test --all") .cwd("nested-crate") .with_stderr_data(str![[r#" ... [DOCTEST] nested_crate ... "#]]) .with_stdout_data( str![[r#" test test_unit_nested_cwd ... ok test test_integration_nested_cwd ... ok ... "#]] .unordered(), ) .run(); p.cargo("test --all") .cwd("very/deeply/nested/deep-crate") .with_stderr_data(str![[r#" ... [DOCTEST] deep_crate ... "#]]) .with_stdout_data( str![[r#" test test_unit_deep_cwd ... ok test test_integration_deep_cwd ... ok ... "#]] .unordered(), ) .run(); } #[cargo_test] fn execution_error() { // Checks the behavior when a test fails to launch. let p = project() .file( "tests/t1.rs", r#" #[test] fn foo() {} "#, ) .build(); let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env()); p.cargo("test") .env(&key, "does_not_exist") // The actual error is usually "no such file", but on Windows it has a // custom message. Since matching against the error string produced by // Rust is not very reliable, this just uses `[..]`. .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/t1.rs (target/debug/deps/t1-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test t1` Caused by: could not execute process `does_not_exist [ROOT]/foo/target/debug/deps/t1-[HASH][EXE]` (never executed) Caused by: [NOT_FOUND] "#]]) .with_status(101) .run(); } #[cargo_test] fn nonzero_exit_status() { // Tests for nonzero exit codes from tests. let p = project() .file( "tests/t1.rs", r#" #[test] fn t() { panic!("this is a normal error") } "#, ) .file( "tests/t2.rs", r#" #[test] fn t() { std::process::exit(4) } "#, ) .build(); p.cargo("test --test t1") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/t1.rs (target/debug/deps/t1-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test t1` "#]]) .with_stdout_data(str![[r#" ... this is a normal error ... "#]]) .with_status(101) .run(); p.cargo("test --test t2") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/t2.rs (target/debug/deps/t2-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test t2` Caused by: process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2-[HASH][EXE]` ([EXIT_STATUS]: 4) [NOTE] test exited abnormally; to see the full output pass --no-capture to the harness. "#]]) .with_status(4) .run(); p.cargo("test --test t2 -- --no-capture") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/t2.rs (target/debug/deps/t2-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test t2` Caused by: process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2-[HASH][EXE] --no-capture` ([EXIT_STATUS]: 4) "#]]) .with_status(4) .run(); // no-fail-fast always uses 101 p.cargo("test --no-fail-fast") .with_stderr_data(str![[r#" [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] tests/t1.rs (target/debug/deps/t1-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test t1` [RUNNING] tests/t2.rs (target/debug/deps/t2-[HASH][EXE]) [ERROR] test failed, to rerun pass `--test t2` Caused by: process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2-[HASH][EXE]` ([EXIT_STATUS]: 4) [NOTE] test exited abnormally; to see the full output pass --no-capture to the harness. [ERROR] 2 targets failed: `--test t1` `--test t2` "#]]) .with_status(101) .run(); p.cargo("test --no-fail-fast -- --no-capture") .with_stderr_does_not_contain( "test exited abnormally; to see the full output pass --no-capture to the harness.", ) .with_stderr_data(str![[r#" [..]thread [..]panicked [..] tests/t1.rs[..] [NOTE] run with `RUST_BACKTRACE=1` environment variable to display a backtrace Caused by: process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2-[HASH][EXE] --no-capture` ([EXIT_STATUS]: 4) ... "#]].unordered()) .with_status(101) .run(); } #[cargo_test] fn cargo_test_print_env_verbose() { let p = project() .file("Cargo.toml", &basic_manifest("foo", "0.0.1")) .file("src/lib.rs", "") .build(); p.cargo("test -vv").with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `[..]CARGO_MANIFEST_DIR=[ROOT]/foo[..] rustc --crate-name foo[..]` [RUNNING] `[..]CARGO_MANIFEST_DIR=[ROOT]/foo[..] rustc --crate-name foo[..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `[..]CARGO_MANIFEST_DIR=[ROOT]/foo[..] [ROOT]/foo/target/debug/deps/foo-[HASH][EXE]` [DOCTEST] foo [RUNNING] `[..]CARGO_MANIFEST_DIR=[ROOT]/foo[..] rustdoc --edition=2015 --crate-type lib --color auto --crate-name foo[..]` "#]]).run(); } #[cargo_test] fn cargo_test_set_out_dir_env_var() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2021" "#, ) .file( "src/lib.rs", r#" pub fn add(left: u64, right: u64) -> u64 { left + right } "#, ) .file( "build.rs", r#" fn main() {} "#, ) .file( "tests/case.rs", r#" #[cfg(test)] pub mod tests { #[test] fn test_add() { assert!(std::env::var("OUT_DIR").is_ok()); assert_eq!(foo::add(2, 5), 7); } } "#, ) .build(); p.cargo("test").run(); p.cargo("test --package foo --test case -- tests::test_add --exact --no-capture") .run(); } ================================================ FILE: tests/testsuite/timings.rs ================================================ //! Tests for --timings. use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::registry::Package; use cargo_test_support::str; #[cargo_test] fn timings_works() { Package::new("dep", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] dep = "0.1" "#, ) .file("src/lib.rs", "") .file("src/main.rs", "fn main() {}") .file("tests/t1.rs", "") .file("examples/ex1.rs", "fn main() {}") .build(); p.cargo("build --all-targets --timings") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] dep v0.1.0 (registry `dummy-registry`) [COMPILING] dep v0.1.0 [COMPILING] foo v0.1.0 ([ROOT]/foo) Timing report saved to [ROOT]/foo/target/cargo-timings/cargo-timing-[..].html [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("clean").run(); p.cargo("test --timings").run(); p.cargo("clean").run(); p.cargo("check --timings").run(); p.cargo("clean").run(); p.cargo("doc --timings").run(); } ================================================ FILE: tests/testsuite/tool_paths.rs ================================================ //! Tests for configuration values that point to programs. use crate::prelude::*; use cargo_test_support::{basic_lib_manifest, project, rustc_host, rustc_host_env, str}; #[cargo_test] fn pathless_tools() { let target = rustc_host(); let foo = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.{}] linker = "nonexistent-linker" "#, target ), ) .build(); foo.cargo("build --verbose") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc [..]-C linker=nonexistent-linker [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } // can set a custom linker via `target.'cfg(..)'.linker` #[cargo_test] fn custom_linker_cfg() { let foo = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [target.'cfg(not(target_os = "none"))'] linker = "nonexistent-linker" "#, ) .build(); foo.cargo("build --verbose") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc [..]-C linker=nonexistent-linker [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } // custom linker set via `target.$triple.linker` have precede over `target.'cfg(..)'.linker` #[cargo_test] fn custom_linker_cfg_precedence() { let target = rustc_host(); let foo = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.'cfg(not(target_os = "none"))'] linker = "ignored-linker" [target.{}] linker = "nonexistent-linker" "#, target ), ) .build(); foo.cargo("build --verbose") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc [..]-C linker=nonexistent-linker [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn custom_linker_cfg_collision() { let foo = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [target.'cfg(not(target_arch = "avr"))'] linker = "nonexistent-linker1" [target.'cfg(not(target_os = "none"))'] linker = "nonexistent-linker2" "#, ) .build(); foo.cargo("build --verbose") .with_status(101) .with_stderr_data(str![[r#" [ERROR] several matching instances of `target.'cfg(..)'.linker` in configurations first match `cfg(not(target_arch = "avr"))` located in [ROOT]/foo/.cargo/config.toml second match `cfg(not(target_os = "none"))` located in [ROOT]/foo/.cargo/config.toml "#]]) .run(); } #[cargo_test] fn absolute_tools() { let target = rustc_host(); // Escaped as they appear within a TOML config file let linker = if cfg!(windows) { r#"C:\\bogus\\nonexistent-linker"# } else { r#"/bogus/nonexistent-linker"# }; let foo = project() .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.{target}] linker = "{linker}" "#, target = target, linker = linker ), ) .build(); foo.cargo("build --verbose") .with_stderr_data(str![[r#" [COMPILING] foo v0.5.0 ([ROOT]/foo) [RUNNING] `rustc [..]-C linker=[..]/bogus/nonexistent-linker [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn relative_tools() { let target = rustc_host(); // Escaped as they appear within a TOML config file let linker = if cfg!(windows) { r#".\\tools\\nonexistent-linker"# } else { r#"./tools/nonexistent-linker"# }; // Funky directory structure to test that relative tool paths are made absolute // by reference to the `.cargo/..` directory and not to (for example) the CWD. let p = project() .no_manifest() .file("bar/Cargo.toml", &basic_lib_manifest("bar")) .file("bar/src/lib.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.{target}] linker = "{linker}" "#, target = target, linker = linker ), ) .build(); p.cargo("build --verbose") .cwd("bar") .with_stderr_data(str![[r#" [COMPILING] bar v0.5.0 ([ROOT]/foo/bar) [RUNNING] `rustc [..]-C linker=[ROOT]/foo/./tools/nonexistent-linker [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn custom_runner() { let target = rustc_host(); let p = project() .file("src/main.rs", "fn main() {}") .file("tests/test.rs", "") .file("benches/bench.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.{}] runner = "nonexistent-runner -r" "#, target ), ) .build(); p.cargo("run -- --param") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param` ... "#]]) .run(); p.cargo("test --test test --verbose -- --param") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [FINISHED] `test` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `nonexistent-runner -r [ROOT]/foo/target/debug/deps/test-[HASH][EXE] --param` ... "#]]) .run(); p.cargo("bench --bench bench --verbose -- --param") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]` [RUNNING] `rustc [..]` [FINISHED] `bench` profile [optimized] target(s) in [ELAPSED]s [RUNNING] `nonexistent-runner -r [ROOT]/foo/target/release/deps/bench-[HASH][EXE] --param --bench` ... "#]]) .run(); } // can set a custom runner via `target.'cfg(..)'.runner` #[cargo_test] fn custom_runner_cfg() { let p = project() .file("src/main.rs", "fn main() {}") .file( ".cargo/config.toml", r#" [target.'cfg(not(target_os = "none"))'] runner = "nonexistent-runner -r" "#, ) .build(); p.cargo("run -- --param") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param` ... "#]]) .run(); } // custom runner set via `target.$triple.runner` have precedence over `target.'cfg(..)'.runner` #[cargo_test] fn custom_runner_cfg_precedence() { let target = rustc_host(); let p = project() .file("src/main.rs", "fn main() {}") .file( ".cargo/config.toml", &format!( r#" [target.'cfg(not(target_os = "none"))'] runner = "ignored-runner" [target.{}] runner = "nonexistent-runner -r" "#, target ), ) .build(); p.cargo("run -- --param") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param` ... "#]]) .run(); } #[cargo_test] fn custom_runner_cfg_collision() { let p = project() .file("src/main.rs", "fn main() {}") .file( ".cargo/config.toml", r#" [target.'cfg(not(target_arch = "avr"))'] runner = "true" [target.'cfg(not(target_os = "none"))'] runner = "false" "#, ) .build(); p.cargo("run -- --param") .with_status(101) .with_stderr_data(str![[r#" [ERROR] several matching instances of `target.'cfg(..)'.runner` in configurations first match `cfg(not(target_arch = "avr"))` located in [ROOT]/foo/.cargo/config.toml second match `cfg(not(target_os = "none"))` located in [ROOT]/foo/.cargo/config.toml "#]]) .run(); } #[cargo_test] fn custom_runner_env() { let p = project().file("src/main.rs", "fn main() {}").build(); let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env()); p.cargo("run") .env(&key, "nonexistent-runner --foo") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `nonexistent-runner --foo target/debug/foo[EXE]` [ERROR] could not execute process `nonexistent-runner --foo target/debug/foo[EXE]` (never executed) Caused by: [NOT_FOUND] "#]]) .run(); } #[cargo_test] fn custom_runner_env_overrides_config() { let target = rustc_host(); let p = project() .file("src/main.rs", "fn main() {}") .file( ".cargo/config.toml", &format!( r#" [target.{}] runner = "should-not-run -r" "#, target ), ) .build(); let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env()); p.cargo("run") .env(&key, "should-run --foo") .with_status(101) .with_stderr_data(str![[r#" ... [RUNNING] `should-run --foo target/debug/foo[EXE]` ... "#]]) .run(); } #[cargo_test] #[cfg(unix)] // Assumes `true` is in PATH. fn custom_runner_env_true() { // Check for a bug where "true" was interpreted as a boolean instead of // the executable. let p = project().file("src/main.rs", "fn main() {}").build(); let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env()); p.cargo("run") .env(&key, "true") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `true target/debug/foo[EXE]` "#]]) .run(); } #[cargo_test] fn custom_runner_target_applies_to_host() { // This ensures that without `--target` (non-cross-target mode) // Cargo still respects targe configuration. let target = rustc_host(); let p = project() .file("src/main.rs", "fn main() {}") .file( ".cargo/config.toml", &format!( r#" [target.{}] runner = "nonexistent-runner -r" "#, target ), ) .build(); p.cargo("run -Z target-applies-to-host") .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .env("CARGO_TARGET_APPLIES_TO_HOST", "false") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `nonexistent-runner -r target/debug/foo[EXE]` [ERROR] could not execute process `nonexistent-runner -r target/debug/foo[EXE]` (never executed) Caused by: [NOT_FOUND] "#]]) .run(); } #[cargo_test] fn custom_linker_env() { let p = project().file("src/main.rs", "fn main() {}").build(); let key = format!("CARGO_TARGET_{}_LINKER", rustc_host_env()); p.cargo("build -v") .env(&key, "nonexistent-linker") .with_status(101) .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc [..]-C linker=nonexistent-linker [..]` ... "#]]) .run(); } #[cargo_test] fn target_in_environment_contains_lower_case() { let p = project().file("src/main.rs", "fn main() {}").build(); let target = rustc_host(); let env_key = format!( "CARGO_TARGET_{}_LINKER", target.to_lowercase().replace('-', "_") ); p.cargo("build -v --target") .arg(target) .env(&env_key, "nonexistent-linker") .with_stderr_data(format!("\ [WARNING] environment variables are expected to use uppercase letters and underscores, the variable `{env_key}` will be ignored and have no effect [WARNING] environment variables are expected to use uppercase letters and underscores, the variable `{env_key}` will be ignored and have no effect [COMPILING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo --edition=2015 src/main.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s " )) .run(); } #[cargo_test] fn cfg_ignored_fields() { // Test for some ignored fields in [target.'cfg()'] tables. let p = project() .file( ".cargo/config.toml", r#" # Try some empty tables. [target.'cfg(not(foo))'] [target.'cfg(not(bar))'.somelib] # A bunch of unused fields. [target.'cfg(not(target_os = "none"))'] linker = 'false' ar = 'false' foo = {rustc-flags = "-l foo"} invalid = 1 runner = 'false' rustflags = '' "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_stderr_data(str![[r#" [WARNING] unused key `somelib` in [target] config table `cfg(not(bar))` [WARNING] unused key `ar` in [target] config table `cfg(not(target_os = "none"))` [WARNING] unused key `foo` in [target] config table `cfg(not(target_os = "none"))` [WARNING] unused key `invalid` in [target] config table `cfg(not(target_os = "none"))` [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } ================================================ FILE: tests/testsuite/unit_graph.rs ================================================ //! Tests for --unit-graph option. use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::registry::Package; use cargo_test_support::str; #[cargo_test] fn gated() { let p = project().file("src/lib.rs", "").build(); p.cargo("build --unit-graph") .with_status(101) .with_stderr_data(str![[r#" [ERROR] the `--unit-graph` flag is unstable, and only available on the nightly channel of Cargo, but this is the `stable` channel See https://doc.rust-lang.org/book/[..].html for more information about Rust release channels. See https://github.com/rust-lang/cargo/issues/8002 for more information about the `--unit-graph` flag. "#]]) .run(); } #[cargo_test] fn simple() { Package::new("a", "1.0.0") .dep("b", "1.0") .feature("feata", &["b/featb"]) .publish(); Package::new("b", "1.0.0") .dep("c", "1.0") .feature("featb", &["c/featc"]) .publish(); Package::new("c", "1.0.0").feature("featc", &[]).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] a = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("build --features a/feata --unit-graph -Zunstable-options") .masquerade_as_nightly_cargo(&["unit-graph"]) .with_stdout_data( str![[r#" { "roots": [ 3 ], "units": [ { "dependencies": [ { "extern_crate_name": "b", "index": 1, "noprelude": false, "nounused": false, "public": false } ], "features": [ "feata" ], "mode": "build", "pkg_id": "registry+https://github.com/rust-lang/crates.io-index#a@1.0.0", "platform": null, "profile": { "codegen_backend": null, "codegen_units": null, "debug_assertions": true, "debuginfo": 2, "incremental": false, "lto": "false", "name": "dev", "opt_level": "0", "overflow_checks": true, "panic": "unwind", "rpath": false, "split_debuginfo": "{...}", "strip": "{...}" }, "target": { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "a", "src_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/a-1.0.0/src/lib.rs", "test": true } }, { "dependencies": [ { "extern_crate_name": "c", "index": 2, "noprelude": false, "nounused": false, "public": false } ], "features": [ "featb" ], "mode": "build", "pkg_id": "registry+https://github.com/rust-lang/crates.io-index#b@1.0.0", "platform": null, "profile": { "codegen_backend": null, "codegen_units": null, "debug_assertions": true, "debuginfo": 2, "incremental": false, "lto": "false", "name": "dev", "opt_level": "0", "overflow_checks": true, "panic": "unwind", "rpath": false, "split_debuginfo": "{...}", "strip": "{...}" }, "target": { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "b", "src_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/b-1.0.0/src/lib.rs", "test": true } }, { "dependencies": [], "features": [ "featc" ], "mode": "build", "pkg_id": "registry+https://github.com/rust-lang/crates.io-index#c@1.0.0", "platform": null, "profile": { "codegen_backend": null, "codegen_units": null, "debug_assertions": true, "debuginfo": 2, "incremental": false, "lto": "false", "name": "dev", "opt_level": "0", "overflow_checks": true, "panic": "unwind", "rpath": false, "split_debuginfo": "{...}", "strip": "{...}" }, "target": { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "c", "src_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/c-1.0.0/src/lib.rs", "test": true } }, { "dependencies": [ { "extern_crate_name": "a", "index": 0, "noprelude": false, "nounused": false, "public": false } ], "features": [], "mode": "build", "pkg_id": "path+[ROOTURL]/foo#0.1.0", "platform": null, "profile": { "codegen_backend": null, "codegen_units": null, "debug_assertions": true, "debuginfo": 2, "incremental": false, "lto": "false", "name": "dev", "opt_level": "0", "overflow_checks": true, "panic": "unwind", "rpath": false, "split_debuginfo": "{...}", "strip": "{...}" }, "target": { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "foo", "src_path": "[ROOT]/foo/src/lib.rs", "test": true } } ], "version": 1 } "#]] .is_json(), ) .run(); } ================================================ FILE: tests/testsuite/update.rs ================================================ //! Tests for the `cargo update` command. use crate::prelude::*; use cargo_test_support::compare::assert_e2e; use cargo_test_support::registry::{self}; use cargo_test_support::registry::{Dependency, Package}; use cargo_test_support::{basic_lib_manifest, basic_manifest, git, project, str}; #[cargo_test] fn minor_update_two_places() { Package::new("log", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] log = "0.1" foo = { path = "foo" } "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] log = "0.1" "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); p.change_file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] log = "0.1.1" "#, ); p.cargo("check").run(); } #[cargo_test] fn transitive_minor_update() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.1.0").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.1" log = "0.1" foo = { path = "foo" } "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.1" "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("serde", "0.1.1").dep("log", "0.1.1").publish(); // Note that `serde` isn't actually updated here! The default behavior for // `update` right now is to as conservatively as possible attempt to satisfy // an update. In this case we previously locked the dependency graph to `log // 0.1.0`, but nothing on the command line says we're allowed to update // that. As a result the update of `serde` here shouldn't update to `serde // 0.1.1` as that would also force an update to `log 0.1.1`. // // Also note that this is probably counterintuitive and weird. We may wish // to change this one day. p.cargo("update serde") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 0 packages to latest compatible versions [NOTE] pass `--verbose` to see 2 unchanged dependencies behind latest "#]]) .run(); } #[cargo_test] fn conservative() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.1.0").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.1" log = "0.1" foo = { path = "foo" } "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.1" "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("serde", "0.1.1").dep("log", "0.1").publish(); p.cargo("update serde") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] serde v0.1.0 -> v0.1.1 [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest "#]]) .run(); } #[cargo_test] fn update_via_new_dep() { Package::new("log", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] log = "0.1" # foo = { path = "foo" } "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] log = "0.1.1" "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); p.uncomment_root_manifest(); p.cargo("check").env("CARGO_LOG", "cargo=trace").run(); } #[cargo_test] fn update_via_new_member() { Package::new("log", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [workspace] # members = [ "foo" ] [dependencies] log = "0.1" "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] log = "0.1.1" "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); p.uncomment_root_manifest(); p.cargo("check").run(); } #[cargo_test] fn add_dep_deep_new_requirement() { Package::new("log", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] log = "0.1" # bar = "0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("bar", "0.1.0").dep("log", "0.1.1").publish(); p.uncomment_root_manifest(); p.cargo("check").run(); } #[cargo_test] fn everything_real_deep() { Package::new("log", "0.1.0").publish(); Package::new("foo", "0.1.0").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] foo = "0.1" # bar = "0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("bar", "0.1.0").dep("log", "0.1.1").publish(); p.uncomment_root_manifest(); p.cargo("check").run(); } #[cargo_test] fn change_package_version() { let p = project() .file( "Cargo.toml", r#" [package] name = "a-foo" version = "0.2.0-alpha" edition = "2015" authors = [] [dependencies] bar = { path = "bar", version = "0.2.0-alpha" } "#, ) .file("src/lib.rs", "") .file("bar/Cargo.toml", &basic_manifest("bar", "0.2.0-alpha")) .file("bar/src/lib.rs", "") .file( "Cargo.lock", r#" [[package]] name = "foo" version = "0.2.0" dependencies = ["bar 0.2.0"] [[package]] name = "bar" version = "0.2.0" "#, ) .build(); p.cargo("check").run(); } #[cargo_test] fn update_precise() { Package::new("serde", "0.1.0").publish(); Package::new("serde", "0.2.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.2" foo = { path = "foo" } "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.1" "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("serde", "0.2.0").publish(); p.cargo("update serde:0.2.1 --precise 0.2.0") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [DOWNGRADING] serde v0.2.1 -> v0.2.0 "#]]) .run(); } #[cargo_test] fn update_precise_mismatched() { Package::new("serde", "1.2.0").publish(); Package::new("serde", "1.2.1").publish(); Package::new("serde", "1.6.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "~1.2" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); // `1.6.0` does not match `"~1.2"` p.cargo("update serde:1.2 --precise 1.6.0") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] failed to select a version for the requirement `serde = "~1.2"` candidate versions found which didn't match: 1.6.0 location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `bar v0.0.1 ([ROOT]/foo)` perhaps a crate was updated and forgotten to be re-vendored? "#]]) .with_status(101) .run(); // `1.9.0` does not exist p.cargo("update serde:1.2 --precise 1.9.0") // This terrible error message has been the same for a long time. A fix is more than welcome! .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `serde` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `bar v0.0.1 ([ROOT]/foo)` "#]]) .with_status(101) .run(); } #[cargo_test] fn update_precise_build_metadata() { Package::new("serde", "0.0.1+first").publish(); Package::new("serde", "0.0.1+second").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.0" edition = "2015" [dependencies] serde = "0.0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); p.cargo("update serde --precise 0.0.1+first").run(); p.cargo("update serde --precise 0.0.1+second") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] serde v0.0.1+first -> v0.0.1+second "#]]) .run(); // This is not considered "Downgrading". Build metadata are not assumed to // be ordered. p.cargo("update serde --precise 0.0.1+first") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] serde v0.0.1+second -> v0.0.1+first "#]]) .run(); } #[cargo_test] fn update_precise_do_not_force_update_deps() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.2.1").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.2" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("serde", "0.2.2").dep("log", "0.1").publish(); p.cargo("update serde:0.2.1 --precise 0.2.2") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] serde v0.2.1 -> v0.2.2 [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest "#]]) .run(); } #[cargo_test] fn update_recursive() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.2.1").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.2" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("serde", "0.2.2").dep("log", "0.1").publish(); p.cargo("update serde:0.2.1 --recursive") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] log v0.1.0 -> v0.1.1 [UPDATING] serde v0.2.1 -> v0.2.2 "#]]) .run(); } #[cargo_test] fn update_aggressive_alias_for_recursive() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.2.1").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.2" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("serde", "0.2.2").dep("log", "0.1").publish(); p.cargo("update serde:0.2.1 --aggressive") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions [UPDATING] log v0.1.0 -> v0.1.1 [UPDATING] serde v0.2.1 -> v0.2.2 "#]]) .run(); } #[cargo_test] fn update_recursive_conflicts_with_precise() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.2.1").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.2" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); Package::new("log", "0.1.1").publish(); Package::new("serde", "0.2.2").dep("log", "0.1").publish(); p.cargo("update serde:0.2.1 --precise 0.2.2 --recursive") .with_status(1) .with_stderr_data(str![[r#" [ERROR] the argument '--precise ' cannot be used with '--recursive' Usage: cargo[EXE] update --precise ]> For more information, try '--help'. "#]]) .run(); } // cargo update should respect its arguments even without a lockfile. // See issue "Running cargo update without a Cargo.lock ignores arguments" // at . #[cargo_test] fn update_precise_first_run() { Package::new("serde", "0.1.0").publish(); Package::new("serde", "0.2.0").publish(); Package::new("serde", "0.2.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" [dependencies] serde = "0.2" "#, ) .file("src/lib.rs", "") .build(); p.cargo("update serde --precise 0.2.0") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [DOWNGRADING] serde v0.2.1 -> v0.2.0 "#]]) .run(); // Assert `cargo metadata` shows serde 0.2.0 p.cargo("metadata") .with_stdout_data( str![[r#" { "metadata": null, "packages": [ { "authors": [], "categories": [], "default_run": null, "dependencies": [ { "features": [], "kind": null, "name": "serde", "optional": false, "registry": null, "rename": null, "req": "^0.2", "source": "registry+https://github.com/rust-lang/crates.io-index", "target": null, "uses_default_features": true } ], "description": null, "documentation": null, "edition": "2015", "features": {}, "homepage": null, "id": "path+[ROOTURL]/foo#bar@0.0.1", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/foo/Cargo.toml", "metadata": null, "name": "bar", "publish": null, "readme": null, "repository": null, "rust_version": null, "source": null, "targets": [ { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "bar", "src_path": "[ROOT]/foo/src/lib.rs", "test": true } ], "version": "0.0.1" }, { "authors": [], "categories": [], "default_run": null, "dependencies": [], "description": null, "documentation": null, "edition": "2015", "features": {}, "homepage": null, "id": "registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0", "keywords": [], "license": null, "license_file": null, "links": null, "manifest_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/serde-0.2.0/Cargo.toml", "metadata": null, "name": "serde", "publish": null, "readme": null, "repository": null, "rust_version": null, "source": "registry+https://github.com/rust-lang/crates.io-index", "targets": [ { "crate_types": [ "lib" ], "doc": true, "doctest": true, "edition": "2015", "kind": [ "lib" ], "name": "serde", "src_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/serde-0.2.0/src/lib.rs", "test": true } ], "version": "0.2.0" } ], "resolve": { "nodes": [ { "dependencies": [ "registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0" ], "deps": [ { "dep_kinds": [ { "kind": null, "target": null } ], "name": "serde", "pkg": "registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0" } ], "features": [], "id": "path+[ROOTURL]/foo#bar@0.0.1" }, { "dependencies": [], "deps": [], "features": [], "id": "registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0" } ], "root": "path+[ROOTURL]/foo#bar@0.0.1" }, "target_directory": "[ROOT]/foo/target", "build_directory": "[ROOT]/foo/target", "version": 1, "workspace_default_members": [ "path+[ROOTURL]/foo#bar@0.0.1" ], "workspace_members": [ "path+[ROOTURL]/foo#bar@0.0.1" ], "workspace_root": "[ROOT]/foo" } "#]] .is_json(), ) .run(); p.cargo("update serde --precise 0.2.0") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index "#]]) .run(); } #[cargo_test] fn preserve_top_comment() { let p = project().file("src/lib.rs", "").build(); p.cargo("update").run(); let lockfile = p.read_lockfile(); assert!(lockfile.starts_with("# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.\n")); let mut lines = lockfile.lines().collect::>(); lines.insert(2, "# some other comment"); let mut lockfile = lines.join("\n"); lockfile.push('\n'); // .lines/.join loses the last newline println!("saving Cargo.lock contents:\n{}", lockfile); p.change_file("Cargo.lock", &lockfile); p.cargo("update").run(); let lockfile2 = p.read_lockfile(); println!("loaded Cargo.lock contents:\n{}", lockfile2); assert_eq!(lockfile, lockfile2); } #[cargo_test] fn dry_run_update() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.1.0").dep("log", "0.1").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.1" log = "0.1" foo = { path = "foo" } "#, ) .file("src/lib.rs", "") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] serde = "0.1" "#, ) .file("foo/src/lib.rs", "") .build(); p.cargo("check").run(); let old_lockfile = p.read_lockfile(); Package::new("log", "0.1.1").publish(); Package::new("serde", "0.1.1").dep("log", "0.1").publish(); p.cargo("update serde --dry-run") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] serde v0.1.0 -> v0.1.1 [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest [WARNING] not updating lockfile due to dry run "#]]) .run(); let new_lockfile = p.read_lockfile(); assert_eq!(old_lockfile, new_lockfile) } #[cargo_test] fn workspace_only() { let p = project().file("src/main.rs", "fn main() {}").build(); p.cargo("generate-lockfile").run(); let lock1 = p.read_lockfile(); p.change_file( "Cargo.toml", r#" [package] name = "foo" authors = [] version = "0.0.2" edition = "2015" "#, ); p.cargo("update --workspace").run(); let lock2 = p.read_lockfile(); assert_ne!(lock1, lock2); assert!(lock1.contains("0.0.1")); assert!(lock2.contains("0.0.2")); assert!(!lock1.contains("0.0.2")); assert!(!lock2.contains("0.0.1")); } #[cargo_test] fn precise_with_build_metadata() { // +foo syntax shouldn't be necessary with --precise Package::new("bar", "0.1.0+extra-stuff.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = "0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("bar", "0.1.1+extra-stuff.1").publish(); Package::new("bar", "0.1.2+extra-stuff.2").publish(); p.cargo("update bar --precise 0.1") .with_status(101) .with_stderr_data(str![[r#" [ERROR] invalid version format for precise version `0.1` Caused by: unexpected end of input while parsing minor version number "#]]) .run(); p.cargo("update bar --precise 0.1.1+does-not-match") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `bar` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.1.0 ([ROOT]/foo)` "#]]) .run(); p.cargo("update bar --precise 0.1.1") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] bar v0.1.0+extra-stuff.0 -> v0.1.1+extra-stuff.1 "#]]) .run(); Package::new("bar", "0.1.3").publish(); p.cargo("update bar --precise 0.1.3+foo") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] no matching package named `bar` found location searched: `dummy-registry` index (which is replacing registry `crates-io`) required by package `foo v0.1.0 ([ROOT]/foo)` "#]]) .run(); p.cargo("update bar --precise 0.1.3") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPDATING] bar v0.1.1+extra-stuff.1 -> v0.1.3 "#]]) .run(); } #[cargo_test] fn update_only_members_order_one() { let git_project = git::new("rustdns", |project| { project .file("Cargo.toml", &basic_lib_manifest("rustdns")) .file("src/lib.rs", "pub fn bar() {}") }); let workspace_toml = format!( r#" [workspace.package] version = "2.29.8" edition = "2021" publish = false [workspace] members = [ "rootcrate", "subcrate", ] resolver = "2" [workspace.dependencies] # Internal crates subcrate = {{ version = "*", path = "./subcrate" }} # External dependencies rustdns = {{ version = "0.5.0", default-features = false, git = "{}" }} "#, git_project.url() ); let p = project() .file("Cargo.toml", &workspace_toml) .file( "rootcrate/Cargo.toml", r#" [package] name = "rootcrate" version.workspace = true edition.workspace = true publish.workspace = true [dependencies] subcrate.workspace = true "#, ) .file("rootcrate/src/main.rs", "fn main() {}") .file( "subcrate/Cargo.toml", r#" [package] name = "subcrate" version.workspace = true edition.workspace = true publish.workspace = true [dependencies] rustdns.workspace = true "#, ) .file("subcrate/src/lib.rs", "pub foo() {}") .build(); // First time around we should compile both foo and bar p.cargo("generate-lockfile") .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/rustdns` [LOCKING] 1 package to latest compatible version "#]]) .run(); // Modify a file manually, shouldn't trigger a recompile git_project.change_file("src/lib.rs", r#"pub fn bar() { println!("hello!"); }"#); // Commit the changes and make sure we don't trigger a recompile because the // lock file says not to change let repo = git2::Repository::open(&git_project.root()).unwrap(); git::add(&repo); git::commit(&repo); p.change_file("Cargo.toml", &workspace_toml.replace("2.29.8", "2.29.81")); p.cargo("update -p rootcrate") .with_stderr_data(str![[r#" [LOCKING] 2 packages to latest compatible versions [UPDATING] rootcrate v2.29.8 ([ROOT]/foo/rootcrate) -> v2.29.81 [UPDATING] subcrate v2.29.8 ([ROOT]/foo/subcrate) -> v2.29.81 "#]]) .run(); } #[cargo_test] fn update_only_members_order_two() { let git_project = git::new("rustdns", |project| { project .file("Cargo.toml", &basic_lib_manifest("rustdns")) .file("src/lib.rs", "pub fn bar() {}") }); let workspace_toml = format!( r#" [workspace.package] version = "2.29.8" edition = "2021" publish = false [workspace] members = [ "crate2", "crate1", ] resolver = "2" [workspace.dependencies] # Internal crates crate1 = {{ version = "*", path = "./crate1" }} # External dependencies rustdns = {{ version = "0.5.0", default-features = false, git = "{}" }} "#, git_project.url() ); let p = project() .file("Cargo.toml", &workspace_toml) .file( "crate2/Cargo.toml", r#" [package] name = "crate2" version.workspace = true edition.workspace = true publish.workspace = true [dependencies] crate1.workspace = true "#, ) .file("crate2/src/main.rs", "fn main() {}") .file( "crate1/Cargo.toml", r#" [package] name = "crate1" version.workspace = true edition.workspace = true publish.workspace = true [dependencies] rustdns.workspace = true "#, ) .file("crate1/src/lib.rs", "pub foo() {}") .build(); // First time around we should compile both foo and bar p.cargo("generate-lockfile") .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/rustdns` [LOCKING] 1 package to latest compatible version "#]]) .run(); // Modify a file manually, shouldn't trigger a recompile git_project.change_file("src/lib.rs", r#"pub fn bar() { println!("hello!"); }"#); // Commit the changes and make sure we don't trigger a recompile because the // lock file says not to change let repo = git2::Repository::open(&git_project.root()).unwrap(); git::add(&repo); git::commit(&repo); p.change_file("Cargo.toml", &workspace_toml.replace("2.29.8", "2.29.81")); p.cargo("update -p crate2") .with_stderr_data(str![[r#" [LOCKING] 2 packages to latest compatible versions [UPDATING] crate1 v2.29.8 ([ROOT]/foo/crate1) -> v2.29.81 [UPDATING] crate2 v2.29.8 ([ROOT]/foo/crate2) -> v2.29.81 "#]]) .run(); } #[cargo_test] fn update_only_members_with_workspace() { let git_project = git::new("rustdns", |project| { project .file("Cargo.toml", &basic_lib_manifest("rustdns")) .file("src/lib.rs", "pub fn bar() {}") }); let workspace_toml = format!( r#" [workspace.package] version = "2.29.8" edition = "2021" publish = false [workspace] members = [ "crate2", "crate1", ] resolver = "2" [workspace.dependencies] # Internal crates crate1 = {{ version = "*", path = "./crate1" }} # External dependencies rustdns = {{ version = "0.5.0", default-features = false, git = "{}" }} "#, git_project.url() ); let p = project() .file("Cargo.toml", &workspace_toml) .file( "crate2/Cargo.toml", r#" [package] name = "crate2" version.workspace = true edition.workspace = true publish.workspace = true [dependencies] crate1.workspace = true "#, ) .file("crate2/src/main.rs", "fn main() {}") .file( "crate1/Cargo.toml", r#" [package] name = "crate1" version.workspace = true edition.workspace = true publish.workspace = true [dependencies] rustdns.workspace = true "#, ) .file("crate1/src/lib.rs", "pub foo() {}") .build(); // First time around we should compile both foo and bar p.cargo("generate-lockfile") .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/rustdns` [LOCKING] 1 package to latest compatible version "#]]) .run(); // Modify a file manually, shouldn't trigger a recompile git_project.change_file("src/lib.rs", r#"pub fn bar() { println!("hello!"); }"#); // Commit the changes and make sure we don't trigger a recompile because the // lock file says not to change let repo = git2::Repository::open(&git_project.root()).unwrap(); git::add(&repo); git::commit(&repo); p.change_file("Cargo.toml", &workspace_toml.replace("2.29.8", "2.29.81")); p.cargo("update --workspace") .with_stderr_data(str![[r#" [LOCKING] 2 packages to latest compatible versions [UPDATING] crate1 v2.29.8 ([ROOT]/foo/crate1) -> v2.29.81 [UPDATING] crate2 v2.29.8 ([ROOT]/foo/crate2) -> v2.29.81 "#]]) .run(); } #[cargo_test] fn update_precise_git_revisions() { let (git_project, git_repo) = git::new_repo("git", |p| { p.file("Cargo.toml", &basic_lib_manifest("git")) .file("src/lib.rs", "") }); let tag_name = "Nazgûl"; git::tag(&git_repo, tag_name); let tag_commit_id = git_repo.head().unwrap().target().unwrap().to_string(); git_project.change_file("src/lib.rs", "fn f() {}"); git::add(&git_repo); let head_id = git::commit(&git_repo).to_string(); let short_id = &head_id[..8]; let url = git_project.url(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] git = {{ git = '{url}' }} "# ), ) .file("src/lib.rs", "") .build(); p.cargo("fetch") .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/git` [LOCKING] 1 package to latest compatible version "#]]) .run(); assert!(p.read_lockfile().contains(&head_id)); p.cargo("update git --precise") .arg(tag_name) .with_stderr_data(format!( "\ [UPDATING] git repository `[ROOTURL]/git` [UPDATING] git v0.5.0 ([ROOTURL]/git#[..]) -> #{} ", &tag_commit_id[..8], )) .run(); assert!(p.read_lockfile().contains(&tag_commit_id)); assert!(!p.read_lockfile().contains(&head_id)); p.cargo("update git --precise") .arg(short_id) .with_stderr_data(format!( "\ [UPDATING] git repository `[ROOTURL]/git` [UPDATING] git v0.5.0 ([ROOTURL]/git[..]) -> #{short_id} ", )) .run(); assert!(p.read_lockfile().contains(&head_id)); assert!(!p.read_lockfile().contains(&tag_commit_id)); // updating back to tag still requires a git fetch, // as the ref may change over time. p.cargo("update git --precise") .arg(tag_name) .with_stderr_data(format!( "\ [UPDATING] git repository `[ROOTURL]/git` [UPDATING] git v0.5.0 ([ROOTURL]/git#[..]) -> #{} ", &tag_commit_id[..8], )) .run(); assert!(p.read_lockfile().contains(&tag_commit_id)); assert!(!p.read_lockfile().contains(&head_id)); // Now make a tag looks like an oid. // It requires a git fetch, as the oid cannot be found in preexisting git db. let arbitrary_tag: String = "a".repeat(head_id.len()); git::tag(&git_repo, &arbitrary_tag); p.cargo("update git --precise") .arg(&arbitrary_tag) .with_stderr_data(format!( "\ [UPDATING] git repository `[ROOTURL]/git` [UPDATING] git v0.5.0 ([ROOTURL]/git#[..]) -> #{} ", &head_id[..8], )) .run(); assert!(p.read_lockfile().contains(&head_id)); assert!(!p.read_lockfile().contains(&tag_commit_id)); } #[cargo_test] fn precise_yanked() { Package::new("bar", "0.1.0").publish(); Package::new("bar", "0.1.1").yanked(true).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" [dependencies] bar = "0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); // Use non-yanked version. let lockfile = p.read_lockfile(); assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.0\"")); p.cargo("update --precise 0.1.1 bar") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [WARNING] selected package `bar@0.1.1` was yanked by the author | = [HELP] if possible, try a compatible non-yanked version [UPDATING] bar v0.1.0 -> v0.1.1 "#]]) .run(); // Use yanked version. let lockfile = p.read_lockfile(); assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.1\"")); } #[cargo_test] fn precise_yanked_multiple_presence() { Package::new("bar", "0.1.0").publish(); Package::new("bar", "0.1.1").yanked(true).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" [dependencies] bar = "0.1" baz = { package = "bar", version = "0.1" } "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); // Use non-yanked version. let lockfile = p.read_lockfile(); assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.0\"")); p.cargo("update --precise 0.1.1 bar") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [WARNING] selected package `bar@0.1.1` was yanked by the author | = [HELP] if possible, try a compatible non-yanked version [UPDATING] bar v0.1.0 -> v0.1.1 "#]]) .run(); // Use yanked version. let lockfile = p.read_lockfile(); assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.1\"")); } #[cargo_test] fn report_behind() { Package::new("two-ver", "0.1.0").publish(); Package::new("two-ver", "0.2.0").publish(); Package::new("pre", "1.0.0-alpha.0").publish(); Package::new("pre", "1.0.0-alpha.1").publish(); Package::new("breaking", "0.1.0").publish(); Package::new("breaking", "0.2.0").publish(); Package::new("breaking", "0.2.1-alpha.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" [dependencies] breaking = "0.1" pre = "=1.0.0-alpha.0" two-ver = "0.2.0" two-ver-one = { version = "0.1.0", package = "two-ver" } "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("breaking", "0.1.1").publish(); p.cargo("update --dry-run") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] breaking v0.1.0 -> v0.1.1 (available: v0.2.0) [NOTE] pass `--verbose` to see 2 unchanged dependencies behind latest [WARNING] not updating lockfile due to dry run "#]]) .run(); p.cargo("update --dry-run --verbose") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] breaking v0.1.0 -> v0.1.1 (available: v0.2.0) [UNCHANGED] pre v1.0.0-alpha.0 (available: v1.0.0-alpha.1) [UNCHANGED] two-ver v0.1.0 (available: v0.2.0) [NOTE] to see how you depend on a package, run `cargo tree --invert @` [WARNING] not updating lockfile due to dry run "#]]) .run(); p.cargo("update").run(); p.cargo("update --dry-run") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 0 packages to latest compatible versions [NOTE] pass `--verbose` to see 3 unchanged dependencies behind latest [WARNING] not updating lockfile due to dry run "#]]) .run(); p.cargo("update --dry-run --verbose") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 0 packages to latest compatible versions [UNCHANGED] breaking v0.1.1 (available: v0.2.0) [UNCHANGED] pre v1.0.0-alpha.0 (available: v1.0.0-alpha.1) [UNCHANGED] two-ver v0.1.0 (available: v0.2.0) [NOTE] to see how you depend on a package, run `cargo tree --invert @` [WARNING] not updating lockfile due to dry run "#]]) .run(); } #[cargo_test] fn update_with_missing_feature() { // Attempting to update a package to a version with a missing feature // should produce a warning. Package::new("bar", "0.1.0").feature("feat1", &[]).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = {version="0.1", features=["feat1"]} "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); // Publish an update that is missing the feature. Package::new("bar", "0.1.1").publish(); p.cargo("update") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 0 packages to latest compatible versions [NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest "#]]) .run(); // Publish a fixed version, should not warn. Package::new("bar", "0.1.2").feature("feat1", &[]).publish(); p.cargo("update") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [UPDATING] bar v0.1.0 -> v0.1.2 "#]]) .run(); } #[cargo_test] fn update_breaking_unstable() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] "#, ) .file("src/lib.rs", "") .build(); p.cargo("update --breaking") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] the `--breaking` flag is unstable, pass `-Z unstable-options` to enable it See https://github.com/rust-lang/cargo/issues/12425 for more information about the `--breaking` flag. "#]]) .run(); } #[cargo_test] fn update_breaking_dry_run() { Package::new("incompatible", "1.0.0").publish(); Package::new("ws", "1.0.0").publish(); let root_manifest = r#" # Check if formatting is preserved. Nothing here should change, due to dry-run. [workspace] members = ["foo"] [workspace.dependencies] ws = "1.0" # Preserve formatting "#; let crate_manifest = r#" # Check if formatting is preserved. Nothing here should change, due to dry-run. [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] incompatible = "1.0" # Preserve formatting ws.workspace = true # Preserve formatting "#; let p = project() .file("Cargo.toml", root_manifest) .file("foo/Cargo.toml", crate_manifest) .file("foo/src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); let lock_file = p.read_file("Cargo.lock"); Package::new("incompatible", "1.0.1").publish(); Package::new("ws", "1.0.1").publish(); Package::new("incompatible", "2.0.0").publish(); Package::new("ws", "2.0.0").publish(); p.cargo("update -Zunstable-options --dry-run --breaking") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPGRADING] incompatible ^1.0 -> ^2.0 [UPGRADING] ws ^1.0 -> ^2.0 [LOCKING] 2 packages to latest compatible versions [UPDATING] incompatible v1.0.0 -> v2.0.0 [UPDATING] ws v1.0.0 -> v2.0.0 [WARNING] aborting update due to dry run "#]]) .run(); let root_manifest_after = p.read_file("Cargo.toml"); assert_e2e().eq(&root_manifest_after, root_manifest); let crate_manifest_after = p.read_file("foo/Cargo.toml"); assert_e2e().eq(&crate_manifest_after, crate_manifest); let lock_file_after = p.read_file("Cargo.lock"); assert_e2e().eq(&lock_file_after, lock_file); } #[cargo_test] fn update_breaking() { registry::alt_init(); Package::new("compatible", "1.0.0").publish(); Package::new("incompatible", "1.0.0").publish(); Package::new("pinned", "1.0.0").publish(); Package::new("less-than", "1.0.0").publish(); Package::new("renamed-from", "1.0.0").publish(); Package::new("pre-release", "1.0.0").publish(); Package::new("yanked", "1.0.0").publish(); Package::new("ws", "1.0.0").publish(); Package::new("shared", "1.0.0").publish(); Package::new("multiple-locations", "1.0.0").publish(); Package::new("multiple-versions", "1.0.0").publish(); Package::new("multiple-versions", "2.0.0").publish(); Package::new("alternative-1", "1.0.0") .alternative(true) .publish(); Package::new("alternative-2", "1.0.0") .alternative(true) .publish(); Package::new("bar", "1.0.0").alternative(true).publish(); Package::new("multiple-registries", "1.0.0").publish(); Package::new("multiple-registries", "2.0.0") .alternative(true) .publish(); Package::new("multiple-source-types", "1.0.0").publish(); Package::new("platform-specific", "1.0.0").publish(); Package::new("dev", "1.0.0").publish(); Package::new("build", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" # Check if formatting is preserved [workspace] members = ["foo", "bar"] [workspace.dependencies] ws = "1.0" # This line gets partially rewritten "#, ) .file( "foo/Cargo.toml", r#" # Check if formatting is preserved [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] compatible = "1.0" # Comment incompatible = "1.0" # Comment pinned = "=1.0" # Comment less-than = "<99.0" # Comment renamed-to = { package = "renamed-from", version = "1.0" } # Comment pre-release = "1.0" # Comment yanked = "1.0" # Comment ws.workspace = true # Comment shared = "1.0" # Comment multiple-locations = { path = "../multiple-locations", version = "1.0" } # Comment multiple-versions = "1.0" # Comment alternative-1 = { registry = "alternative", version = "1.0" } # Comment multiple-registries = "1.0" # Comment bar = { path = "../bar", registry = "alternative", version = "1.0.0" } # Comment multiple-source-types = { path = "../multiple-source-types", version = "1.0.0" } # Comment [dependencies.alternative-2] # Comment version = "1.0" # Comment registry = "alternative" # Comment [target.'cfg(unix)'.dependencies] platform-specific = "1.0" # Comment [dev-dependencies] dev = "1.0" # Comment [build-dependencies] build = "1.0" # Comment "#, ) .file("foo/src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "1.0.0" edition = "2015" authors = [] [dependencies] shared = "1.0" multiple-versions = "2.0" multiple-registries = { registry = "alternative", version = "2.0" } # Comment multiple-source-types = "1.0" # Comment "#, ) .file("bar/src/lib.rs", "") .file( "multiple-locations/Cargo.toml", r#" [package] name = "multiple-locations" version = "1.0.0" edition = "2015" authors = [] "#, ) .file("multiple-locations/src/lib.rs", "") .file( "multiple-source-types/Cargo.toml", r#" [package] name = "multiple-source-types" version = "1.0.0" edition = "2015" authors = [] "#, ) .file("multiple-source-types/src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("compatible", "1.0.1").publish(); Package::new("incompatible", "1.0.1").publish(); Package::new("pinned", "1.0.1").publish(); Package::new("less-than", "1.0.1").publish(); Package::new("renamed-from", "1.0.1").publish(); Package::new("ws", "1.0.1").publish(); Package::new("multiple-locations", "1.0.1").publish(); Package::new("multiple-versions", "1.0.1").publish(); Package::new("multiple-versions", "2.0.1").publish(); Package::new("alternative-1", "1.0.1") .alternative(true) .publish(); Package::new("alternative-2", "1.0.1") .alternative(true) .publish(); Package::new("platform-specific", "1.0.1").publish(); Package::new("dev", "1.0.1").publish(); Package::new("build", "1.0.1").publish(); Package::new("incompatible", "2.0.0").publish(); Package::new("pinned", "2.0.0").publish(); Package::new("less-than", "2.0.0").publish(); Package::new("renamed-from", "2.0.0").publish(); Package::new("pre-release", "2.0.0-alpha").publish(); Package::new("yanked", "2.0.0").yanked(true).publish(); Package::new("ws", "2.0.0").publish(); Package::new("shared", "2.0.0").publish(); Package::new("multiple-locations", "2.0.0").publish(); Package::new("multiple-versions", "3.0.0").publish(); Package::new("alternative-1", "2.0.0") .alternative(true) .publish(); Package::new("alternative-2", "2.0.0") .alternative(true) .publish(); Package::new("bar", "2.0.0").alternative(true).publish(); Package::new("multiple-registries", "2.0.0").publish(); Package::new("multiple-registries", "3.0.0") .alternative(true) .publish(); Package::new("multiple-source-types", "2.0.0").publish(); Package::new("platform-specific", "2.0.0").publish(); Package::new("dev", "2.0.0").publish(); Package::new("build", "2.0.0").publish(); p.cargo("update -Zunstable-options --breaking") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `alternative` index [UPGRADING] multiple-registries ^2.0 -> ^3.0 [UPDATING] `dummy-registry` index [UPGRADING] multiple-source-types ^1.0 -> ^2.0 [UPGRADING] multiple-versions ^2.0 -> ^3.0 [UPGRADING] shared ^1.0 -> ^2.0 [UPGRADING] alternative-1 ^1.0 -> ^2.0 [UPGRADING] alternative-2 ^1.0 -> ^2.0 [UPGRADING] incompatible ^1.0 -> ^2.0 [UPGRADING] multiple-registries ^1.0 -> ^2.0 [UPGRADING] multiple-versions ^1.0 -> ^3.0 [UPGRADING] ws ^1.0 -> ^2.0 [UPGRADING] dev ^1.0 -> ^2.0 [UPGRADING] build ^1.0 -> ^2.0 [UPGRADING] platform-specific ^1.0 -> ^2.0 [LOCKING] 12 packages to latest compatible versions [UPDATING] alternative-1 v1.0.0 (registry `alternative`) -> v2.0.0 [UPDATING] alternative-2 v1.0.0 (registry `alternative`) -> v2.0.0 [UPDATING] build v1.0.0 -> v2.0.0 [UPDATING] dev v1.0.0 -> v2.0.0 [UPDATING] incompatible v1.0.0 -> v2.0.0 [UPDATING] multiple-registries v2.0.0 (registry `alternative`) -> v3.0.0 [UPDATING] multiple-registries v1.0.0 -> v2.0.0 [UPDATING] multiple-source-types v1.0.0 -> v2.0.0 [ADDING] multiple-versions v3.0.0 [UPDATING] platform-specific v1.0.0 -> v2.0.0 [UPDATING] shared v1.0.0 -> v2.0.0 [UPDATING] ws v1.0.0 -> v2.0.0 "#]]) .run(); let root_manifest = p.read_file("Cargo.toml"); assert_e2e().eq( &root_manifest, str![[r#" # Check if formatting is preserved [workspace] members = ["foo", "bar"] [workspace.dependencies] ws = "2.0" # This line gets partially rewritten "#]], ); let foo_manifest = p.read_file("foo/Cargo.toml"); assert_e2e().eq( &foo_manifest, str![[r#" # Check if formatting is preserved [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] compatible = "1.0" # Comment incompatible = "2.0" # Comment pinned = "=1.0" # Comment less-than = "<99.0" # Comment renamed-to = { package = "renamed-from", version = "1.0" } # Comment pre-release = "1.0" # Comment yanked = "1.0" # Comment ws.workspace = true # Comment shared = "2.0" # Comment multiple-locations = { path = "../multiple-locations", version = "1.0" } # Comment multiple-versions = "3.0" # Comment alternative-1 = { registry = "alternative", version = "2.0" } # Comment multiple-registries = "2.0" # Comment bar = { path = "../bar", registry = "alternative", version = "1.0.0" } # Comment multiple-source-types = { path = "../multiple-source-types", version = "1.0.0" } # Comment [dependencies.alternative-2] # Comment version = "2.0" # Comment registry = "alternative" # Comment [target.'cfg(unix)'.dependencies] platform-specific = "2.0" # Comment [dev-dependencies] dev = "2.0" # Comment [build-dependencies] build = "2.0" # Comment "#]], ); let bar_manifest = p.read_file("bar/Cargo.toml"); assert_e2e().eq( &bar_manifest, str![[r#" [package] name = "bar" version = "1.0.0" edition = "2015" authors = [] [dependencies] shared = "2.0" multiple-versions = "3.0" multiple-registries = { registry = "alternative", version = "3.0" } # Comment multiple-source-types = "2.0" # Comment "#]], ); p.cargo("update") .with_stderr_data(str![[r#" [UPDATING] `alternative` index [UPDATING] `dummy-registry` index [LOCKING] 4 packages to latest compatible versions [UPDATING] compatible v1.0.0 -> v1.0.1 [UPDATING] less-than v1.0.0 -> v2.0.0 [UPDATING] pinned v1.0.0 -> v1.0.1 (available: v2.0.0) [UPDATING] renamed-from v1.0.0 -> v1.0.1 (available: v2.0.0) "#]]) .run(); } #[cargo_test] fn update_breaking_specific_packages() { Package::new("just-foo", "1.0.0") .add_dep(Dependency::new("transitive-compatible", "1.0.0").build()) .add_dep(Dependency::new("transitive-incompatible", "1.0.0").build()) .publish(); Package::new("just-bar", "1.0.0").publish(); Package::new("shared", "1.0.0").publish(); Package::new("ws", "1.0.0").publish(); Package::new("transitive-compatible", "1.0.0").publish(); Package::new("transitive-incompatible", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "bar"] [workspace.dependencies] ws = "1.0" "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] just-foo = "1.0" shared = "1.0" ws.workspace = true "#, ) .file("foo/src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] just-bar = "1.0" shared = "1.0" ws.workspace = true "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("just-foo", "1.0.1") .add_dep(Dependency::new("transitive-compatible", "1.0.0").build()) .add_dep(Dependency::new("transitive-incompatible", "1.0.0").build()) .publish(); Package::new("just-bar", "1.0.1").publish(); Package::new("shared", "1.0.1").publish(); Package::new("ws", "1.0.1").publish(); Package::new("transitive-compatible", "1.0.1").publish(); Package::new("transitive-incompatible", "1.0.1").publish(); Package::new("just-foo", "2.0.0") // Upgrading just-foo implies accepting an update of transitive-compatible. .add_dep(Dependency::new("transitive-compatible", "1.0.1").build()) // Upgrading just-foo implies accepting a major update of transitive-incompatible. .add_dep(Dependency::new("transitive-incompatible", "2.0.0").build()) .publish(); Package::new("just-bar", "2.0.0").publish(); Package::new("shared", "2.0.0").publish(); Package::new("ws", "2.0.0").publish(); Package::new("transitive-incompatible", "2.0.0").publish(); p.cargo("update -Zunstable-options --breaking just-foo shared ws") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPGRADING] shared ^1.0 -> ^2.0 [UPGRADING] ws ^1.0 -> ^2.0 [UPGRADING] just-foo ^1.0 -> ^2.0 [LOCKING] 5 packages to latest compatible versions [UPDATING] just-foo v1.0.0 -> v2.0.0 [UPDATING] shared v1.0.0 -> v2.0.0 [UPDATING] transitive-compatible v1.0.0 -> v1.0.1 [UPDATING] transitive-incompatible v1.0.0 -> v2.0.0 [UPDATING] ws v1.0.0 -> v2.0.0 "#]]) .run(); } #[cargo_test] fn update_breaking_specific_packages_that_wont_update() { Package::new("compatible", "1.0.0").publish(); Package::new("renamed-from", "1.0.0").publish(); Package::new("non-semver", "1.0.0").publish(); Package::new("bar", "1.0.0") .add_dep(Dependency::new("transitive-compatible", "1.0.0").build()) .add_dep(Dependency::new("transitive-incompatible", "1.0.0").build()) .publish(); Package::new("transitive-compatible", "1.0.0").publish(); Package::new("transitive-incompatible", "1.0.0").publish(); let crate_manifest = r#" # Check if formatting is preserved [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] compatible = "1.0" # Comment renamed-to = { package = "renamed-from", version = "1.0" } # Comment non-semver = "~1.0" # Comment bar = "1.0" # Comment "#; let p = project() .file("Cargo.toml", crate_manifest) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); let lock_file = p.read_file("Cargo.lock"); Package::new("compatible", "1.0.1").publish(); Package::new("renamed-from", "1.0.1").publish(); Package::new("non-semver", "1.0.1").publish(); Package::new("transitive-compatible", "1.0.1").publish(); Package::new("transitive-incompatible", "1.0.1").publish(); Package::new("renamed-from", "2.0.0").publish(); Package::new("non-semver", "2.0.0").publish(); Package::new("transitive-incompatible", "2.0.0").publish(); // Test that transitive dependencies produce helpful errors p.cargo("update -Zunstable-options --breaking transitive-compatible transitive-incompatible") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specifications did not match any direct dependencies that could be upgraded transitive-compatible transitive-incompatible [NOTE] `transitive-compatible` exists as a transitive dependency but those are not available for upgrading through `--breaking` [NOTE] `transitive-incompatible` exists as a transitive dependency but those are not available for upgrading through `--breaking` "#]]) .run(); // Test that renamed, non-semver, no-breaking-update dependencies produce errors p.cargo("update -Zunstable-options --breaking compatible renamed-from non-semver") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] package ID specifications did not match any direct dependencies that could be upgraded compatible renamed-from non-semver "#]]) .run(); let crate_manifest_after = p.read_file("Cargo.toml"); assert_e2e().eq(&crate_manifest_after, crate_manifest); let lock_file_after = p.read_file("Cargo.lock"); assert_e2e().eq(&lock_file_after, lock_file); p.cargo( "update compatible renamed-from non-semver transitive-compatible transitive-incompatible", ) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [LOCKING] 5 packages to latest compatible versions [UPDATING] compatible v1.0.0 -> v1.0.1 [UPDATING] non-semver v1.0.0 -> v1.0.1 (available: v2.0.0) [UPDATING] renamed-from v1.0.0 -> v1.0.1 (available: v2.0.0) [UPDATING] transitive-compatible v1.0.0 -> v1.0.1 [UPDATING] transitive-incompatible v1.0.0 -> v1.0.1 "#]]) .run(); } #[cargo_test] fn update_breaking_without_lock_file() { Package::new("compatible", "1.0.0").publish(); Package::new("incompatible", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] compatible = "1.0" # Comment incompatible = "1.0" # Comment "#, ) .file("src/lib.rs", "") .build(); Package::new("compatible", "1.0.1").publish(); Package::new("incompatible", "1.0.1").publish(); Package::new("incompatible", "2.0.0").publish(); p.cargo("update -Zunstable-options --breaking") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [UPGRADING] incompatible ^1.0 -> ^2.0 [LOCKING] 2 packages to latest compatible versions "#]]) .run(); } #[cargo_test] fn update_breaking_spec_version() { Package::new("compatible", "1.0.0").publish(); Package::new("incompatible", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] compatible = "1.0" # Comment incompatible = "1.0" # Comment "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("compatible", "1.0.1").publish(); Package::new("incompatible", "1.0.1").publish(); Package::new("incompatible", "2.0.0").publish(); // Invalid spec p.cargo("update -Zunstable-options --breaking incompatible@foo") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] invalid package ID specification: `incompatible@foo` Caused by: expected a version like "1.32" "#]]) .run(); // Spec version not matching our current dependencies p.cargo("update -Zunstable-options --breaking incompatible@2.0.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification did not match any direct dependencies that could be upgraded incompatible@2.0.0 "#]]) .run(); // Spec source not matching our current dependencies p.cargo("update -Zunstable-options --breaking https://alternative.com#incompatible@1.0.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification did not match any direct dependencies that could be upgraded https://alternative.com/#incompatible@1.0.0 "#]]) .run(); // Accepted spec p.cargo("update -Zunstable-options --breaking incompatible@1.0.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [UPGRADING] incompatible ^1.0 -> ^2.0 [LOCKING] 1 package to latest compatible version [UPDATING] incompatible v1.0.0 -> v2.0.0 "#]]) .run(); // Accepted spec, full format Package::new("incompatible", "3.0.0").publish(); p.cargo("update -Zunstable-options --breaking https://github.com/rust-lang/crates.io-index#incompatible@2.0.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [UPGRADING] incompatible ^2.0 -> ^3.0 [LOCKING] 1 package to latest compatible version [UPDATING] incompatible v2.0.0 -> v3.0.0 "#]]) .run(); // Spec matches a dependency that will not be upgraded p.cargo("update -Zunstable-options --breaking compatible@1.0.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [ERROR] package ID specification did not match any direct dependencies that could be upgraded compatible@1.0.0 "#]]) .run(); // Non-existing versions p.cargo("update -Zunstable-options --breaking incompatible@9.0.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification did not match any direct dependencies that could be upgraded incompatible@9.0.0 "#]]) .run(); p.cargo("update -Zunstable-options --breaking compatible@9.0.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification did not match any direct dependencies that could be upgraded compatible@9.0.0 "#]]) .run(); } #[cargo_test] fn update_breaking_spec_version_transitive() { Package::new("dep", "1.0.0").publish(); Package::new("dep", "1.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] dep = "1.0" bar = { path = "bar", version = "0.0.1" } "#, ) .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] dep = "1.1" "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("dep", "1.1.1").publish(); Package::new("dep", "2.0.0").publish(); // Will upgrade the direct dependency p.cargo("update -Zunstable-options --breaking dep@1.0") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [UPGRADING] dep ^1.0 -> ^2.0 [LOCKING] 1 package to latest compatible version [ADDING] dep v2.0.0 "#]]) .run(); // But not the transitive one, because bar is not a workspace member p.cargo("update -Zunstable-options --breaking dep@1.1") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [ERROR] package ID specification did not match any direct dependencies that could be upgraded dep@1.1 "#]]) .run(); // A non-breaking update is different, as it will update transitive dependencies p.cargo("update dep@1.1") .with_stderr_data(str![[r#" [UPDATING] `[..]` index [LOCKING] 1 package to latest compatible version [UPDATING] dep v1.1.0 -> v1.1.1 "#]]) .run(); } #[cargo_test] fn update_breaking_mixed_compatibility() { Package::new("mixed-compatibility", "1.0.0").publish(); Package::new("mixed-compatibility", "2.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "bar"] "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] mixed-compatibility = "1.0" "#, ) .file("foo/src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" edition = "2015" authors = [] [dependencies] mixed-compatibility = "2.0" "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("mixed-compatibility", "2.0.1").publish(); p.cargo("update -Zunstable-options --breaking") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [UPGRADING] mixed-compatibility ^1.0 -> ^2.0 [LOCKING] 1 package to latest compatible version [ADDING] mixed-compatibility v2.0.1 "#]]) .run(); } #[cargo_test] fn update_breaking_mixed_pinning_renaming() { Package::new("mixed-pinned", "1.0.0").publish(); Package::new("mixed-ws-pinned", "1.0.0").publish(); Package::new("renamed-from", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["pinned", "unpinned", "mixed"] [workspace.dependencies] mixed-ws-pinned = "=1.0" "#, ) .file( "pinned/Cargo.toml", r#" [package] name = "pinned" version = "0.0.1" edition = "2015" authors = [] [dependencies] mixed-pinned = "=1.0" mixed-ws-pinned.workspace = true renamed-to = { package = "renamed-from", version = "1.0" } "#, ) .file("pinned/src/lib.rs", "") .file( "unpinned/Cargo.toml", r#" [package] name = "unpinned" version = "0.0.1" edition = "2015" authors = [] [dependencies] mixed-pinned = "1.0" mixed-ws-pinned = "1.0" renamed-from = "1.0" "#, ) .file("unpinned/src/lib.rs", "") .file( "mixed/Cargo.toml", r#" [package] name = "mixed" version = "0.0.1" edition = "2015" authors = [] [target.'cfg(windows)'.dependencies] mixed-pinned = "1.0" [target.'cfg(unix)'.dependencies] mixed-pinned = "=1.0" "#, ) .file("mixed/src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("mixed-pinned", "2.0.0").publish(); Package::new("mixed-ws-pinned", "2.0.0").publish(); Package::new("renamed-from", "2.0.0").publish(); p.cargo("update -Zunstable-options --breaking") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `[..]` index [UPGRADING] mixed-pinned ^1.0 -> ^2.0 [UPGRADING] mixed-ws-pinned ^1.0 -> ^2.0 [UPGRADING] renamed-from ^1.0 -> ^2.0 [LOCKING] 3 packages to latest compatible versions [ADDING] mixed-pinned v2.0.0 [ADDING] mixed-ws-pinned v2.0.0 [ADDING] renamed-from v2.0.0 "#]]) .run(); let root_manifest = p.read_file("Cargo.toml"); assert_e2e().eq( &root_manifest, str![[r#" [workspace] members = ["pinned", "unpinned", "mixed"] [workspace.dependencies] mixed-ws-pinned = "=1.0" "#]], ); let pinned_manifest = p.read_file("pinned/Cargo.toml"); assert_e2e().eq( &pinned_manifest, str![[r#" [package] name = "pinned" version = "0.0.1" edition = "2015" authors = [] [dependencies] mixed-pinned = "=1.0" mixed-ws-pinned.workspace = true renamed-to = { package = "renamed-from", version = "1.0" } "#]], ); let unpinned_manifest = p.read_file("unpinned/Cargo.toml"); assert_e2e().eq( &unpinned_manifest, str![[r#" [package] name = "unpinned" version = "0.0.1" edition = "2015" authors = [] [dependencies] mixed-pinned = "2.0" mixed-ws-pinned = "2.0" renamed-from = "2.0" "#]], ); let mixed_manifest = p.read_file("mixed/Cargo.toml"); assert_e2e().eq( &mixed_manifest, str![[r#" [package] name = "mixed" version = "0.0.1" edition = "2015" authors = [] [target.'cfg(windows)'.dependencies] mixed-pinned = "2.0" [target.'cfg(unix)'.dependencies] mixed-pinned = "=1.0" "#]], ); } #[cargo_test] fn update_breaking_pre_release_downgrade() { Package::new("bar", "2.0.0-beta.21").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "2.0.0-beta.21" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); // The purpose of this test is // to demonstrate that `update --breaking` will not try to downgrade to the latest stable version (1.7.0), // but will error because the dependency uses an exact version (not caret). Package::new("bar", "1.7.0").publish(); p.cargo("update -Zunstable-options --breaking bar") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] package ID specification did not match any direct dependencies that could be upgraded bar "#]]) .run(); } #[cargo_test] fn update_breaking_pre_release_upgrade() { Package::new("bar", "2.0.0-beta.21").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "2.0.0-beta.21" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); // `2.0.0-beta.21` cannot be upgraded with --breaking because it uses an exact version (not caret) Package::new("bar", "2.0.0-beta.22").publish(); p.cargo("update -Zunstable-options --breaking bar") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] package ID specification did not match any direct dependencies that could be upgraded bar "#]]) .run(); // `2.0.0-beta.21` cannot be upgraded to `2.0.0` with --breaking because it uses an exact version (not caret) Package::new("bar", "2.0.0").publish(); p.cargo("update -Zunstable-options --breaking bar") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [ERROR] package ID specification did not match any direct dependencies that could be upgraded bar "#]]) .run(); Package::new("bar", "3.0.0").publish(); p.cargo("update -Zunstable-options --breaking bar") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPGRADING] bar ^2.0.0-beta.21 -> ^3.0.0 [LOCKING] 1 package to latest compatible version [UPDATING] bar v2.0.0-beta.21 -> v3.0.0 "#]]) .run(); } #[cargo_test] fn prefixed_v_in_version() { Package::new("bar", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "1.0.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("bar", "1.0.1").publish(); p.cargo("update bar --precise v1.0.1") .with_status(101) .with_stderr_data(str![[r#" [ERROR] the version provided, `v1.0.1` is not a valid SemVer version [HELP] try changing the version to `1.0.1` Caused by: unexpected character 'v' while parsing major version number "#]]) .run(); } #[cargo_test] fn update_breaking_missing_package_error() { Package::new("bar", "1.0.0").publish(); Package::new("transitive", "1.0.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("bar", "2.0.0") .add_dep(Dependency::new("transitive", "1.0.0").build()) .publish(); // Non-existent package reports an error p.cargo("update -Zunstable-options --breaking no_such_crate") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification did not match any direct dependencies that could be upgraded no_such_crate "#]]) .run(); // Valid package processes, invalid package reports error p.cargo("update -Zunstable-options --breaking bar no_such_crate") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPGRADING] bar ^1.0 -> ^2.0 [ERROR] package ID specification did not match any direct dependencies that could be upgraded no_such_crate "#]]) .run(); // Successfully upgrade bar to add transitive to lockfile p.cargo("update -Zunstable-options --breaking bar") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [UPGRADING] bar ^1.0 -> ^2.0 [LOCKING] 2 packages to latest compatible versions [UPDATING] bar v1.0.0 -> v2.0.0 [ADDING] transitive v1.0.0 "#]]) .run(); // Transitive dependency reports helpful error p.cargo("update -Zunstable-options --breaking transitive") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specification did not match any direct dependencies that could be upgraded transitive [NOTE] `transitive` exists as a transitive dependency but those are not available for upgrading through `--breaking` "#]]) .run(); // Multiple error types reported together p.cargo("update -Zunstable-options --breaking no_such_crate transitive another_missing") .masquerade_as_nightly_cargo(&["update-breaking"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] package ID specifications did not match any direct dependencies that could be upgraded no_such_crate transitive another_missing [NOTE] `transitive` exists as a transitive dependency but those are not available for upgrading through `--breaking` "#]]) .run(); } ================================================ FILE: tests/testsuite/utils/cross_compile.rs ================================================ //! Support for cross-compile tests with the `--target` flag. //! //! Note that cross-testing is very limited. You need to install the //! "alternate" target to the host (32-bit for 64-bit hosts or vice-versa). //! //! Set `CFG_DISABLE_CROSS_TESTS=1` environment variable to disable these tests //! if you are unable to use the alternate target. Unfortunately 32-bit //! support on macOS is going away, so macOS users are out of luck. //! //! These tests are all disabled on rust-lang/rust's CI, but run in Cargo's CI. use crate::prelude::*; use cargo_test_support::{basic_manifest, cross_compile::alternate, main_file, project}; use cargo_util::ProcessError; use std::fmt::Write; use std::{ process::{Command, Output}, sync::{ Once, atomic::{AtomicBool, Ordering}, }, }; /// Whether or not the resulting cross binaries can run on the host. static CAN_RUN_ON_HOST: AtomicBool = AtomicBool::new(false); pub fn disabled() -> bool { // First, disable if requested. match std::env::var("CFG_DISABLE_CROSS_TESTS") { Ok(ref s) if *s == "1" => return true, _ => {} } // It requires setting `target.linker` for cross-compilation to work on aarch64, // so not going to bother now. if cfg!(all(target_arch = "aarch64", target_os = "linux")) { return true; } // Cross tests are only tested to work on macos, linux, and MSVC windows. if !(cfg!(target_os = "macos") || cfg!(target_os = "linux") || cfg!(target_env = "msvc")) { return true; } // It's not particularly common to have a cross-compilation setup, so // try to detect that before we fail a bunch of tests through no fault // of the user. static CAN_BUILD_CROSS_TESTS: AtomicBool = AtomicBool::new(false); static CHECK: Once = Once::new(); let cross_target = alternate(); let run_cross_test = || -> anyhow::Result { let p = project() .at("cross_test") .file("Cargo.toml", &basic_manifest("cross_test", "1.0.0")) .file("src/main.rs", &main_file(r#""testing!""#, &[])) .build(); let build_result = p .cargo("build --target") .arg(&cross_target) .exec_with_output(); if build_result.is_ok() { CAN_BUILD_CROSS_TESTS.store(true, Ordering::SeqCst); } let result = p .cargo("run --target") .arg(&cross_target) .exec_with_output(); if result.is_ok() { CAN_RUN_ON_HOST.store(true, Ordering::SeqCst); } build_result }; CHECK.call_once(|| { drop(run_cross_test()); }); if CAN_BUILD_CROSS_TESTS.load(Ordering::SeqCst) { // We were able to compile a simple project, so the user has the // necessary `std::` bits installed. Therefore, tests should not // be disabled. return false; } // We can't compile a simple cross project. We want to warn the user // by failing a single test and having the remainder of the cross tests // pass. We don't use `std::sync::Once` here because panicking inside its // `call_once` method would poison the `Once` instance, which is not what // we want. static HAVE_WARNED: AtomicBool = AtomicBool::new(false); if HAVE_WARNED.swap(true, Ordering::SeqCst) { // We are some other test and somebody else is handling the warning. // Just disable the current test. return true; } // We are responsible for warning the user, which we do by panicking. let mut message = format!( " Cannot cross compile to {}. This failure can be safely ignored. If you would prefer to not see this failure, you can set the environment variable CFG_DISABLE_CROSS_TESTS to \"1\". Alternatively, you can install the necessary libraries to enable cross compilation tests. Cross compilation tests depend on your host platform. ", cross_target ); if cfg!(target_os = "linux") { message.push_str( " Linux cross tests target i686-unknown-linux-gnu, which requires the ability to build and run 32-bit targets. This requires the 32-bit libraries to be installed. For example, on Ubuntu, run `sudo apt install gcc-multilib` to install the necessary libraries. ", ); } else if cfg!(target_os = "macos") { message.push_str( " macOS on aarch64 cross tests to target x86_64-apple-darwin. This should be natively supported via Xcode, nothing additional besides the rustup target should be needed. ", ); } else if cfg!(target_os = "windows") { message.push_str( " Windows cross tests target i686-pc-windows-msvc, which requires the ability to build and run 32-bit targets. This should work automatically if you have properly installed Visual Studio build tools. ", ); } else { // The check at the top should prevent this. panic!("platform should have been skipped"); } let rustup_available = Command::new("rustup").output().is_ok(); if rustup_available { write!( message, " Make sure that the appropriate `rustc` target is installed with rustup: rustup target add {} ", cross_target ) .unwrap(); } else { write!( message, " rustup does not appear to be installed. Make sure that the appropriate `rustc` target is installed for the target `{}`. ", cross_target ) .unwrap(); } // Show the actual error message. match run_cross_test() { Ok(_) => message.push_str("\nUh oh, second run succeeded?\n"), Err(err) => match err.downcast_ref::() { Some(proc_err) => write!(message, "\nTest error: {}\n", proc_err).unwrap(), None => write!(message, "\nUnexpected non-process error: {}\n", err).unwrap(), }, } panic!("{}", message); } /// Whether or not the host can run cross-compiled executables. pub fn can_run_on_host() -> bool { if disabled() { return false; } assert!(CAN_RUN_ON_HOST.load(Ordering::SeqCst)); return true; } ================================================ FILE: tests/testsuite/utils/ext.rs ================================================ use std::path::PathBuf; use cargo_test_support::{ArgLineCommandExt, Execs, Project, TestEnvCommandExt, compare}; pub trait CargoProjectExt { /// Creates a `ProcessBuilder` to run cargo. /// /// Arguments can be separated by spaces. /// /// For `cargo run`, see [`Project::rename_run`]. /// /// # Example: /// /// ```no_run /// # let p = cargo_test_support::project().build(); /// p.cargo("build --bin foo").run(); /// ``` fn cargo(&self, cmd: &str) -> Execs; } impl CargoProjectExt for Project { fn cargo(&self, cmd: &str) -> Execs { let cargo = cargo_exe(); let mut execs = self.process(&cargo); execs.env("CARGO", cargo); execs.arg_line(cmd); execs } } /// Path to the cargo binary pub fn cargo_exe() -> PathBuf { snapbox::cmd::cargo_bin!("cargo").to_path_buf() } /// Test the cargo command pub trait CargoCommandExt { fn cargo_ui() -> Self; } impl CargoCommandExt for snapbox::cmd::Command { fn cargo_ui() -> Self { Self::new(cargo_exe()) .with_assert(compare::assert_ui()) .env("CARGO_TERM_COLOR", "always") .env("CARGO_TERM_HYPERLINKS", "true") .test_env() } } ================================================ FILE: tests/testsuite/utils/mod.rs ================================================ use std::path::PathBuf; use cargo_test_support::{ArgLineCommandExt, Execs, execs, process}; pub mod cross_compile; pub mod ext; pub mod tools; /// Run `cargo $arg_line`, see [`Execs`] pub fn cargo_process(arg_line: &str) -> Execs { let cargo = cargo_exe(); let mut p = process(&cargo); p.env("CARGO", cargo); p.arg_line(arg_line); execs().with_process_builder(p) } /// Path to the cargo binary pub fn cargo_exe() -> PathBuf { snapbox::cmd::cargo_bin!("cargo").to_path_buf() } ================================================ FILE: tests/testsuite/utils/tools.rs ================================================ //! Common executables that can be reused by various tests. use crate::prelude::*; use cargo_test_support::{Project, basic_manifest, paths, project}; use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::sync::OnceLock; static ECHO_WRAPPER: OnceLock>> = OnceLock::new(); static ECHO: OnceLock>> = OnceLock::new(); static CLIPPY_DRIVER: OnceLock>> = OnceLock::new(); /// Returns the path to an executable that works as a wrapper around rustc. /// /// The wrapper will echo the command line it was called with to stderr. pub fn echo_wrapper() -> PathBuf { let mut lock = ECHO_WRAPPER .get_or_init(|| Default::default()) .lock() .unwrap(); if let Some(path) = &*lock { return path.clone(); } let p = project() .at(paths::global_root().join("rustc-echo-wrapper")) .file("Cargo.toml", &basic_manifest("rustc-echo-wrapper", "1.0.0")) .file( "src/main.rs", r#" use std::fs::read_to_string; use std::path::PathBuf; fn main() { // Handle args from `@path` argfile for rustc let args = std::env::args() .flat_map(|p| if let Some(p) = p.strip_prefix("@") { read_to_string(p).unwrap().lines().map(String::from).collect() } else { vec![p] }) .collect::>(); eprintln!("WRAPPER CALLED: {}", args[1..].join(" ")); let status = std::process::Command::new(&args[1]) .args(&args[2..]).status().unwrap(); std::process::exit(status.code().unwrap_or(1)); } "#, ) .build(); p.cargo("build").run(); let path = p.bin("rustc-echo-wrapper"); *lock = Some(path.clone()); path } /// Returns the path to an executable that prints its arguments. /// /// Do not expect this to be anything fancy. pub fn echo() -> PathBuf { let mut lock = ECHO.get_or_init(|| Default::default()).lock().unwrap(); if let Some(path) = &*lock { return path.clone(); } if let Ok(path) = cargo_util::paths::resolve_executable(Path::new("echo")) { *lock = Some(path.clone()); return path; } // Often on Windows, `echo` is not available. let p = project() .at(paths::global_root().join("basic-echo")) .file("Cargo.toml", &basic_manifest("basic-echo", "1.0.0")) .file( "src/main.rs", r#" fn main() { let mut s = String::new(); let mut it = std::env::args().skip(1).peekable(); while let Some(n) = it.next() { s.push_str(&n); if it.peek().is_some() { s.push(' '); } } println!("{}", s); } "#, ) .build(); p.cargo("build").run(); let path = p.bin("basic-echo"); *lock = Some(path.clone()); path } /// Returns a project which builds a cargo-echo simple subcommand pub fn echo_subcommand() -> Project { let p = project() .at("cargo-echo") .file("Cargo.toml", &basic_manifest("cargo-echo", "0.0.1")) .file( "src/main.rs", r#" fn main() { let args: Vec<_> = ::std::env::args().skip(1).collect(); println!("{}", args.join(" ")); } "#, ) .build(); p.cargo("build").run(); p } /// A wrapper around `rustc` instead of calling `clippy`. pub fn wrapped_clippy_driver() -> PathBuf { let mut lock = CLIPPY_DRIVER .get_or_init(|| Default::default()) .lock() .unwrap(); if let Some(path) = &*lock { return path.clone(); } let clippy_driver = project() .at(paths::global_root().join("clippy-driver")) .file("Cargo.toml", &basic_manifest("clippy-driver", "0.0.1")) .file( "src/main.rs", r#" fn main() { let mut args = std::env::args_os(); let _me = args.next().unwrap(); let rustc = args.next().unwrap(); let status = std::process::Command::new(rustc).args(args).status().unwrap(); std::process::exit(status.code().unwrap_or(1)); } "#, ) .build(); clippy_driver.cargo("build").run(); let path = clippy_driver.bin("clippy-driver"); *lock = Some(path.clone()); path } ================================================ FILE: tests/testsuite/vendor.rs ================================================ //! Tests for the `cargo vendor` command. //! //! Note that every test here uses `--respect-source-config` so that the //! "fake" crates.io is used. Otherwise `vendor` would download the crates.io //! index from the network. use std::fs; use crate::prelude::*; use cargo_test_support::assert_deterministic_mtime; use cargo_test_support::compare::assert_e2e; use cargo_test_support::git; use cargo_test_support::registry::{self, Package, RegistryBuilder}; use cargo_test_support::str; use cargo_test_support::{Project, basic_lib_manifest, basic_manifest, paths, project}; #[cargo_test] fn vendor_simple() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] log = "0.3.5" "#, ) .file("src/lib.rs", "") .build(); Package::new("log", "0.3.5").publish(); p.cargo("vendor --respect-source-config").run(); let lock = p.read_file("vendor/log/Cargo.toml"); assert!(lock.contains("version = \"0.3.5\"")); add_crates_io_vendor_config(&p); p.cargo("check").run(); } #[cargo_test] fn vendor_sample_config() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] log = "0.3.5" "#, ) .file("src/lib.rs", "") .build(); Package::new("log", "0.3.5").publish(); p.cargo("vendor --respect-source-config") .with_stdout_data(str![[r#" [source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "vendor" "#]]) .run(); } #[cargo_test] fn vendor_sample_config_alt_registry() { let registry = RegistryBuilder::new().alternative().http_index().build(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] log = { version = "0.3.5", registry = "alternative" } "#, ) .file("src/lib.rs", "") .build(); Package::new("log", "0.3.5").alternative(true).publish(); p.cargo("vendor --respect-source-config") .with_stdout_data(format!( r#"[source."{0}"] registry = "{0}" replace-with = "vendored-sources" [source.vendored-sources] directory = "vendor" "#, registry.index_url() )) .run(); } #[cargo_test] fn vendor_path_specified() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] log = "0.3.5" "#, ) .file("src/lib.rs", "") .build(); Package::new("log", "0.3.5").publish(); let path = if cfg!(windows) { r#"deps\.vendor"# } else { "deps/.vendor" }; let output = p.cargo("vendor --respect-source-config").arg(path).run(); // Assert against original output to ensure that // path is normalized by `ops::vendor` on Windows. assert_eq!( &String::from_utf8(output.stdout).unwrap(), r#"[source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "deps/.vendor" "# ); let lock = p.read_file("deps/.vendor/log/Cargo.toml"); assert!(lock.contains("version = \"0.3.5\"")); } fn add_crates_io_vendor_config(p: &Project) { p.change_file( ".cargo/config.toml", r#" [source.crates-io] replace-with = 'vendor' [source.vendor] directory = 'vendor' "#, ); } fn add_git_vendor_config(p: &Project, git_project: &Project) { p.change_file( ".cargo/config.toml", &format!( r#" [source."git+{url}"] git = "{url}" replace-with = 'vendor' [source.vendor] directory = 'vendor' "#, url = git_project.url() ), ); } #[cargo_test] fn package_exclude() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = "0.1.0" "#, ) .file("src/lib.rs", "") .build(); Package::new("bar", "0.1.0") .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" exclude = [".*", "!.include", "!.dotdir/include"] "#, ) .file("src/lib.rs", "") .file(".exclude", "") .file(".include", "") .file(".dotdir/exclude", "") .file(".dotdir/include", "") .publish(); p.cargo("vendor --respect-source-config").run(); let csum = p.read_file("vendor/bar/.cargo-checksum.json"); // Everything is included because `cargo-vendor` // do direct extractions from tarballs // (Some are excluded like `.git` or `.cargo-ok` though.) assert!(csum.contains(".include")); assert!(csum.contains(".exclude")); assert!(csum.contains(".dotdir/exclude")); assert!(csum.contains(".dotdir/include")); } #[cargo_test] fn discovery_inferred_build_rs_included() { let git_project = git::new("dep", |project| { project .file( "Cargo.toml", r#" [package] name = "dep" version = "0.0.1" edition = "2015" license = "MIT" description = "foo" documentation = "docs.rs/foo" authors = [] include = ["src/lib.rs", "build.rs"] "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" edition = "2015" [dependencies.dep] git = '{}' "#, git_project.url() ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("vendor --respect-source-config").run(); add_git_vendor_config(&p, &git_project); let lock = p.read_file("vendor/dep/Cargo.toml"); assert_e2e().eq( lock, str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "dep" version = "0.0.1" authors = [] build = "build.rs" include = [ "src/lib.rs", "build.rs", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" documentation = "docs.rs/foo" readme = false license = "MIT" [lib] name = "dep" path = "src/lib.rs" "##]], ); p.cargo("check").run(); } #[cargo_test] fn discovery_inferred_build_rs_excluded() { let git_project = git::new("dep", |project| { project .file( "Cargo.toml", r#" [package] name = "dep" version = "0.0.1" edition = "2015" license = "MIT" description = "foo" documentation = "docs.rs/foo" authors = [] include = ["src/lib.rs"] "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" edition = "2015" [dependencies.dep] git = '{}' "#, git_project.url() ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("vendor --respect-source-config").run(); add_git_vendor_config(&p, &git_project); let lock = p.read_file("vendor/dep/Cargo.toml"); assert_e2e().eq( lock, str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "dep" version = "0.0.1" authors = [] build = false include = ["src/lib.rs"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" documentation = "docs.rs/foo" readme = false license = "MIT" [lib] name = "dep" path = "src/lib.rs" "##]], ); p.cargo("check").run(); } #[cargo_test] fn discovery_inferred_lib_included() { let git_project = git::new("dep", |project| { project .file( "Cargo.toml", r#" [package] name = "dep" version = "0.0.1" edition = "2015" license = "MIT" description = "foo" documentation = "docs.rs/foo" authors = [] include = ["src/main.rs", "src/lib.rs"] "#, ) .file("src/main.rs", "fn main() {}") .file("src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" edition = "2015" [dependencies.dep] git = '{}' "#, git_project.url() ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("vendor --respect-source-config").run(); add_git_vendor_config(&p, &git_project); let lock = p.read_file("vendor/dep/Cargo.toml"); assert_e2e().eq( lock, str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "dep" version = "0.0.1" authors = [] build = false include = [ "src/main.rs", "src/lib.rs", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" documentation = "docs.rs/foo" readme = false license = "MIT" [lib] name = "dep" path = "src/lib.rs" [[bin]] name = "dep" path = "src/main.rs" "##]], ); p.cargo("check").run(); } #[cargo_test] fn discovery_inferred_lib_excluded() { let git_project = git::new("dep", |project| { project .file( "Cargo.toml", r#" [package] name = "dep" version = "0.0.1" edition = "2015" license = "MIT" description = "foo" documentation = "docs.rs/foo" authors = [] include = ["src/main.rs"] "#, ) .file("src/main.rs", "fn main() {}") .file("src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" edition = "2015" [dependencies.dep] git = '{}' "#, git_project.url() ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("vendor --respect-source-config").run(); add_git_vendor_config(&p, &git_project); let lock = p.read_file("vendor/dep/Cargo.toml"); assert_e2e().eq( lock, str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "dep" version = "0.0.1" authors = [] build = false include = ["src/main.rs"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" documentation = "docs.rs/foo" readme = false license = "MIT" [[bin]] name = "dep" path = "src/main.rs" "##]], ); p.cargo("check").run(); } #[cargo_test] fn discovery_inferred_other_included() { let git_project = git::new("dep", |project| { project .file( "Cargo.toml", r#" [package] name = "dep" version = "0.0.1" edition = "2015" license = "MIT" description = "foo" documentation = "docs.rs/foo" authors = [] include = ["src/lib.rs", "src/bin/foo/main.rs", "examples/example_foo.rs", "tests/test_foo.rs", "benches/bench_foo.rs"] "#, ) .file("src/lib.rs", "") .file("src/bin/foo/main.rs", "fn main() {}") .file("examples/example_foo.rs", "fn main() {}") .file("tests/test_foo.rs", "fn main() {}") .file("benches/bench_foo.rs", "fn main() {}") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" edition = "2015" [dependencies.dep] git = '{}' "#, git_project.url() ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("vendor --respect-source-config").run(); add_git_vendor_config(&p, &git_project); let lock = p.read_file("vendor/dep/Cargo.toml"); assert_e2e().eq( lock, str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "dep" version = "0.0.1" authors = [] build = false include = [ "src/lib.rs", "src/bin/foo/main.rs", "examples/example_foo.rs", "tests/test_foo.rs", "benches/bench_foo.rs", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" documentation = "docs.rs/foo" readme = false license = "MIT" [lib] name = "dep" path = "src/lib.rs" [[bin]] name = "foo" path = "src/bin/foo/main.rs" [[example]] name = "example_foo" path = "examples/example_foo.rs" [[test]] name = "test_foo" path = "tests/test_foo.rs" [[bench]] name = "bench_foo" path = "benches/bench_foo.rs" "##]], ); p.cargo("check").run(); } #[cargo_test] fn discovery_inferred_other_excluded() { let git_project = git::new("dep", |project| { project .file( "Cargo.toml", r#" [package] name = "dep" version = "0.0.1" edition = "2015" license = "MIT" description = "foo" documentation = "docs.rs/foo" authors = [] include = ["src/lib.rs"] "#, ) .file("src/lib.rs", "") .file("src/bin/foo/main.rs", "fn main() {}") .file("examples/example_foo.rs", "fn main() {}") .file("tests/test_foo.rs", "fn main() {}") .file("benches/bench_foo.rs", "fn main() {}") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" edition = "2015" [dependencies.dep] git = '{}' "#, git_project.url() ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("vendor --respect-source-config").run(); add_git_vendor_config(&p, &git_project); let lock = p.read_file("vendor/dep/Cargo.toml"); assert_e2e().eq( lock, str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "dep" version = "0.0.1" authors = [] build = false include = ["src/lib.rs"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" documentation = "docs.rs/foo" readme = false license = "MIT" [lib] name = "dep" path = "src/lib.rs" "##]], ); p.cargo("check").run(); } #[cargo_test] fn two_versions() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bitflags = "0.8.0" bar = { path = "bar" } "#, ) .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" [dependencies] bitflags = "0.7.0" "#, ) .file("bar/src/lib.rs", "") .build(); Package::new("bitflags", "0.7.0").publish(); Package::new("bitflags", "0.8.0").publish(); p.cargo("vendor --respect-source-config").run(); let lock = p.read_file("vendor/bitflags/Cargo.toml"); assert!(lock.contains("version = \"0.8.0\"")); let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); add_crates_io_vendor_config(&p); p.cargo("check").run(); } #[cargo_test] fn two_explicit_versions() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bitflags = "0.8.0" bar = { path = "bar" } "#, ) .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" [dependencies] bitflags = "0.7.0" "#, ) .file("bar/src/lib.rs", "") .build(); Package::new("bitflags", "0.7.0").publish(); Package::new("bitflags", "0.8.0").publish(); p.cargo("vendor --respect-source-config --versioned-dirs") .run(); let lock = p.read_file("vendor/bitflags-0.8.0/Cargo.toml"); assert!(lock.contains("version = \"0.8.0\"")); let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); add_crates_io_vendor_config(&p); p.cargo("check").run(); } #[cargo_test] fn help() { let p = project().build(); p.cargo("vendor -h").run(); } #[cargo_test] fn update_versions() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bitflags = "0.7.0" "#, ) .file("src/lib.rs", "") .build(); Package::new("bitflags", "0.7.0").publish(); Package::new("bitflags", "0.8.0").publish(); p.cargo("vendor --respect-source-config").run(); let lock = p.read_file("vendor/bitflags/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); p.change_file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bitflags = "0.8.0" "#, ); p.cargo("vendor --respect-source-config").run(); let lock = p.read_file("vendor/bitflags/Cargo.toml"); assert!(lock.contains("version = \"0.8.0\"")); } #[cargo_test] fn two_lockfiles() { let p = project() .no_manifest() .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bitflags = "=0.7.0" "#, ) .file("foo/src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" [dependencies] bitflags = "=0.8.0" "#, ) .file("bar/src/lib.rs", "") .build(); Package::new("bitflags", "0.7.0").publish(); Package::new("bitflags", "0.8.0").publish(); p.cargo("vendor --respect-source-config -s bar/Cargo.toml --manifest-path foo/Cargo.toml") .run(); let lock = p.read_file("vendor/bitflags/Cargo.toml"); assert!(lock.contains("version = \"0.8.0\"")); let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); add_crates_io_vendor_config(&p); p.cargo("check").cwd("foo").run(); p.cargo("check").cwd("bar").run(); } #[cargo_test] fn test_sync_argument() { let p = project() .no_manifest() .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bitflags = "=0.7.0" "#, ) .file("foo/src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" [dependencies] bitflags = "=0.8.0" "#, ) .file("bar/src/lib.rs", "") .file( "baz/Cargo.toml", r#" [package] name = "baz" version = "0.1.0" [dependencies] bitflags = "=0.8.0" "#, ) .file("baz/src/lib.rs", "") .build(); Package::new("bitflags", "0.7.0").publish(); Package::new("bitflags", "0.8.0").publish(); p.cargo("vendor --respect-source-config --manifest-path foo/Cargo.toml -s bar/Cargo.toml baz/Cargo.toml test_vendor") .with_stderr_data(str![[r#" [ERROR] unexpected argument 'test_vendor' found Usage: cargo[EXE] vendor [OPTIONS] [path] For more information, try '--help'. "#]] ) .with_status(1) .run(); p.cargo("vendor --respect-source-config --manifest-path foo/Cargo.toml -s bar/Cargo.toml -s baz/Cargo.toml test_vendor") .run(); let lock = p.read_file("test_vendor/bitflags/Cargo.toml"); assert!(lock.contains("version = \"0.8.0\"")); let lock = p.read_file("test_vendor/bitflags-0.7.0/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); } #[cargo_test] fn delete_old_crates() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bitflags = "=0.7.0" "#, ) .file("src/lib.rs", "") .build(); Package::new("bitflags", "0.7.0").publish(); Package::new("log", "0.3.5").publish(); p.cargo("vendor --respect-source-config").run(); p.read_file("vendor/bitflags/Cargo.toml"); p.change_file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] log = "=0.3.5" "#, ); p.cargo("vendor --respect-source-config").run(); let lock = p.read_file("vendor/log/Cargo.toml"); assert!(lock.contains("version = \"0.3.5\"")); assert!(!p.root().join("vendor/bitflags/Cargo.toml").exists()); } #[cargo_test] fn ignore_files() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] url = "1.4.1" "#, ) .file("src/lib.rs", "") .build(); Package::new("url", "1.4.1") // These will be vendored .file(".cargo_vcs_info.json", "") .file("Cargo.toml.orig", "") .file("foo.orig", "") .file("foo.rej", "") .file("src/lib.rs", "") // These will not be vendored .file(".cargo-ok", "") .file(".gitattributes", "") .file(".gitignore", "") .publish(); p.cargo("vendor --respect-source-config").run(); let csum = p.read_file("vendor/url/.cargo-checksum.json"); assert_e2e().eq( csum, str![[r#" { "files": { ".cargo_vcs_info.json": "[..]", "Cargo.toml": "[..]", "Cargo.toml.orig": "[..]", "foo.orig": "[..]", "foo.rej": "[..]", "src/lib.rs": "[..]" }, "package": "[..]" } "#]] .is_json(), ); } #[cargo_test] fn included_files_only() { let git = git::new("a", |p| { p.file("Cargo.toml", &basic_lib_manifest("a")) .file("src/lib.rs", "") .file(".gitignore", "a") .file("a/b.md", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a = {{ git = '{}' }} "#, git.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config").run(); let csum = p.read_file("vendor/a/.cargo-checksum.json"); assert!(!csum.contains("a/b.md")); } #[cargo_test] fn dependent_crates_in_crates() { let git = git::new("a", |p| { p.file( "Cargo.toml", r#" [package] name = "a" version = "0.1.0" [dependencies] b = { path = 'b' } "#, ) .file("src/lib.rs", "") .file("b/Cargo.toml", &basic_lib_manifest("b")) .file("b/src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a = {{ git = '{}' }} "#, git.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config").run(); p.read_file("vendor/a/.cargo-checksum.json"); p.read_file("vendor/b/.cargo-checksum.json"); } #[cargo_test] fn vendoring_git_crates() { let git = git::new("git", |p| { p.file("Cargo.toml", &basic_lib_manifest("serde_derive")) .file("src/lib.rs", "") .file("src/wut.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies.serde] version = "0.5.0" [dependencies.serde_derive] version = "0.5.0" [patch.crates-io] serde_derive = {{ git = '{}' }} "#, git.url() ), ) .file("src/lib.rs", "") .build(); Package::new("serde", "0.5.0") .dep("serde_derive", "0.5") .publish(); Package::new("serde_derive", "0.5.0").publish(); p.cargo("vendor --respect-source-config").run(); p.read_file("vendor/serde_derive/src/wut.rs"); add_crates_io_vendor_config(&p); p.cargo("check").run(); } #[cargo_test] fn git_simple() { let git = git::new("git", |p| { p.file("Cargo.toml", &basic_lib_manifest("a")) .file("src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a = {{ git = '{}' }} "#, git.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config").run(); let csum = p.read_file("vendor/a/.cargo-checksum.json"); assert!(csum.contains("\"package\":null")); } #[cargo_test] fn git_diff_rev() { let (git_project, git_repo) = git::new_repo("git", |p| { p.file("Cargo.toml", &basic_manifest("a", "0.1.0")) .file("src/lib.rs", "") }); let url = git_project.url(); let ref_1 = "v0.1.0"; let ref_2 = "v0.2.0"; git::tag(&git_repo, ref_1); git_project.change_file("Cargo.toml", &basic_manifest("a", "0.2.0")); git::add(&git_repo); git::commit(&git_repo); git::tag(&git_repo, ref_2); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a_1 = {{ package = "a", git = '{url}', rev = '{ref_1}' }} a_2 = {{ package = "a", git = '{url}', rev = '{ref_2}' }} "# ), ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config") .with_stdout_data(str![[r#" [source."git+[ROOTURL]/git?rev=v0.1.0"] git = "[ROOTURL]/git" rev = "v0.1.0" replace-with = "vendored-sources" [source."git+[ROOTURL]/git?rev=v0.2.0"] git = "[ROOTURL]/git" rev = "v0.2.0" replace-with = "vendored-sources" [source.vendored-sources] directory = "vendor" "#]]) .run(); } #[cargo_test] fn git_duplicate() { let git = git::new("a", |p| { p.file( "Cargo.toml", r#" [package] name = "a" version = "0.1.0" [dependencies] b = { path = 'b' } "#, ) .file("src/lib.rs", "") .file("b/Cargo.toml", &basic_lib_manifest("b")) .file("b/src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a = {{ git = '{}' }} b = '0.5.0' "#, git.url() ), ) .file("src/lib.rs", "") .build(); Package::new("b", "0.5.0").publish(); p.cargo("vendor --respect-source-config") .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/a` [UPDATING] `dummy-registry` index [LOCKING] 3 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] b v0.5.0 (registry `dummy-registry`) [ERROR] failed to sync Caused by: found duplicate version of package `b v0.5.0` vendored from two sources: source 1: registry `crates-io` source 2: [ROOTURL]/a#[..] "#]]) .with_status(101) .run(); } #[cargo_test] fn git_complex() { let git_b = git::new("git_b", |p| { p.file( "Cargo.toml", r#" [package] name = "b" version = "0.1.0" edition = "2021" [dependencies] dep_b = { path = 'dep_b' } "#, ) .file("src/lib.rs", "") .file("dep_b/Cargo.toml", &basic_lib_manifest("dep_b")) .file("dep_b/src/lib.rs", "") }); let git_a = git::new("git_a", |p| { p.file( "Cargo.toml", &format!( r#" [package] name = "a" version = "0.1.0" edition = "2021" [dependencies] b = {{ git = '{}' }} dep_a = {{ path = 'dep_a' }} "#, git_b.url() ), ) .file("src/lib.rs", "") .file("dep_a/Cargo.toml", &basic_lib_manifest("dep_a")) .file("dep_a/src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" edition = "2021" [dependencies] a = {{ git = '{}' }} "#, git_a.url() ), ) .file("src/lib.rs", "") .build(); let output = p.cargo("vendor --respect-source-config").run(); let output = String::from_utf8(output.stdout).unwrap(); p.change_file(".cargo/config.toml", &output); p.cargo("check -v") .with_stderr_data( str![[r#" [CHECKING] dep_b v0.5.0 ([ROOTURL]/git_b#[..]) [CHECKING] dep_a v0.5.0 ([ROOTURL]/git_a#[..]) [RUNNING] `rustc [..] [ROOT]/foo/vendor/dep_b/src/lib.rs [..]` [RUNNING] `rustc [..] [ROOT]/foo/vendor/dep_a/src/lib.rs [..]` [CHECKING] b v0.1.0 ([ROOTURL]/git_b#[..]) [RUNNING] `rustc [..] [ROOT]/foo/vendor/b/src/lib.rs [..]` [CHECKING] a v0.1.0 ([ROOTURL]/git_a#[..]) [RUNNING] `rustc [..] [ROOT]/foo/vendor/a/src/lib.rs [..]` [CHECKING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] src/lib.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), ) .run(); } #[cargo_test] fn git_deterministic() { let git_dep = git::new("git_dep", |p| { p.file( "Cargo.toml", r#" [package] name = "git_dep" version = "0.0.1" edition = "2021" license = "MIT" description = "foo" documentation = "docs.rs/foo" authors = [] [[example]] name = "c" [[example]] name = "b" [[example]] name = "a" "#, ) .file("src/lib.rs", "") .file("examples/z.rs", "fn main() {}") .file("examples/y.rs", "fn main() {}") .file("examples/x.rs", "fn main() {}") .file("examples/c.rs", "fn main() {}") .file("examples/b.rs", "fn main() {}") .file("examples/a.rs", "fn main() {}") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] git_dep = {{ git = '{}' }} "#, git_dep.url() ), ) .file("src/lib.rs", "") .build(); let output = p.cargo("vendor --respect-source-config").run(); let output = String::from_utf8(output.stdout).unwrap(); p.change_file(".cargo/config.toml", &output); let git_dep_manifest = p.read_file("vendor/git_dep/Cargo.toml"); assert_e2e().eq( git_dep_manifest, str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "git_dep" version = "0.0.1" authors = [] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" documentation = "docs.rs/foo" readme = false license = "MIT" [lib] name = "git_dep" path = "src/lib.rs" [[example]] name = "a" path = "examples/a.rs" [[example]] name = "b" path = "examples/b.rs" [[example]] name = "c" path = "examples/c.rs" [[example]] name = "x" path = "examples/x.rs" [[example]] name = "y" path = "examples/y.rs" [[example]] name = "z" path = "examples/z.rs" "##]], ); } #[cargo_test] fn git_update_rev() { let (git_project, git_repo) = git::new_repo("git", |p| { p.file("Cargo.toml", &basic_manifest("a", "0.1.0")) .file("src/lib.rs", "") }); let url = git_project.url(); let ref_1 = "initial"; let ref_2 = "update"; git::tag(&git_repo, ref_1); git_project.change_file("src/lib.rs", "pub fn f() {}"); git::add(&git_repo); git::commit(&git_repo); git::tag(&git_repo, ref_2); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a = {{ git = '{url}', rev = '{ref_1}' }} "# ), ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config --versioned-dirs") .run(); let lib = p.read_file("vendor/a-0.1.0/src/lib.rs"); assert_e2e().eq(lib, ""); p.change_file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a = {{ git = '{url}', rev = '{ref_2}' }} "# ), ); p.cargo("vendor --respect-source-config --versioned-dirs") .run(); let lib = p.read_file("vendor/a-0.1.0/src/lib.rs"); assert_e2e().eq(lib, "pub fn f() {}"); } #[cargo_test] fn depend_on_vendor_dir_not_deleted() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] libc = "0.2.30" "#, ) .file("src/lib.rs", "") .build(); Package::new("libc", "0.2.30").publish(); p.cargo("vendor --respect-source-config").run(); assert!(p.root().join("vendor/libc").is_dir()); p.change_file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] libc = "0.2.30" [patch.crates-io] libc = { path = 'vendor/libc' } "#, ); p.cargo("vendor --respect-source-config").run(); assert!(p.root().join("vendor/libc").is_dir()); } #[cargo_test] fn ignore_hidden() { // Don't delete files starting with `.` Package::new("bar", "0.1.0").publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "1.0.0" [dependencies] bar = "0.1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config").run(); // Add a `.git` directory. let repo = git::init(&p.root().join("vendor")); git::add(&repo); git::commit(&repo); assert!(p.root().join("vendor/.git").exists()); // Vendor again, shouldn't change anything. p.cargo("vendor --respect-source-config").run(); // .git should not be removed. assert!(p.root().join("vendor/.git").exists()); // And just for good measure, make sure no files changed. let mut opts = git2::StatusOptions::new(); assert!( repo.statuses(Some(&mut opts)) .unwrap() .iter() .all(|status| status.status() == git2::Status::CURRENT) ); } #[cargo_test] fn config_instructions_works() { // Check that the config instructions work for all dependency kinds. registry::alt_init(); Package::new("dep", "0.1.0").publish(); Package::new("altdep", "0.1.0").alternative(true).publish(); let git_project = git::new("gitdep", |project| { project .file("Cargo.toml", &basic_lib_manifest("gitdep")) .file("src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" edition = "2021" [dependencies] dep = "0.1" altdep = {{version="0.1", registry="alternative"}} gitdep = {{git='{}'}} "#, git_project.url() ), ) .file("src/lib.rs", "") .build(); let output = p.cargo("vendor --respect-source-config").run(); let output = String::from_utf8(output.stdout).unwrap(); p.change_file(".cargo/config.toml", &output); p.cargo("check -v") .with_stderr_data( str![[r#" [CHECKING] altdep v0.1.0 (registry `alternative`) [CHECKING] dep v0.1.0 [RUNNING] `rustc [..] [ROOT]/foo/vendor/altdep/src/lib.rs [..]` [RUNNING] `rustc [..] [ROOT]/foo/vendor/gitdep/src/lib.rs [..]` [RUNNING] `rustc [..] [ROOT]/foo/vendor/dep/src/lib.rs [..]` [CHECKING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] src/lib.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [CHECKING] gitdep v0.5.0 ([ROOTURL]/gitdep#[..]) "#]] .unordered(), ) .run(); } #[cargo_test] fn git_crlf_preservation() { // Check that newlines don't get changed when you vendor // (will only fail if your system is setup with core.autocrlf=true on windows) let input = "hello \nthere\nmy newline\nfriends"; let git_project = git::new("git", |p| { p.file("Cargo.toml", &basic_lib_manifest("a")) .file("src/lib.rs", input) }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" [dependencies] a = {{ git = '{}' }} "#, git_project.url() ), ) .file("src/lib.rs", "") .build(); fs::write( paths::home().join(".gitconfig"), r#" [core] autocrlf = true "#, ) .unwrap(); p.cargo("vendor --respect-source-config").run(); let output = p.read_file("vendor/a/src/lib.rs"); assert_eq!(input, output); } #[cargo_test] #[cfg(unix)] fn vendor_preserves_permissions() { use std::os::unix::fs::MetadataExt; Package::new("bar", "1.0.0") .file_with_mode("example.sh", 0o755, "#!/bin/sh") .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config").run(); let umask = cargo::util::get_umask(); let metadata = fs::metadata(p.root().join("vendor/bar/src/lib.rs")).unwrap(); assert_eq!(metadata.mode() & 0o777, 0o644 & !umask); let metadata = fs::metadata(p.root().join("vendor/bar/example.sh")).unwrap(); assert_eq!(metadata.mode() & 0o777, 0o755 & !umask); } #[cargo_test] fn no_remote_dependency_no_vendor() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = { path = "bar" } "#, ) .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" "#, ) .file("bar/src/lib.rs", "") .build(); p.cargo("vendor") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version There is no dependency to vendor in this project. "#]]) .run(); assert!(!p.root().join("vendor").exists()); } #[cargo_test] fn vendor_crate_with_ws_inherit() { let git = git::new("ws", |p| { p.file( "Cargo.toml", r#" [workspace] members = ["bar"] [workspace.package] version = "0.1.0" "#, ) .file( "bar/Cargo.toml", r#" [package] name = "bar" version.workspace = true edition = "2021" "#, ) .file("bar/src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" edition = "2021" [dependencies] bar = {{ git = '{}' }} "#, git.url() ), ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config").run(); p.change_file( ".cargo/config.toml", &format!( r#" [source."{}"] git = "{}" replace-with = "vendor" [source.vendor] directory = "vendor" "#, git.url(), git.url() ), ); p.cargo("check -v") .with_stderr_data(str![[r#" [CHECKING] bar v0.1.0 ([ROOTURL]/ws#[..]) [RUNNING] `rustc [..] [ROOT]/foo/vendor/bar/src/lib.rs [..]` [CHECKING] foo v0.1.0 ([ROOT]/foo) [RUNNING] `rustc [..] src/lib.rs [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn dont_delete_non_registry_sources_with_respect_source_config() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] log = "0.3.5" "#, ) .file("src/lib.rs", "") .build(); Package::new("log", "0.3.5").publish(); p.cargo("vendor --respect-source-config").run(); let lock = p.read_file("vendor/log/Cargo.toml"); assert!(lock.contains("version = \"0.3.5\"")); add_crates_io_vendor_config(&p); p.cargo("vendor --respect-source-config new-vendor-dir") .with_stderr_data(str![[r#" Vendoring log v0.3.5 ([ROOT]/foo/vendor/log) to new-vendor-dir/log To use vendored sources, add this to your .cargo/config.toml for this project: "#]]) .with_stdout_data(str![[r#" [source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "new-vendor-dir" "#]]) .run(); } #[cargo_test] fn error_loading_which_lock() { // Tests an error message to make sure it is clear which // manifest/workspace caused the problem. In this particular case, it was // because the 2024 edition wants to know which rust version is in use. let p = project() .file( "Cargo.toml", r#" [package] name = "a" version = "0.1.0" "#, ) .file("src/lib.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.1.0" edition = "2024" "#, ) .file("b/src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config -s b/Cargo.toml") .env("RUSTC", "does-not-exist") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to sync Caused by: failed to load lockfile for [ROOT]/foo/b Caused by: could not execute process `does-not-exist -vV` (never executed) Caused by: [NOT_FOUND] "#]]) .run(); } #[cargo_test] fn error_downloading() { // Tests the error message when downloading packages. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = "1.0" "#, ) .file("src/lib.rs", "") .build(); Package::new("bar", "1.0.0").publish(); p.cargo("generate-lockfile").run(); std::fs::remove_file(cargo_test_support::paths::root().join("dl/bar/1.0.0/download")).unwrap(); p.cargo("vendor --respect-source-config") .with_status(101) .with_stderr_data(str![[r#" [DOWNLOADING] crates ... [ERROR] failed to sync Caused by: failed to download packages for [ROOT]/foo Caused by: failed to download from `[ROOTURL]/dl/bar/1.0.0/download` Caused by: [37] Could[..]t read a file:// file (Could[..]t open file [ROOT]/dl/bar/1.0.0/download) "#]]) .run(); } #[cargo_test] fn vendor_rename_fallback() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] log = "0.3.5" "#, ) .file("src/lib.rs", "") .build(); Package::new("log", "0.3.5").publish(); p.cargo("vendor --respect-source-config --no-delete") .env("CARGO_LOG", "cargo::ops::vendor=warn") .env("__CARGO_TEST_VENDOR_FALLBACK_CP_SOURCES", "true") .with_status(0) .with_stderr_data(str![[r#" ... [..]failed to `mv "[..]vendor[..].vendor-staging[..]log-0.3.5" "[..]vendor[..]log"`: simulated rename error for testing ... "#]]) .run(); assert!(p.root().join("vendor/log/Cargo.toml").exists()); } #[cargo_test] fn vendor_local_registry() { // A regression test for rust-lang/cargo#16412 let root = paths::root(); fs::create_dir(root.join(".cargo")).unwrap(); fs::write( root.join(".cargo/config.toml"), r#" [source.crates-io] registry = 'https://wut' replace-with = 'my-awesome-local-registry' [source.my-awesome-local-registry] local-registry = 'registry' "#, ) .unwrap(); Package::new("bar", "0.0.0") .local(true) .file("src/lib.rs", "pub fn bar() {}") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" edition = "2021" [dependencies] bar = "0.0.0" "#, ) .file("src/lib.rs", "pub fn foo() { bar::bar(); }") .build(); p.cargo("vendor --respect-source-config") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version [UNPACKING] bar v0.0.0 (registry `[ROOT]/registry`) Vendoring bar v0.0.0 ([ROOT]/home/.cargo/registry/src/-[HASH]/bar-0.0.0) to vendor/bar To use vendored sources, add this to your .cargo/config.toml for this project: "#]]) .run(); assert_e2e().eq( p.read_file("vendor/bar/Cargo.toml"), str![[r#" [package] name = "bar" version = "0.0.0" authors = [] "#]], ); assert_e2e().eq( p.read_file("vendor/bar/src/lib.rs"), str!["pub fn bar() {}"], ); } #[cargo_test] fn deterministic_mtime() { Package::new("foo", "0.1.0") // content doesn't matter, we just want to check mtime .file("Cargo.lock", "") .file(".cargo_vcs_info.json", "") .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "a" edition = "2015" [dependencies] foo = '0.1.0' "#, ) .file("src/lib.rs", "") .build(); p.cargo("vendor --respect-source-config").run(); // Generated files should have deterministic mtime after unpacking. assert_deterministic_mtime(p.root().join("vendor/foo/Cargo.lock")); assert_deterministic_mtime(p.root().join("vendor/foo/Cargo.toml")); assert_deterministic_mtime(p.root().join("vendor/foo/.cargo_vcs_info.json")); } #[cargo_test] fn vendor_filters_git_files_recursively() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = "0.1.0" "#, ) .file("src/lib.rs", "") .build(); Package::new("bar", "0.1.0") .file("src/lib.rs", "") .file(".gitattributes", "*.rs text") .file("subdir/.gitattributes", "*.c text") .file("subdir/.gitignore", "target/") .file("deep/nested/.git/config", "") .file("tests/.gitattributes", "*.txt text") .publish(); p.cargo("vendor --respect-source-config").run(); // After fix, these should be filtered assert!(!p.root().join("vendor/bar/subdir/.gitattributes").exists()); assert!(!p.root().join("vendor/bar/subdir/.gitignore").exists()); assert!(!p.root().join("vendor/bar/deep/nested/.git").exists()); assert!(!p.root().join("vendor/bar/tests/.gitattributes").exists()); assert!(!p.root().join("vendor/bar/.gitattributes").exists()); assert!(p.root().join("vendor/bar/src/lib.rs").exists()); } ================================================ FILE: tests/testsuite/verify_project.rs ================================================ //! Tests for the `cargo verify-project` command. use crate::prelude::*; use cargo_test_support::{basic_bin_manifest, main_file, project, str}; #[cargo_test] fn cargo_verify_project_path_to_cargo_toml_relative() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("verify-project --manifest-path foo/Cargo.toml") .cwd(p.root().parent().unwrap()) .with_stdout_data(str![[r#" {"success":"true"} "#]]) .run(); } #[cargo_test] fn cargo_verify_project_path_to_cargo_toml_absolute() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("verify-project --manifest-path") .arg(p.root().join("Cargo.toml")) .cwd(p.root().parent().unwrap()) .with_stdout_data(str![[r#" {"success":"true"} "#]]) .run(); } #[cargo_test] fn cargo_verify_project_cwd() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) .build(); p.cargo("verify-project") .with_stdout_data(str![[r#" {"success":"true"} "#]]) .run(); } #[cargo_test] fn cargo_verify_project_honours_unstable_features() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["test-dummy-unstable"] [package] name = "foo" version = "0.0.1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("verify-project") .masquerade_as_nightly_cargo(&["test-dummy-unstable"]) .with_stdout_data(str![[r#" {"success":"true"} "#]]) .run(); p.cargo("verify-project") .with_status(1) .with_stdout_data(str![[r#" {"invalid":"failed to parse manifest at `[..]`"} "#]]) .run(); } #[cargo_test] fn verify_project_invalid_toml_syntax() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "#, ) .file("src/lib.rs", "") .build(); p.cargo("verify-project") .with_status(1) .with_stdout_data(str![[r#" {"invalid":"TOML parse error at line 4, column 11/n |/n4 | version = /n | ^/nstring values must be quoted, expected literal string/n"} "#]]) .run(); } ================================================ FILE: tests/testsuite/version.rs ================================================ //! Tests for displaying the cargo version. use crate::prelude::*; use crate::utils::cargo_process; use cargo_test_support::project; #[cargo_test] fn simple() { let p = project().build(); p.cargo("version") .with_stdout_data(&format!("cargo {}\n", cargo::version())) .run(); p.cargo("--version") .with_stdout_data(&format!("cargo {}\n", cargo::version())) .run(); p.cargo("-V") .with_stdout_data(&format!("cargo {}\n", cargo::version())) .run(); } #[cargo_test] fn version_works_without_rustc() { let p = project().build(); p.cargo("version").env("PATH", "").run(); } #[cargo_test] fn version_works_with_bad_config() { let p = project() .file(".cargo/config.toml", "this is not toml") .build(); p.cargo("version").run(); } #[cargo_test] fn version_works_with_bad_target_dir() { let p = project() .file( ".cargo/config.toml", r#" [build] target-dir = 4 "#, ) .build(); p.cargo("version").run(); } #[cargo_test] fn verbose() { // This is mainly to check that it doesn't explode. cargo_process("-vV") .with_stdout_data(format!( "\ cargo {} release: [..] commit-hash: [..] commit-date: [..] host: [HOST_TARGET] libgit2: [..] (sys:[..] [..]) libcurl: [..] (sys:[..] [..]) ... os: [..] ", cargo::version() )) .run(); } ================================================ FILE: tests/testsuite/warn_on_failure.rs ================================================ //! Tests for whether or not warnings are displayed for build scripts. use crate::prelude::*; use cargo_test_support::registry::Package; use cargo_test_support::{Project, project, str}; static WARNING1: &str = "Hello! I'm a warning. :)"; static WARNING2: &str = "And one more!"; fn make_lib(lib_src: &str) { Package::new("bar", "0.0.1") .file( "Cargo.toml", r#" [package] name = "bar" authors = [] version = "0.0.1" edition = "2015" build = "build.rs" "#, ) .file( "build.rs", &format!( r#" fn main() {{ use std::io::Write; println!("cargo::warning={{}}", "{}"); println!("hidden stdout"); write!(&mut ::std::io::stderr(), "hidden stderr"); println!("cargo::warning={{}}", "{}"); }} "#, WARNING1, WARNING2 ), ) .file("src/lib.rs", &format!("fn f() {{ {} }}", lib_src)) .publish(); } fn make_upstream(main_src: &str) -> Project { project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("src/main.rs", &format!("fn main() {{ {} }}", main_src)) .build() } #[cargo_test] fn no_warning_on_success() { make_lib(""); let upstream = make_upstream(""); upstream .cargo("build") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [COMPILING] bar v0.0.1 [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn no_warning_on_bin_failure() { make_lib(""); let upstream = make_upstream("hi()"); upstream .cargo("build") .with_status(101) .with_stdout_does_not_contain("hidden stdout") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [COMPILING] bar v0.0.1 [COMPILING] foo v0.0.1 ([ROOT]/foo) error[E0425]: cannot find function `hi` in this scope ... [ERROR] could not compile `foo` (bin "foo") due to 1 previous error "#]]) .run(); } #[cargo_test] fn warning_on_lib_failure() { make_lib("err()"); let upstream = make_upstream(""); upstream .cargo("build") .with_status(101) .with_stdout_does_not_contain("hidden stdout") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) [COMPILING] bar v0.0.1 error[E0425]: cannot find function `err` in this scope ... [WARNING] bar@0.0.1: Hello! I'm a warning. :) [WARNING] bar@0.0.1: And one more! [ERROR] could not compile `bar` (lib) due to 1 previous error "#]]) .run(); } ================================================ FILE: tests/testsuite/warning_override.rs ================================================ //! Tests for overriding warning behavior using `build.warnings` config option. use crate::prelude::*; use crate::utils::tools; use cargo_test_support::registry::Package; use cargo_test_support::{Project, cargo_test, project, str}; fn make_project_with_rustc_warning() -> Project { project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2021" "# ), ) .file("src/main.rs", "fn main() { let x = 3; }") .build() } #[cargo_test] fn rustc_caching_allow_first() { let p = make_project_with_rustc_warning(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='allow'") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='deny'") .with_stderr_data(str![[r#" [WARNING] unused variable: `x` --> src/main.rs:1:17 | 1 | fn main() { let x = 3; } | ^ [HELP] if this is intentional, prefix it with an underscore: `_x` | = [NOTE] `#[warn(unused_variables)]` [..]on by default [ERROR] `foo` (bin "foo") generated 1 warning[..] [ERROR] warnings are denied by `build.warnings` configuration "#]]) .with_status(101) .run(); } #[cargo_test] fn rustc_caching_deny_first() { let p = make_project_with_rustc_warning(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='deny'") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [WARNING] unused variable: `x` --> src/main.rs:1:17 | 1 | fn main() { let x = 3; } | ^ [HELP] if this is intentional, prefix it with an underscore: `_x` | = [NOTE] `#[warn(unused_variables)]` [..]on by default [ERROR] `foo` (bin "foo") generated 1 warning[..] [ERROR] warnings are denied by `build.warnings` configuration "#]]) .with_status(101) .run(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='allow'") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn config() { let p = make_project_with_rustc_warning(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .env("CARGO_BUILD_WARNINGS", "deny") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [WARNING] unused variable: `x` --> src/main.rs:1:17 | 1 | fn main() { let x = 3; } | ^ [HELP] if this is intentional, prefix it with an underscore: `_x` | = [NOTE] `#[warn(unused_variables)]` [..]on by default [ERROR] `foo` (bin "foo") generated 1 warning[..] [ERROR] warnings are denied by `build.warnings` configuration "#]]) .with_status(101) .run(); // CLI has precedence over env p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='warn'") .env("CARGO_BUILD_WARNINGS", "deny") .with_stderr_data(str![[r#" [WARNING] unused variable: `x` --> src/main.rs:1:17 | 1 | fn main() { let x = 3; } | ^ [HELP] if this is intentional, prefix it with an underscore: `_x` | = [NOTE] `#[warn(unused_variables)]` [..]on by default [WARNING] `foo` (bin "foo") generated 1 warning[..] [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn requires_nightly() { // build.warnings has no effect without -Zwarnings. let p = make_project_with_rustc_warning(); p.cargo("check") .arg("--config") .arg("build.warnings='deny'") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [WARNING] unused variable: `x` --> src/main.rs:1:17 | 1 | fn main() { let x = 3; } | ^ [HELP] if this is intentional, prefix it with an underscore: `_x` | = [NOTE] `#[warn(unused_variables)]` [..]on by default [WARNING] `foo` (bin "foo") generated 1 warning[..] [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn clippy() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" "#, ) .file("src/lib.rs", "use std::io;") // <-- unused import .build(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='deny'") .env("RUSTC_WORKSPACE_WRAPPER", tools::wrapped_clippy_driver()) .with_stderr_data(str![[r#" [CHECKING] foo v0.0.1 ([ROOT]/foo) [WARNING] unused import: `std::io` ... [ERROR] `foo` (lib) generated 1 warning (run `cargo clippy --fix --lib -p foo` to apply 1 suggestion) [ERROR] warnings are denied by `build.warnings` configuration "#]]) .with_status(101) .run(); } #[cargo_test] fn unknown_value() { let p = make_project_with_rustc_warning(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='forbid'") .with_stderr_data(str![[r#" [ERROR] error in --config cli option: could not load config key `build.warnings` Caused by: unknown variant `forbid`, expected one of `warn`, `allow`, `deny` "#]]) .with_status(101) .run(); } #[cargo_test] fn hard_warning_deny() { let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2021" "# ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("rustc") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='deny'") .arg("--") .arg("-ox.rs") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [WARNING] [..] [WARNING] [..] [WARNING] `foo` (bin "foo") generated 2 warnings [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn hard_warning_allow() { let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2021" "# ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("rustc") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='allow'") .arg("--") .arg("-ox.rs") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_status(0) .run(); } #[cargo_test] fn keep_going() { let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2021" "# ), ) .file("build.rs", "fn main() { let x = 3; }") .file("src/main.rs", "fn main() { let y = 4; }") .build(); p.cargo("check") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='deny'") .with_stderr_data(str![[r#" [COMPILING] foo v0.0.1 ([ROOT]/foo) [WARNING] unused variable: `x` ... [ERROR] `foo` (build script) generated 1 warning [ERROR] warnings are denied by `build.warnings` configuration "#]]) .with_status(101) .run(); p.cargo("check --keep-going") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='deny'") .with_stderr_data(str![[r#" [WARNING] unused variable: `x` ... [ERROR] `foo` (build script) generated 1 warning [COMPILING] foo v0.0.1 ([ROOT]/foo) ... [ERROR] `foo` (bin "foo") generated 1 warning (run `cargo fix --bin "foo" -p foo` to apply 1 suggestion) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [ERROR] warnings are denied by `build.warnings` configuration "#]]) .with_status(101) .run(); } #[cargo_test] fn cap_lints() { Package::new("has_warning", "1.0.0") .file("src/lib.rs", "pub fn foo() { let x = 3; }") .publish(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.0.1" edition = "2021" [dependencies] has_warning = "1" "# ), ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -vv") .env("RUSTFLAGS", "-Dwarnings") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] has_warning v1.0.0 (registry `dummy-registry`) [CHECKING] has_warning v1.0.0 [RUNNING] [..] [WARNING] unused variable: `x` ... [WARNING] `has_warning` (lib) generated 1 warning [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] [..] [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["warnings"]) .arg("-Zwarnings") .arg("--config") .arg("build.warnings='deny'") .with_stderr_data(str![[r#" [CHECKING] has_warning v1.0.0 [RUNNING] [..] [WARNING] unused variable: `x` ... [WARNING] `has_warning` (lib) generated 1 warning [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] [..] [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } ================================================ FILE: tests/testsuite/weak_dep_features.rs ================================================ //! Tests for weak-dep-features. use std::fmt::Write; use crate::prelude::*; use cargo_test_support::registry::{Dependency, Package, RegistryBuilder}; use cargo_test_support::str; use cargo_test_support::{project, publish}; use super::features2::switch_to_resolver_2; // Helper to create lib.rs files that check features. fn require(enabled_features: &[&str], disabled_features: &[&str]) -> String { let mut s = String::new(); writeln!(s, "#![allow(unexpected_cfgs)]").unwrap(); for feature in enabled_features { writeln!(s, "#[cfg(not(feature=\"{feature}\"))] compile_error!(\"expected feature {feature} to be enabled\");", feature=feature).unwrap(); } for feature in disabled_features { writeln!(s, "#[cfg(feature=\"{feature}\")] compile_error!(\"did not expect feature {feature} to be enabled\");", feature=feature).unwrap(); } s } #[cargo_test] fn simple() { Package::new("bar", "1.0.0") .feature("feat", &[]) .file("src/lib.rs", &require(&["feat"], &[])) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = { version = "1.0", optional = true } [features] f1 = ["bar?/feat"] "#, ) .file("src/lib.rs", &require(&["f1"], &[])) .build(); // It's a bit unfortunate that this has to download `bar`, but avoiding // that is extremely difficult. p.cargo("check --features f1") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check --features f1,bar") .with_stderr_data(str![[r#" [CHECKING] bar v1.0.0 [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn deferred() { // A complex chain that requires deferring enabling the feature due to // another dependency getting enabled. Package::new("bar", "1.0.0") .feature("feat", &[]) .file("src/lib.rs", &require(&["feat"], &[])) .publish(); Package::new("dep", "1.0.0") .add_dep(Dependency::new("bar", "1.0").optional(true)) .feature("feat", &["bar?/feat"]) .publish(); Package::new("bar_activator", "1.0.0") .feature_dep("dep", "1.0", &["bar"]) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] dep = { version = "1.0", features = ["feat"] } bar_activator = "1.0" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 3 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] dep v1.0.0 (registry `dummy-registry`) [DOWNLOADED] bar_activator v1.0.0 (registry `dummy-registry`) [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [CHECKING] bar v1.0.0 [CHECKING] dep v1.0.0 [CHECKING] bar_activator v1.0.0 [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn not_optional_dep() { // Attempt to use dep_name?/feat where dep_name is not optional. Package::new("dep", "1.0.0").feature("feat", &[]).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] dep = "1.0" [features] feat = ["dep?/feat"] "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: feature `feat` includes `dep?/feat` with a `?`, but `dep` is not an optional dependency A non-optional dependency of the same name is defined; consider removing the `?` or changing the dependency to be optional "#]]) .run(); } #[cargo_test] fn optional_cli_syntax() { // --features bar?/feat Package::new("bar", "1.0.0") .feature("feat", &[]) .file("src/lib.rs", &require(&["feat"], &[])) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = { version = "1.0", optional = true } "#, ) .file("src/lib.rs", "") .build(); // Does not build bar. p.cargo("check --features bar?/feat") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); // Builds bar. p.cargo("check --features bar?/feat,bar") .with_stderr_data(str![[r#" [CHECKING] bar v1.0.0 [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); eprintln!("check V2 resolver"); switch_to_resolver_2(&p); p.build_dir().rm_rf(); // Does not build bar. p.cargo("check --features bar?/feat") .with_stderr_data(str![[r#" [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); // Builds bar. p.cargo("check --features bar?/feat,bar") .with_stderr_data(str![[r#" [CHECKING] bar v1.0.0 [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn required_features() { // required-features doesn't allow ? Package::new("bar", "1.0.0").feature("feat", &[]).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = { version = "1.0", optional = true } [[bin]] name = "foo" required-features = ["bar?/feat"] "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [ERROR] invalid feature `bar?/feat` in required-features of target `foo`: optional dependency with `?` is not allowed in required-features "#]]) .run(); } #[cargo_test] fn weak_with_host_decouple() { // weak-dep-features with new resolver // // foo v0.1.0 // └── common v1.0.0 // └── bar v1.0.0 <-- does not have `feat` enabled // [build-dependencies] // └── bar_activator v1.0.0 // └── common v1.0.0 // └── bar v1.0.0 <-- does have `feat` enabled Package::new("bar", "1.0.0") .feature("feat", &[]) .file( "src/lib.rs", r#" pub fn feat() -> bool { cfg!(feature = "feat") } "#, ) .publish(); Package::new("common", "1.0.0") .add_dep(Dependency::new("bar", "1.0").optional(true)) .feature("feat", &["bar?/feat"]) .file( "src/lib.rs", r#" #[cfg(feature = "bar")] pub fn feat() -> bool { bar::feat() } #[cfg(not(feature = "bar"))] pub fn feat() -> bool { false } "#, ) .publish(); Package::new("bar_activator", "1.0.0") .feature_dep("common", "1.0", &["bar", "feat"]) .file( "src/lib.rs", r#" pub fn feat() -> bool { common::feat() } "#, ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" resolver = "2" [dependencies] common = { version = "1.0", features = ["feat"] } [build-dependencies] bar_activator = "1.0" "#, ) .file( "src/main.rs", r#" fn main() { assert!(!common::feat()); } "#, ) .file( "build.rs", r#" fn main() { assert!(bar_activator::feat()); } "#, ) .build(); p.cargo("run") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 3 packages to latest compatible versions [DOWNLOADING] crates ... [DOWNLOADED] common v1.0.0 (registry `dummy-registry`) [DOWNLOADED] bar_activator v1.0.0 (registry `dummy-registry`) [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [COMPILING] bar v1.0.0 [COMPILING] common v1.0.0 [COMPILING] bar_activator v1.0.0 [COMPILING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [RUNNING] `target/debug/foo[EXE]` "#]]) .run(); } #[cargo_test] fn weak_namespaced() { // Behavior with a dep: dependency. Package::new("bar", "1.0.0") .feature("feat", &[]) .file("src/lib.rs", &require(&["feat"], &[])) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = { version = "1.0", optional = true } [features] f1 = ["bar?/feat"] f2 = ["dep:bar"] "#, ) .file("src/lib.rs", &require(&["f1"], &["f2", "bar"])) .build(); p.cargo("check --features f1") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("tree -f") .arg("{p} feats:{f}") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) feats: "#]]) .run(); p.cargo("tree --features f1 -f") .arg("{p} feats:{f}") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) feats:f1 "#]]) .run(); p.cargo("tree --features f1,f2 -f") .arg("{p} feats:{f}") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) feats:f1,f2 └── bar v1.0.0 feats:feat "#]]) .run(); // "bar" remains not-a-feature p.change_file("src/lib.rs", &require(&["f1", "f2"], &["bar"])); p.cargo("check --features f1,f2") .with_stderr_data(str![[r#" [CHECKING] bar v1.0.0 [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn tree() { Package::new("bar", "1.0.0") .feature("feat", &[]) .file("src/lib.rs", &require(&["feat"], &[])) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] bar = { version = "1.0", optional = true } [features] f1 = ["bar?/feat"] "#, ) .file("src/lib.rs", &require(&["f1"], &[])) .build(); p.cargo("tree --features f1") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) "#]]) .run(); p.cargo("tree --features f1,bar") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar v1.0.0 "#]]) .run(); p.cargo("tree --features f1,bar -e features") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) └── bar feature "default" └── bar v1.0.0 "#]]) .run(); p.cargo("tree --features f1,bar -e features -i bar") .with_stdout_data(str![[r#" bar v1.0.0 ├── bar feature "default" │ └── foo v0.1.0 ([ROOT]/foo) │ ├── foo feature "bar" (command-line) │ ├── foo feature "default" (command-line) │ └── foo feature "f1" (command-line) └── bar feature "feat" └── foo feature "f1" (command-line) "#]]) .run(); p.cargo("tree -e features --features bar?/feat") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo) "#]]) .run(); // This is a little strange in that it produces no output. // Maybe `cargo tree` should print a note about why? p.cargo("tree -e features -i bar --features bar?/feat") .with_stdout_data("") .run(); p.cargo("tree -e features -i bar --features bar?/feat,bar") .with_stdout_data(str![[r#" bar v1.0.0 ├── bar feature "default" │ └── foo v0.1.0 ([ROOT]/foo) │ ├── foo feature "bar" (command-line) │ └── foo feature "default" (command-line) └── bar feature "feat" (command-line) "#]]) .run(); } #[cargo_test] fn publish() { let registry = RegistryBuilder::new().http_api().http_index().build(); // Publish behavior with /? syntax. Package::new("bar", "1.0.0").feature("feat", &[]).publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" description = "foo" license = "MIT" homepage = "https://example.com/" [dependencies] bar = { version = "1.0", optional = true } [features] feat1 = [] feat2 = ["bar?/feat"] "#, ) .file("src/lib.rs", "") .build(); p.cargo("publish") .replace_crates_io(registry.index_url()) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] foo v0.1.0 ([ROOT]/foo) [UPDATING] crates.io index [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.1.0 ([ROOT]/foo) [COMPILING] foo v0.1.0 ([ROOT]/foo/target/package/foo-0.1.0) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [UPLOADING] foo v0.1.0 ([ROOT]/foo) [UPLOADED] foo v0.1.0 to registry `crates-io` [NOTE] waiting for foo v0.1.0 to be available at registry `crates-io` [HELP] you may press ctrl-c to skip waiting; the crate should be available shortly [PUBLISHED] foo v0.1.0 at registry `crates-io` "#]]) .run(); publish::validate_upload_with_contents( r#" { "authors": [], "badges": {}, "categories": [], "deps": [ { "default_features": true, "features": [], "kind": "normal", "name": "bar", "optional": true, "target": null, "version_req": "^1.0" } ], "description": "foo", "documentation": null, "features": { "feat1": [], "feat2": ["bar?/feat"] }, "homepage": "https://example.com/", "keywords": [], "license": "MIT", "license_file": null, "links": null, "name": "foo", "readme": null, "readme_file": null, "repository": null, "rust_version": null, "vers": "0.1.0" } "#, "foo-0.1.0.crate", &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs", "Cargo.lock"], [( "Cargo.toml", str![[r##" # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2015" name = "foo" version = "0.1.0" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "foo" homepage = "https://example.com/" readme = false license = "MIT" [features] feat1 = [] feat2 = ["bar?/feat"] [lib] name = "foo" path = "src/lib.rs" [dependencies.bar] version = "1.0" optional = true "##]], )], ); } ================================================ FILE: tests/testsuite/workspaces.rs ================================================ //! Tests for workspaces. use std::env; use std::fs; use crate::prelude::*; use cargo_test_support::registry::Package; use cargo_test_support::str; use cargo_test_support::{ ProjectBuilder, basic_lib_manifest, basic_manifest, git, paths, project, project_in_home, sleep_ms, }; #[cargo_test] fn simple_explicit() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = ".." "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); assert!(!p.bin("bar").is_file()); p.cargo("build").cwd("bar").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("bar").is_file()); assert!(p.root().join("Cargo.lock").is_file()); assert!(!p.root().join("bar/Cargo.lock").is_file()); } #[cargo_test] fn simple_explicit_default_members() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] default-members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = ".." "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").run(); assert!(p.bin("bar").is_file()); assert!(!p.bin("foo").is_file()); } #[cargo_test] fn non_virtual_default_members_build_other_member() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = [".", "bar", "baz"] default-members = ["baz"] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "pub fn bar() {}") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", "pub fn baz() {}") .build(); p.cargo("check") .with_stderr_data(str![[r#" [CHECKING] baz v0.1.0 ([ROOT]/foo/baz) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check --manifest-path bar/Cargo.toml") .with_stderr_data(str![[r#" [CHECKING] bar v0.1.0 ([ROOT]/foo/bar) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn non_virtual_default_members_build_root_project() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] default-members = ["."] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "pub fn bar() {}") .build(); p.cargo("check") .with_stderr_data(str![[r#" [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn inferred_root() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); assert!(!p.bin("bar").is_file()); p.cargo("build").cwd("bar").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("bar").is_file()); assert!(p.root().join("Cargo.lock").is_file()); assert!(!p.root().join("bar/Cargo.lock").is_file()); } #[cargo_test] fn inferred_path_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "bar" } [workspace] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}") .file("bar/src/lib.rs", ""); let p = p.build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); assert!(!p.bin("bar").is_file()); p.cargo("build").cwd("bar").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("bar").is_file()); assert!(p.root().join("Cargo.lock").is_file()); assert!(!p.root().join("bar/Cargo.lock").is_file()); } #[cargo_test] fn transitive_path_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "bar" } [workspace] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] baz = { path = "../baz" } "#, ) .file("bar/src/main.rs", "fn main() {}") .file("bar/src/lib.rs", "") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/main.rs", "fn main() {}") .file("baz/src/lib.rs", ""); let p = p.build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); assert!(!p.bin("bar").is_file()); assert!(!p.bin("baz").is_file()); p.cargo("build").cwd("bar").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("bar").is_file()); assert!(!p.bin("baz").is_file()); p.cargo("build").cwd("baz").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("bar").is_file()); assert!(p.bin("baz").is_file()); assert!(p.root().join("Cargo.lock").is_file()); assert!(!p.root().join("bar/Cargo.lock").is_file()); assert!(!p.root().join("baz/Cargo.lock").is_file()); } #[cargo_test] fn parent_pointer_works() { let p = project() .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "../bar" } [workspace] "#, ) .file("foo/src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = "../foo" "#, ) .file("bar/src/main.rs", "fn main() {}") .file("bar/src/lib.rs", ""); let p = p.build(); p.cargo("build").cwd("foo").run(); p.cargo("build").cwd("bar").run(); assert!(p.root().join("foo/Cargo.lock").is_file()); assert!(!p.root().join("bar/Cargo.lock").is_file()); } #[cargo_test] fn same_names_in_workspace() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] workspace = ".." "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] two packages named `foo` in this workspace: - [ROOT]/foo/bar/Cargo.toml - [ROOT]/foo/Cargo.toml "#]]) .run(); } #[cargo_test] fn parent_doesnt_point_to_child() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .cwd("bar") .with_status(101) .with_stderr_data(str![[r#" [ERROR] current package believes it's in a workspace when it's not: current: [ROOT]/foo/bar/Cargo.toml workspace: [ROOT]/foo/Cargo.toml this may be fixable by ensuring that this crate is depended on by the workspace root: [ROOT]/foo/Cargo.toml Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest. "#]]) .run(); } #[cargo_test] fn invalid_parent_pointer() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] workspace = "foo" "#, ) .file("src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to read `[ROOT]/foo/foo/Cargo.toml` Caused by: [NOT_FOUND] "#]]) .run(); } #[cargo_test] fn invalid_members() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["foo"] "#, ) .file("src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to load manifest for workspace member `[ROOT]/foo/foo` referenced by workspace at `[ROOT]/foo/Cargo.toml` Caused by: failed to read `[ROOT]/foo/foo/Cargo.toml` Caused by: [NOT_FOUND] "#]]) .run(); } #[cargo_test] fn bare_workspace_ok() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] "#, ) .file("src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check").run(); } #[cargo_test] fn two_roots() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [workspace] members = [".."] "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] multiple workspace roots found in the same workspace: [ROOT]/foo/bar [ROOT]/foo "#]]) .run(); } #[cargo_test] fn workspace_isnt_root() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] workspace = "bar" "#, ) .file("src/main.rs", "fn main() {}") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] root of a workspace inferred but wasn't a root: [ROOT]/foo/bar/Cargo.toml "#]]) .run(); } #[cargo_test] fn dangling_member() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = "../baz" "#, ) .file("bar/src/main.rs", "fn main() {}") .file( "baz/Cargo.toml", r#" [package] name = "baz" version = "0.1.0" edition = "2015" authors = [] workspace = "../baz" "#, ) .file("baz/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package `[ROOT]/foo/bar/Cargo.toml` is a member of the wrong workspace expected: [ROOT]/foo/Cargo.toml actual: [ROOT]/foo/baz/Cargo.toml "#]]) .run(); } #[cargo_test] fn cycle() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] workspace = "bar" "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = ".." "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] root of a workspace inferred but wasn't a root: [ROOT]/foo/bar/Cargo.toml "#]]) .run(); } #[cargo_test] fn share_dependencies() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] dep1 = "0.1" [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] dep1 = "< 0.1.5" "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); Package::new("dep1", "0.1.3").publish(); Package::new("dep1", "0.1.8").publish(); p.cargo("check") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [ADDING] dep1 v0.1.3 (available: v0.1.8) [DOWNLOADING] crates ... [DOWNLOADED] dep1 v0.1.3 (registry `dummy-registry`) [CHECKING] dep1 v0.1.3 [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn fetch_fetches_all() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] dep1 = "*" "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); Package::new("dep1", "0.1.3").publish(); p.cargo("fetch") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] dep1 v0.1.3 (registry `dummy-registry`) "#]]) .run(); } #[cargo_test] fn lock_works_for_everyone() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] dep2 = "0.1" [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [dependencies] dep1 = "0.1" "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); Package::new("dep1", "0.1.0").publish(); Package::new("dep2", "0.1.0").publish(); p.cargo("generate-lockfile") .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 2 packages to latest compatible versions "#]]) .run(); Package::new("dep1", "0.1.1").publish(); Package::new("dep2", "0.1.1").publish(); p.cargo("check") .with_stderr_data(str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] dep2 v0.1.0 (registry `dummy-registry`) [CHECKING] dep2 v0.1.0 [CHECKING] foo v0.1.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check") .cwd("bar") .with_stderr_data(str![[r#" [DOWNLOADING] crates ... [DOWNLOADED] dep1 v0.1.0 (registry `dummy-registry`) [CHECKING] dep1 v0.1.0 [CHECKING] bar v0.1.0 ([ROOT]/foo/bar) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn virtual_works() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").cwd("bar").run(); assert!(p.root().join("Cargo.lock").is_file()); assert!(p.bin("bar").is_file()); assert!(!p.root().join("bar/Cargo.lock").is_file()); } #[cargo_test] fn explicit_package_argument_works_with_virtual_manifest() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build --package bar").run(); assert!(p.root().join("Cargo.lock").is_file()); assert!(p.bin("bar").is_file()); assert!(!p.root().join("bar/Cargo.lock").is_file()); } #[cargo_test] fn virtual_misconfigure() { let p = project() .file( "Cargo.toml", r#" [workspace] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .cwd("bar") .with_status(101) .with_stderr_data(str![[r#" [ERROR] current package believes it's in a workspace when it's not: current: [ROOT]/foo/bar/Cargo.toml workspace: [ROOT]/foo/Cargo.toml this may be fixable by adding `bar` to the `workspace.members` array of the manifest located at: [ROOT]/foo/Cargo.toml Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest. "#]]) .run(); } #[cargo_test] fn virtual_build_all_implied() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check").run(); } #[cargo_test] fn virtual_default_members() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar", "baz"] default-members = ["bar"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("bar/src/main.rs", "fn main() {}") .file("baz/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").run(); assert!(p.bin("bar").is_file()); assert!(!p.bin("baz").is_file()); } #[cargo_test] fn virtual_default_member_is_not_a_member() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar"] default-members = ["something-else"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package `[ROOT]/foo/something-else` is listed in default-members but is not a member for workspace at `[ROOT]/foo/Cargo.toml`. "#]]) .run(); } #[cargo_test] fn virtual_default_members_build_other_member() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["bar", "baz"] default-members = ["baz"] "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "pub fn bar() {}") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", "pub fn baz() {}") .build(); p.cargo("check --manifest-path bar/Cargo.toml") .with_stderr_data(str![[r#" [CHECKING] bar v0.1.0 ([ROOT]/foo/bar) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn virtual_build_no_members() { let p = project().file( "Cargo.toml", r#" [workspace] "#, ); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] manifest path `[ROOT]/foo` contains no package: The manifest is virtual, and the workspace has no members. "#]]) .run(); } #[cargo_test] fn include_virtual() { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/main.rs", "") .file( "bar/Cargo.toml", r#" [workspace] "#, ); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] multiple workspace roots found in the same workspace: [ROOT]/foo/bar [ROOT]/foo "#]]) .run(); } #[cargo_test] fn members_include_path_deps() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["p1"] [dependencies] p3 = { path = "p3" } "#, ) .file("src/lib.rs", "") .file( "p1/Cargo.toml", r#" [package] name = "p1" version = "0.1.0" edition = "2015" authors = [] [dependencies] p2 = { path = "../p2" } "#, ) .file("p1/src/lib.rs", "") .file("p2/Cargo.toml", &basic_manifest("p2", "0.1.0")) .file("p2/src/lib.rs", "") .file("p3/Cargo.toml", &basic_manifest("p3", "0.1.0")) .file("p3/src/lib.rs", ""); let p = p.build(); p.cargo("check").cwd("p1").run(); p.cargo("check").cwd("p2").run(); p.cargo("check").cwd("p3").run(); p.cargo("check").run(); assert!(p.root().join("target").is_dir()); assert!(!p.root().join("p1/target").is_dir()); assert!(!p.root().join("p2/target").is_dir()); assert!(!p.root().join("p3/target").is_dir()); } #[cargo_test] fn new_creates_members_list() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] "#, ) .file("src/lib.rs", ""); let p = p.build(); p.cargo("new --lib bar").with_stderr_data(str![[r#" [CREATING] library `bar` package [ADDING] `bar` as member of workspace at `[ROOT]/foo` [NOTE] see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html "#]]).run(); } #[cargo_test] fn new_warning_with_corrupt_ws() { let p = project().file("Cargo.toml", "asdf").build(); p.cargo("new bar").with_stderr_data(str![[r#" [CREATING] binary (application) `bar` package [ERROR] key with no value, expected `=` --> Cargo.toml:1:5 | 1 | asdf | ^ [WARNING] compiling this new package may not work due to invalid workspace configuration failed searching for potential workspace package manifest: `[ROOT]/foo/bar/Cargo.toml` invalid potential workspace manifest: `[ROOT]/foo/Cargo.toml` [HELP] to avoid searching for a non-existent workspace, add `[workspace]` to the package manifest [NOTE] see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html "#]]).run(); } #[cargo_test] fn lock_doesnt_change_depending_on_crate() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ['baz'] [dependencies] foo = "*" "#, ) .file("src/lib.rs", "") .file( "baz/Cargo.toml", r#" [package] name = "baz" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = "*" "#, ) .file("baz/src/lib.rs", ""); let p = p.build(); Package::new("foo", "1.0.0").publish(); Package::new("bar", "1.0.0").publish(); p.cargo("check").run(); let lockfile = p.read_lockfile(); p.cargo("check").cwd("baz").run(); let lockfile2 = p.read_lockfile(); assert_eq!(lockfile, lockfile2); } #[cargo_test] fn rebuild_please() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ['lib', 'bin'] "#, ) .file("lib/Cargo.toml", &basic_manifest("lib", "0.1.0")) .file( "lib/src/lib.rs", r#" pub fn foo() -> u32 { 0 } "#, ) .file( "bin/Cargo.toml", r#" [package] name = "bin" version = "0.1.0" edition = "2015" [dependencies] lib = { path = "../lib" } "#, ) .file( "bin/src/main.rs", r#" extern crate lib; fn main() { assert_eq!(lib::foo(), 0); } "#, ); let p = p.build(); p.cargo("run").cwd("bin").run(); sleep_ms(1000); p.change_file("lib/src/lib.rs", "pub fn foo() -> u32 { 1 }"); p.cargo("build").cwd("lib").run(); p.cargo("run") .cwd("bin") .with_status(101) .with_stderr_data(str![[r#" ... assertion[..] ... "#]]) .run(); } #[cargo_test] fn workspace_in_git() { let git_project = git::new("dep1", |project| { project .file( "Cargo.toml", r#" [workspace] members = ["foo"] "#, ) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", "") }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "lib" version = "0.1.0" edition = "2015" [dependencies.foo] git = '{}' "#, git_project.url() ), ) .file( "src/lib.rs", r#" pub fn foo() -> u32 { 0 } "#, ); let p = p.build(); p.cargo("check").run(); } #[cargo_test] fn lockfile_can_specify_nonexistent_members() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a"] "#, ) .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/main.rs", "fn main() {}") .file( "Cargo.lock", r#" [[package]] name = "a" version = "0.1.0" [[package]] name = "b" version = "0.1.0" "#, ); let p = p.build(); p.cargo("check").cwd("a").run(); } #[cargo_test] fn you_cannot_generate_lockfile_for_empty_workspaces() { let p = project() .file( "Cargo.toml", r#" [workspace] "#, ) .file("bar/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("update") .with_status(101) .with_stderr_data(str![[r#" [ERROR] you can't generate a lockfile for an empty workspace. "#]]) .run(); } #[cargo_test] fn workspace_with_transitive_dev_deps() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" edition = "2015" authors = ["mbrubeck@example.com"] [dependencies.bar] path = "bar" [workspace] "#, ) .file("src/main.rs", r#"fn main() {}"#) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.5.0" edition = "2015" authors = ["mbrubeck@example.com"] [dev-dependencies.baz] path = "../baz" "#, ) .file( "bar/src/lib.rs", r#" pub fn init() {} #[cfg(test)] #[test] fn test() { extern crate baz; baz::do_stuff(); } "#, ) .file("baz/Cargo.toml", &basic_manifest("baz", "0.5.0")) .file("baz/src/lib.rs", r#"pub fn do_stuff() {}"#); let p = p.build(); p.cargo("test -p bar").run(); } #[cargo_test] fn error_if_parent_cargo_toml_is_invalid() { let p = project() .file("Cargo.toml", "Totally not a TOML file") .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .cwd("bar") .with_status(101) .with_stderr_data(str![[r#" [ERROR] key with no value, expected `=` --> ../Cargo.toml:1:9 | 1 | Totally not a TOML file | ^ [ERROR] failed searching for potential workspace package manifest: `[ROOT]/foo/bar/Cargo.toml` invalid potential workspace manifest: `[ROOT]/foo/Cargo.toml` [HELP] to avoid searching for a non-existent workspace, add `[workspace]` to the package manifest "#]]) .run(); } #[cargo_test] fn relative_path_for_member_works() { let p = project() .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["../bar"] "#, ) .file("foo/src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = "../foo" "#, ) .file("bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check").cwd("foo").run(); p.cargo("check").cwd("bar").run(); } #[cargo_test] fn relative_path_for_root_works() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] [dependencies] subproj = { path = "./subproj" } "#, ) .file("src/main.rs", "fn main() {}") .file("subproj/Cargo.toml", &basic_manifest("subproj", "0.1.0")) .file("subproj/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check --manifest-path ./Cargo.toml").run(); p.cargo("check --manifest-path ../Cargo.toml") .cwd("subproj") .run(); } #[cargo_test] fn path_dep_outside_workspace_is_not_member() { let p = project() .no_manifest() .file( "ws/Cargo.toml", r#" [package] name = "ws" version = "0.1.0" edition = "2015" authors = [] [dependencies] foo = { path = "../foo" } [workspace] "#, ) .file("ws/src/lib.rs", "extern crate foo;") .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", ""); let p = p.build(); p.cargo("check").cwd("ws").run(); } #[cargo_test] fn test_in_and_out_of_workspace() { let p = project() .no_manifest() .file( "ws/Cargo.toml", r#" [package] name = "ws" version = "0.1.0" edition = "2015" authors = [] [dependencies] foo = { path = "../foo" } [workspace] members = [ "../bar" ] "#, ) .file("ws/src/lib.rs", "extern crate foo; pub fn f() { foo::f() }") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "../bar" } "#, ) .file( "foo/src/lib.rs", "extern crate bar; pub fn f() { bar::f() }", ) .file( "bar/Cargo.toml", r#" [package] workspace = "../ws" name = "bar" version = "0.1.0" edition = "2015" authors = [] "#, ) .file("bar/src/lib.rs", "pub fn f() { }"); let p = p.build(); p.cargo("check").cwd("ws").run(); assert!(p.root().join("ws/Cargo.lock").is_file()); assert!(p.root().join("ws/target").is_dir()); assert!(!p.root().join("foo/Cargo.lock").is_file()); assert!(!p.root().join("foo/target").is_dir()); assert!(!p.root().join("bar/Cargo.lock").is_file()); assert!(!p.root().join("bar/target").is_dir()); p.cargo("check").cwd("foo").run(); assert!(p.root().join("foo/Cargo.lock").is_file()); assert!(p.root().join("foo/target").is_dir()); assert!(!p.root().join("bar/Cargo.lock").is_file()); assert!(!p.root().join("bar/target").is_dir()); } #[cargo_test] fn test_path_dependency_under_member() { let p = project() .file( "ws/Cargo.toml", r#" [package] name = "ws" version = "0.1.0" edition = "2015" authors = [] [dependencies] foo = { path = "../foo" } [workspace] "#, ) .file("ws/src/lib.rs", "extern crate foo; pub fn f() { foo::f() }") .file( "foo/Cargo.toml", r#" [package] workspace = "../ws" name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "./bar" } "#, ) .file( "foo/src/lib.rs", "extern crate bar; pub fn f() { bar::f() }", ) .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("foo/bar/src/lib.rs", "pub fn f() { }"); let p = p.build(); p.cargo("check").cwd("ws").run(); assert!(!p.root().join("foo/bar/Cargo.lock").is_file()); assert!(!p.root().join("foo/bar/target").is_dir()); p.cargo("check").cwd("foo/bar").run(); assert!(!p.root().join("foo/bar/Cargo.lock").is_file()); assert!(!p.root().join("foo/bar/target").is_dir()); } #[cargo_test] fn excluded_simple() { let p = project() .file( "Cargo.toml", r#" [package] name = "ws" version = "0.1.0" edition = "2015" authors = [] [workspace] exclude = ["foo"] "#, ) .file("src/lib.rs", "") .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", ""); let p = p.build(); p.cargo("check").run(); assert!(p.root().join("target").is_dir()); p.cargo("check").cwd("foo").run(); assert!(p.root().join("foo/target").is_dir()); } #[cargo_test] fn exclude_members_preferred() { let p = project() .file( "Cargo.toml", r#" [package] name = "ws" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["foo/bar"] exclude = ["foo"] "#, ) .file("src/lib.rs", "") .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", "") .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("foo/bar/src/lib.rs", ""); let p = p.build(); p.cargo("check").run(); assert!(p.root().join("target").is_dir()); p.cargo("check").cwd("foo").run(); assert!(p.root().join("foo/target").is_dir()); p.cargo("check").cwd("foo/bar").run(); assert!(!p.root().join("foo/bar/target").is_dir()); } #[cargo_test] fn exclude_but_also_depend() { let p = project() .file( "Cargo.toml", r#" [package] name = "ws" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "foo/bar" } [workspace] exclude = ["foo"] "#, ) .file("src/lib.rs", "") .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", "") .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("foo/bar/src/lib.rs", ""); let p = p.build(); p.cargo("check").run(); assert!(p.root().join("target").is_dir()); p.cargo("check").cwd("foo").run(); assert!(p.root().join("foo/target").is_dir()); p.cargo("check").cwd("foo/bar").run(); assert!(p.root().join("foo/bar/target").is_dir()); } #[cargo_test] fn excluded_default_members_still_must_be_members() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo"] default-members = ["foo", "bar"] exclude = ["bar"] "#, ) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", "") .file("bar/something.txt", ""); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package `[ROOT]/foo/bar` is listed in default-members but is not a member for workspace at `[ROOT]/foo/Cargo.toml`. "#]]) .run(); } #[cargo_test] fn excluded_default_members_crate_glob() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "bar/*"] default-members = ["bar/*"] exclude = ["bar/quux"] "#, ) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/main.rs", "fn main() {}") .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("bar/baz/src/main.rs", "fn main() {}") .file("bar/quux/Cargo.toml", &basic_manifest("quux", "0.1.0")) .file("bar/quux/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").run(); assert!(p.root().join("target").is_dir()); assert!(!p.bin("foo").is_file()); assert!(p.bin("baz").is_file()); assert!(!p.bin("quux").exists()); p.cargo("build --workspace").run(); assert!(p.root().join("target").is_dir()); assert!(p.bin("foo").is_file()); assert!(!p.bin("quux").exists()); p.cargo("build").cwd("bar/quux").run(); assert!(p.root().join("bar/quux/target").is_dir()); } #[cargo_test] fn excluded_default_members_not_crate_glob() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "bar/*"] default-members = ["bar/*"] exclude = ["bar/docs"] "#, ) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/main.rs", "fn main() {}") .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("bar/baz/src/main.rs", "fn main() {}") .file("bar/docs/readme.txt", "This folder is not a crate!"); let p = p.build(); p.cargo("build").run(); assert!(!p.bin("foo").is_file()); assert!(p.bin("baz").is_file()); p.cargo("build --workspace").run(); assert!(p.bin("foo").is_file()); } #[cargo_test] fn glob_syntax() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["crates/*"] exclude = ["crates/qux"] "#, ) .file("src/main.rs", "fn main() {}") .file( "crates/bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = "../.." "#, ) .file("crates/bar/src/main.rs", "fn main() {}") .file( "crates/baz/Cargo.toml", r#" [package] name = "baz" version = "0.1.0" edition = "2015" authors = [] workspace = "../.." "#, ) .file("crates/baz/src/main.rs", "fn main() {}") .file( "crates/qux/Cargo.toml", r#" [package] name = "qux" version = "0.1.0" edition = "2015" authors = [] "#, ) .file("crates/qux/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); assert!(!p.bin("bar").is_file()); assert!(!p.bin("baz").is_file()); p.cargo("build").cwd("crates/bar").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("bar").is_file()); p.cargo("build").cwd("crates/baz").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("baz").is_file()); p.cargo("build").cwd("crates/qux").run(); assert!(!p.bin("qux").is_file()); assert!(p.root().join("Cargo.lock").is_file()); assert!(!p.root().join("crates/bar/Cargo.lock").is_file()); assert!(!p.root().join("crates/baz/Cargo.lock").is_file()); assert!(p.root().join("crates/qux/Cargo.lock").is_file()); } /*FIXME: This fails because of how workspace.exclude and workspace.members are working. #[cargo_test] fn glob_syntax_2() { let p = project() .file("Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["crates/b*"] exclude = ["crates/q*"] "#) .file("src/main.rs", "fn main() {}") .file("crates/bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = "../.." "#) .file("crates/bar/src/main.rs", "fn main() {}") .file("crates/baz/Cargo.toml", r#" [package] name = "baz" version = "0.1.0" edition = "2015" authors = [] workspace = "../.." "#) .file("crates/baz/src/main.rs", "fn main() {}") .file("crates/qux/Cargo.toml", r#" [package] name = "qux" version = "0.1.0" edition = "2015" authors = [] "#) .file("crates/qux/src/main.rs", "fn main() {}"); p.build(); p.cargo("build").run(); assert!(p.bin("foo").is_file()); assert!(!p.bin("bar").is_file()); assert!(!p.bin("baz").is_file()); p.cargo("build").cwd("crates/bar").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("bar").is_file()); p.cargo("build").cwd("crates/baz").run(); assert!(p.bin("foo").is_file()); assert!(p.bin("baz").is_file()); p.cargo("build").cwd("crates/qux").run(); assert!(!p.bin("qux").is_file()); assert!(p.root().join("Cargo.lock").is_file()); assert!(!p.root().join("crates/bar/Cargo.lock").is_file()); assert!(!p.root().join("crates/baz/Cargo.lock").is_file()); assert!(p.root().join("crates/qux/Cargo.lock").is_file()); } */ #[cargo_test] fn glob_syntax_invalid_members() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["crates/*"] "#, ) .file("src/main.rs", "fn main() {}") .file("crates/bar/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to load manifest for workspace member `[ROOT]/foo/crates/bar` referenced via `crates/*` by workspace at `[ROOT]/foo/Cargo.toml` Caused by: failed to read `[ROOT]/foo/crates/bar/Cargo.toml` Caused by: [NOT_FOUND] "#]]) .run(); } /// This is a freshness test for feature use with workspaces. /// /// `feat_lib` is used by `caller1` and `caller2`, but with different features enabled. /// This test ensures that alternating building `caller1`, `caller2` doesn't force /// recompile of `feat_lib`. /// /// Ideally, once we solve rust-lang/cargo#3620, then a single Cargo build at the top level /// will be enough. #[cargo_test] fn dep_used_with_separate_features() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["feat_lib", "caller1", "caller2"] "#, ) .file( "feat_lib/Cargo.toml", r#" [package] name = "feat_lib" version = "0.1.0" edition = "2015" authors = [] [features] myfeature = [] "#, ) .file("feat_lib/src/lib.rs", "") .file( "caller1/Cargo.toml", r#" [package] name = "caller1" version = "0.1.0" edition = "2015" authors = [] [dependencies] feat_lib = { path = "../feat_lib" } "#, ) .file("caller1/src/main.rs", "fn main() {}") .file("caller1/src/lib.rs", "") .file( "caller2/Cargo.toml", r#" [package] name = "caller2" version = "0.1.0" edition = "2015" authors = [] [dependencies] feat_lib = { path = "../feat_lib", features = ["myfeature"] } caller1 = { path = "../caller1" } "#, ) .file("caller2/src/main.rs", "fn main() {}") .file("caller2/src/lib.rs", ""); let p = p.build(); // Build the entire workspace. p.cargo("build --workspace") .with_stderr_data(str![[r#" [COMPILING] feat_lib v0.1.0 ([ROOT]/foo/feat_lib) [COMPILING] caller1 v0.1.0 ([ROOT]/foo/caller1) [COMPILING] caller2 v0.1.0 ([ROOT]/foo/caller2) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); assert!(p.bin("caller1").is_file()); assert!(p.bin("caller2").is_file()); // Build `caller1`. Should build the dep library. Because the features // are different than the full workspace, it rebuilds. // Ideally once we solve rust-lang/cargo#3620, then a single Cargo build at the top level // will be enough. p.cargo("build") .cwd("caller1") .with_stderr_data(str![[r#" [COMPILING] feat_lib v0.1.0 ([ROOT]/foo/feat_lib) [COMPILING] caller1 v0.1.0 ([ROOT]/foo/caller1) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); // Alternate building `caller2`/`caller1` a few times, just to make sure // features are being built separately. Should not rebuild anything. p.cargo("build") .cwd("caller2") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("build") .cwd("caller1") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("build") .cwd("caller2") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn dont_recurse_out_of_cargo_home() { let git_project = git::new("dep", |project| { project .file("Cargo.toml", &basic_manifest("dep", "0.1.0")) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; use std::path::Path; use std::process::{self, Command}; fn main() { let cargo = env::var_os("CARGO").unwrap(); let cargo_manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap(); let output = Command::new(cargo) .args(&["metadata", "--format-version", "1", "--manifest-path"]) .arg(&Path::new(&cargo_manifest_dir).join("Cargo.toml")) .output() .unwrap(); if !output.status.success() { eprintln!("{}", String::from_utf8(output.stderr).unwrap()); process::exit(1); } } "#, ) }); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies.dep] git = "{}" [workspace] "#, git_project.url() ), ) .file("src/lib.rs", ""); let p = p.build(); p.cargo("check") .env("CARGO_HOME", p.root().join(".cargo")) .run(); } // FIXME: this fails because of how workspace.exclude and workspace.members are working. /* #[cargo_test] fn include_and_exclude() { let p = project() .file("Cargo.toml", r#" [workspace] members = ["foo"] exclude = ["foo/bar"] "#) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", "") .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("foo/bar/src/lib.rs", ""); p.build(); p.cargo("build").cwd("foo").run(); assert!(p.root().join("target").is_dir()); assert!(!p.root().join("foo/target").is_dir()); p.cargo("build").cwd("foo/bar").run(); assert!(p.root().join("foo/bar/target").is_dir()); } */ #[cargo_test] fn cargo_home_at_root_works() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["a"] "#, ) .file("src/lib.rs", "") .file("a/Cargo.toml", &basic_manifest("a", "0.1.0")) .file("a/src/lib.rs", ""); let p = p.build(); p.cargo("check").run(); p.cargo("check --frozen").env("CARGO_HOME", p.root()).run(); } #[cargo_test] fn parent_manifest_error_mentions_workspace_search() { ProjectBuilder::new(paths::home()) .file( "Cargo.toml", r#" [package] name = "home-manifest" version = "0.1.0" authors = [] "#, ) .build(); let p = project_in_home("stuff") .file("src/main.rs", "fn main() {}") .build(); p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed searching for potential workspace package manifest: `[ROOT]/home/stuff/Cargo.toml` invalid potential workspace manifest: `[ROOT]/home/Cargo.toml` [HELP] to avoid searching for a non-existent workspace, add `[workspace]` to the package manifest Caused by: failed to parse manifest at `[ROOT]/home/Cargo.toml` Caused by: no targets specified in the manifest either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present "#]]) .run(); } #[cargo_test] fn relative_rustc() { let p = project() .file( "src/main.rs", r#" use std::process::Command; use std::env; fn main() { let mut cmd = Command::new("rustc"); for arg in env::args_os().skip(1) { cmd.arg(arg); } std::process::exit(cmd.status().unwrap().code().unwrap()); } "#, ) .build(); p.cargo("build").run(); let src = p .root() .join("target/debug/foo") .with_extension(env::consts::EXE_EXTENSION); Package::new("a", "0.1.0").publish(); let p = project() .at("lib") .file( "Cargo.toml", r#" [package] name = "lib" version = "0.1.0" edition = "2015" [dependencies] a = "0.1" "#, ) .file("src/lib.rs", "") .build(); fs::copy(&src, p.root().join(src.file_name().unwrap())).unwrap(); let file = format!("./foo{}", env::consts::EXE_SUFFIX); p.cargo("build").env("RUSTC", &file).run(); } #[cargo_test] fn ws_rustc_err() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a"] "#, ) .file("a/Cargo.toml", &basic_lib_manifest("a")) .file("a/src/lib.rs", "") .build(); p.cargo("rustc") .with_status(101) .with_stderr_data(str![[r#" [ERROR] manifest path `[ROOT]/foo/Cargo.toml` is a virtual manifest, but this command requires running against an actual package in this workspace "#]]) .run(); p.cargo("rustdoc") .with_status(101) .with_stderr_data(str![[r#" [ERROR] manifest path `[ROOT]/foo/Cargo.toml` is a virtual manifest, but this command requires running against an actual package in this workspace "#]]) .run(); } #[cargo_test] fn ws_err_unused() { for table in &[ "[lib]", "[[bin]]", "[[example]]", "[[test]]", "[[bench]]", "[dependencies]", "[dev-dependencies]", "[build-dependencies]", "[features]", "[target]", "[badges]", "[lints]", ] { let key = table.trim_start_matches('[').trim_end_matches(']'); let p = project() .file( "Cargo.toml", &format!( r#" [workspace] members = ["a"] {table} "#, ), ) .file("a/Cargo.toml", &basic_lib_manifest("a")) .file("a/src/lib.rs", "") .build(); p.cargo("check") .with_status(101) .with_stderr_data(&format!( "\ [ERROR] failed to parse manifest at `[..]/foo/Cargo.toml` Caused by: this virtual manifest specifies a `{key}` section, which is not allowed ", )) .run(); } } #[cargo_test] fn ws_warn_unused() { for (key, name) in &[ ("[profile.dev]\nopt-level = 1", "profiles"), ("[replace]\n\"bar:0.1.0\" = { path = \"bar\" }", "replace"), ("[patch.crates-io]\nbar = { path = \"bar\" }", "patch"), ] { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a"] "#, ) .file( "a/Cargo.toml", &format!( r#" [package] name = "a" version = "0.1.0" edition = "2015" {} "#, key ), ) .file("a/src/lib.rs", "") .build(); p.cargo("check") .with_stderr_data(&format!( "\ [WARNING] {} for the non root package will be ignored, specify {} at the workspace root: package: [ROOT]/foo/a/Cargo.toml workspace: [ROOT]/foo/Cargo.toml [CHECKING] a v0.1.0 ([ROOT]/foo/a) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s ", name, name )) .run(); } } #[cargo_test] fn ws_warn_path() { // Warnings include path to manifest. let p = project() .file( "Cargo.toml", r#" [workspace] members = ["a"] "#, ) .file( "a/Cargo.toml", r#" cargo-features = ["edition"] [package] name = "foo" version = "0.1.0" edition = "2015" "#, ) .file("a/src/lib.rs", "") .build(); p.cargo("check").with_stderr_data(str![[r#" [WARNING] [ROOT]/foo/a/Cargo.toml: the cargo feature `edition` has been stabilized in the 1.31 release and is no longer necessary to be listed in the manifest See https://doc.rust-lang.org/cargo/reference/manifest.html#the-edition-field for more information about using this feature. [CHECKING] foo v0.1.0 ([ROOT]/foo/a) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]).run(); } #[cargo_test] fn invalid_missing() { // Make sure errors are not suppressed with -q. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [dependencies] x = { path = 'x' } "#, ) .file("src/lib.rs", "") .build(); p.cargo("check -q") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to get `x` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: failed to load source for dependency `x` Caused by: unable to update [ROOT]/foo/x Caused by: failed to read `[ROOT]/foo/x/Cargo.toml` Caused by: [NOT_FOUND] "#]]) .run(); } #[cargo_test] fn member_dep_missing() { // Make sure errors are not suppressed with -q. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" [workspace] members = ["bar"] "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" [dependencies] baz = { path = "baz" } "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); p.cargo("check -q") .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to load manifest for workspace member `[ROOT]/foo/bar` referenced by workspace at `[ROOT]/foo/Cargo.toml` Caused by: failed to load manifest for dependency `baz` Caused by: failed to read `[ROOT]/foo/bar/baz/Cargo.toml` Caused by: [NOT_FOUND] "#]]) .run(); } #[cargo_test] fn simple_primary_package_env_var() { let is_primary_package = r#" #[test] fn verify_primary_package() {{ assert!(option_env!("CARGO_PRIMARY_PACKAGE").is_some()); }} "#; let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [workspace] members = ["bar"] "#, ) .file("src/lib.rs", is_primary_package) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] workspace = ".." "#, ) .file("bar/src/lib.rs", is_primary_package); let p = p.build(); p.cargo("test").run(); // Again, this time selecting a specific crate p.cargo("clean").run(); p.cargo("test -p bar").run(); // Again, this time selecting all crates p.cargo("clean").run(); p.cargo("test --all").run(); } #[cargo_test] fn virtual_primary_package_env_var() { let is_primary_package = r#" #[test] fn verify_primary_package() {{ assert!(option_env!("CARGO_PRIMARY_PACKAGE").is_some()); }} "#; let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "bar"] "#, ) .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("foo/src/lib.rs", is_primary_package) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", is_primary_package); let p = p.build(); p.cargo("test").run(); // Again, this time selecting a specific crate p.cargo("clean").run(); p.cargo("test -p foo").run(); } #[cargo_test] fn ensure_correct_workspace_when_nested() { let p = project() .file( "Cargo.toml", r#" [workspace] [package] name = "bar" version = "0.1.0" edition = "2015" authors = [] "#, ) .file("src/lib.rs", "") .file( "sub/Cargo.toml", r#" [workspace] members = ["foo"] "#, ) .file( "sub/foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2015" authors = [] [dependencies] bar = { path = "../.."} "#, ) .file("sub/foo/src/main.rs", "fn main() {}"); let p = p.build(); p.cargo("tree") .cwd("sub/foo") .with_stdout_data(str![[r#" foo v0.1.0 ([ROOT]/foo/sub/foo) └── bar v0.1.0 ([ROOT]/foo) "#]]) .run(); } #[cargo_test] fn nonexistence_package_together_with_workspace() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" authors = [] edition = "2021" [workspace] members = ["baz"] "#, ) .file("src/lib.rs", "") .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) .file("baz/src/lib.rs", ""); let p = p.build(); p.cargo("check --package nonexistence --workspace") .with_status(101) .with_stderr_data( str![[r#" [ERROR] package(s) `nonexistence` not found in workspace `[ROOT]/foo` "#]] .unordered(), ) .run(); // With pattern * p.cargo("check --package nonpattern* --workspace") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package pattern(s) `nonpattern*` not found in workspace `[ROOT]/foo` "#]]) .run(); p.cargo("package --package nonexistence --workspace") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package(s) `nonexistence` not found in workspace `[ROOT]/foo` "#]]) .run(); // With pattern * p.cargo("package --package nonpattern* --workspace") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package pattern(s) `nonpattern*` not found in workspace `[ROOT]/foo` "#]]) .run(); p.cargo("publish --dry-run --package nonexistence --workspace") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package(s) `nonexistence` not found in workspace `[ROOT]/foo` "#]]) .run(); // With pattern * p.cargo("publish --dry-run --package nonpattern* --workspace") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package pattern(s) `nonpattern*` not found in workspace `[ROOT]/foo` "#]]) .run(); p.cargo("tree --package nonexistence --workspace") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package(s) `nonexistence` not found in workspace `[ROOT]/foo` "#]]) .run(); // With pattern * p.cargo("tree --package nonpattern* --workspace") .with_status(101) .with_stderr_data(str![[r#" [ERROR] package pattern(s) `nonpattern*` not found in workspace `[ROOT]/foo` "#]]) .run(); } // A failing case from #[cargo_test] fn fix_only_check_manifest_path_member() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "bar"] resolver = "3" "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" edition = "2021" "#, ) .file("foo/src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" edition = "2021" "#, ) .file("bar/src/main.rs", "fn main() {}") .build(); p.cargo("fix --manifest-path foo/Cargo.toml --allow-no-vcs") .with_stderr_data(str![[r#" [CHECKING] foo v0.1.0 ([ROOT]/foo/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn error_if_member_is_outside_root() { // This test ensures that if a member is physically outside the workspace root, // we get a helpful error message. // // Setup: // root/Cargo.toml (workspace, members = []) // member/Cargo.toml (package, workspace = "../root") let _root = project() .at("root") .file( "Cargo.toml", r#" [package] name = "root" version = "0.1.0" edition = "2015" [workspace] members = [] "#, ) .file("src/main.rs", "fn main() {}") .build(); let member = project() .at("member") .file( "Cargo.toml", r#" [package] name = "member" version = "0.1.0" edition = "2015" workspace = "../root" "#, ) .file("src/main.rs", "fn main() {}") .build(); // The error message should reflect that these paths are unrelated (Old Behavior) member.cargo("build") .with_status(101) .with_stderr_data(str![[r#" [ERROR] current package believes it's in a workspace when it's not: current: [ROOT]/member/Cargo.toml workspace: [ROOT]/root/Cargo.toml this may be fixable by adding `../member` to the `workspace.members` array of the manifest located at: [ROOT]/root/Cargo.toml Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest. "#]]) .run(); } #[cargo_test] fn error_if_manifest_path_is_relative() { // This test simulates running cargo usage with a relative --manifest-path // that includes `..` to verify normalization and suggestions. // // Directory structure: // root/Cargo.toml // root/subdir/ // outside/Cargo.toml (workspace = "../root") let root = project() .at("root") .file( "Cargo.toml", r#" [package] name = "root" version = "0.1.0" edition = "2015" [workspace] members = [] "#, ) .file("src/main.rs", "fn main() {}") .file("subdir/file", "") .build(); let _outside = project() .at("outside") .file( "Cargo.toml", r#" [package] name = "outside" version = "0.1.0" edition = "2015" workspace = "../root" "#, ) .file("src/main.rs", "fn main() {}") .build(); // Run from `root/subdir` pointing to `../../outside/Cargo.toml` // The workspace root is at `root`. // The package is at `outside`. // Relative path from root to outside is `../outside`. // We execute inside `root`, but targeting the outside package. root.cargo("build") .cwd(root.root().join("subdir")) .arg("-v") .arg("--manifest-path") .arg("../../outside/Cargo.toml") .with_status(101) .with_stderr_data(str![[r#" [ERROR] current package believes it's in a workspace when it's not: current: [ROOT]/outside/Cargo.toml workspace: [ROOT]/root/Cargo.toml this may be fixable by adding `../outside` to the `workspace.members` array of the manifest located at: [ROOT]/root/Cargo.toml Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest. "#]]) .run(); } ================================================ FILE: tests/testsuite/yank.rs ================================================ //! Tests for the `cargo yank` command. use std::fs; use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::registry; use cargo_test_support::str; fn setup(name: &str, version: &str) { let dir = registry::api_path().join(format!("api/v1/crates/{}/{}", name, version)); dir.mkdir_p(); fs::write(dir.join("yank"), r#"{"ok": true}"#).unwrap(); } #[cargo_test] fn explicit_version() { let registry = registry::init(); setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("yank --version 0.0.1") .replace_crates_io(registry.index_url()) .run(); p.cargo("yank --undo --version 0.0.1") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index Unyank foo@0.0.1 [ERROR] failed to undo a yank from the registry at [ROOTURL]/api Caused by: EOF while parsing a value at line 1 column 0 "#]]) .run(); } #[cargo_test] fn explicit_version_with_asymmetric() { let registry = registry::RegistryBuilder::new() .http_api() .token(cargo_test_support::registry::Token::rfc_key()) .build(); setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [project] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); // The http_api server will check that the authorization is correct. // If the authorization was not sent then we would get an unauthorized error. p.cargo("yank --version 0.0.1") .arg("-Zasymmetric-token") .masquerade_as_nightly_cargo(&["asymmetric-token"]) .replace_crates_io(registry.index_url()) .run(); p.cargo("yank --undo --version 0.0.1") .arg("-Zasymmetric-token") .masquerade_as_nightly_cargo(&["asymmetric-token"]) .replace_crates_io(registry.index_url()) .run(); } #[cargo_test] fn inline_version() { let registry = registry::init(); setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("yank foo@0.0.1") .replace_crates_io(registry.index_url()) .run(); p.cargo("yank --undo foo@0.0.1") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [UPDATING] crates.io index Unyank foo@0.0.1 [ERROR] failed to undo a yank from the registry at [ROOTURL]/api Caused by: EOF while parsing a value at line 1 column 0 "#]]) .run(); } #[cargo_test] fn version_required() { setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("yank foo") .with_status(101) .with_stderr_data(str![[r#" [ERROR] `--version` is required "#]]) .run(); } #[cargo_test] fn inline_version_without_name() { setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("yank @0.0.1") .with_status(101) .with_stderr_data(str![[r#" [ERROR] missing crate name for `@0.0.1` "#]]) .run(); } #[cargo_test] fn inline_and_explicit_version() { setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("yank foo@0.0.1 --version 0.0.1") .with_status(101) .with_stderr_data(str![[r#" [ERROR] cannot specify both `@0.0.1` and `--version` "#]]) .run(); } #[cargo_test] fn bad_version() { let registry = registry::init(); setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("yank foo@bar") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] invalid version `bar` Caused by: unexpected character 'b' while parsing major version number "#]]) .run(); } #[cargo_test] fn prefixed_v_in_version() { let registry = registry::init(); setup("foo", "0.0.1"); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] license = "MIT" description = "foo" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("yank bar@v0.0.1") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr_data(str![[r#" [ERROR] the version provided, `v0.0.1` is not a valid SemVer version [HELP] try changing the version to `0.0.1` Caused by: unexpected character 'v' while parsing major version number "#]]) .run(); } ================================================ FILE: triagebot.toml ================================================ [relabel] allow-unauthenticated = [ "A-*", "C-*", "Command-*", "E-*", "I-*", "O-*", "S-*", "Z-*", "beta-nominated", "regression-*", "relnotes", ] [ping.windows] message = """\ Hey Windows Group! This bug has been identified as a good "Windows candidate". In case it's useful, here are some [instructions] for tackling these sorts of bugs. Maybe take a look? Thanks! <3 [instructions]: https://rustc-dev-guide.rust-lang.org/notification-groups/windows.html """ label = "O-windows" [shortcut] # Enable issue transfers within the org # Documentation at: https://forge.rust-lang.org/triagebot/transfer.html [transfer] # Enable `@rustbot note` functionality # Documentation at: https://forge.rust-lang.org/triagebot/note.html [note] # Enable comments linking to triagebot range-diff when a PR is rebased # onto a different base commit # Documentation at: https://forge.rust-lang.org/triagebot/range-diff.html [range-diff] # Adds at the end of a review body a link to view the changes that happened # since the review # Documentation at: https://forge.rust-lang.org/triagebot/review-changes-since.html [review-changes-since] # Adds a "View all comments" link on the issue body that shows all the comments of an issue/PR # Documentation at: https://forge.rust-lang.org/triagebot/view-all-comments-link.html [view-all-comments-link] threshold = 20 [merge-conflicts] remove = [] add = ["S-waiting-on-author"] unless = ["S-blocked", "S-waiting-on-review"] [autolabel."S-waiting-on-review"] new_pr = true [assign] contributing_url = "https://rust-lang.github.io/cargo/contrib/" warn_non_default_branch = true [assign.owners] "*" = ["@ehuss", "@epage", "@weihanglo"] [review-submitted] reviewed_label = "S-waiting-on-author" review_labels = ["S-waiting-on-review"] [review-requested] remove_labels = ["S-waiting-on-author"] add_labels = ["S-waiting-on-review"] [autolabel."A-build-execution"] trigger_files = [ "src/cargo/core/compiler/compilation.rs", "src/cargo/core/compiler/job_queue/", "src/cargo/core/compiler/mod.rs", ] [autolabel."A-build-scripts"] trigger_files = [ "crates/build-rs-test-lib/", "crates/build-rs/", "src/cargo/core/compiler/custom_build.rs", ] [autolabel."A-cache-messages"] trigger_files = ["src/cargo/util/rustc.rs"] [autolabel."A-cargo-targets"] trigger_files = [ "src/cargo/ops/cargo_compile/compile_filter.rs", "src/cargo/ops/cargo_compile/unit_generator.rs", ] [autolabel."A-cfg-expr"] trigger_files = [ "crates/cargo-platform/", "src/cargo/core/compiler/build_context/target_info.rs", ] [autolabel."A-cli"] trigger_files = ["src/bin/", "src/cargo/util/command_prelude.rs"] [autolabel."A-cli-help"] trigger_files = ["crates/mdman/", "src/etc/man/"] [autolabel."A-completions"] trigger_files = ["src/etc/_cargo", "src/etc/cargo.bashcomp.sh"] [autolabel."A-configuration"] trigger_files = ["src/cargo/util/context/mod.rs"] [autolabel."A-console-output"] trigger_files = [ "src/cargo/core/shell.rs", "src/cargo/util/progress.rs", ] [autolabel."A-crate-dependencies"] trigger_files = ["src/cargo/core/dependency.rs"] [autolabel."A-crate-types"] trigger_files = ["src/cargo/core/compiler/crate_type.rs"] [autolabel."A-credential-provider"] trigger_files = ["credential/"] [autolabel."A-dep-info"] trigger_files = ["src/cargo/core/compiler/output_depinfo.rs"] [autolabel."A-dependency-resolution"] trigger_files = [ "benches/benchsuite/benches/resolve.rs", "crates/resolver-tests/", "src/cargo/core/resolver/", ] [autolabel."A-directory-source"] trigger_files = ["src/cargo/sources/directory.rs"] [autolabel."A-documenting-cargo-itself"] trigger_files = ["src/doc/"] [autolabel."A-environment-variables"] trigger_files = [ "crates/home/", "src/cargo/util/context/environment.rs", ] [autolabel."A-features2"] trigger_files = ["src/cargo/core/resolver/features.rs"] [autolabel."A-filesystem"] trigger_files = ["src/cargo/util/flock.rs", "src/cargo/util/important_paths.rs"] [autolabel."A-future-incompat"] trigger_files = ["src/cargo/core/compiler/future_incompat.rs"] [autolabel."A-git"] trigger_files = ["src/cargo/sources/git/", "src/cargo/ops/cargo_package/vcs.rs"] [autolabel."A-home"] trigger_files = ["crates/home/"] [autolabel."A-infrastructure"] trigger_files = [ ".cargo/", ".github/", "build.rs", "ci/", "clippy.toml", "crates/xtask-", "deny.toml", "publish.py", "triagebot.toml", ] [autolabel."A-interacts-with-crates.io"] trigger_files = ["crates/crates-io/", "src/cargo/ops/registry/"] [autolabel."A-json-output"] trigger_files = ["src/cargo/util/machine_message.rs"] [autolabel."A-layout"] trigger_files = [ "src/cargo/core/compiler/build_runner/compilation_files.rs", "src/cargo/core/compiler/layout.rs", ] [autolabel."A-links"] trigger_files = ["src/cargo/core/compiler/links.rs"] [autolabel."A-local-registry-source"] trigger_files = ["src/cargo/sources/registry/local.rs"] [autolabel."A-lockfile"] trigger_files = ["src/cargo/ops/lockfile.rs", "src/cargo/core/resolver/encode.rs"] [autolabel."A-lto"] trigger_files = ["src/cargo/core/compiler/lto.rs"] [autolabel."A-manifest"] trigger_files = [ "crates/cargo-util-schemas/src/manifest/", "src/cargo/core/manifest.rs", "src/cargo/util/toml/mod.rs", "src/cargo/util/toml_mut/", ] [autolabel."A-networking"] trigger_files = ["src/cargo/util/network/"] [autolabel."A-overrides"] trigger_files = ["src/cargo/sources/replaced.rs"] [autolabel."A-profiles"] trigger_files = ["src/cargo/core/profiles.rs"] [autolabel."A-rebuild-detection"] trigger_files = ["src/cargo/core/compiler/fingerprint/"] [autolabel."A-registries"] trigger_files = ["src/cargo/sources/registry/", "src/cargo/core/registry.rs"] [autolabel."A-registry-authentication"] trigger_files = ["src/cargo/util/auth/"] [autolabel."A-semver"] trigger_files = [ "crates/semver-check", "src/cargo/util/semver_ext.rs", ] [autolabel."A-source-replacement"] trigger_files = ["src/cargo/sources/replaced.rs"] [autolabel."A-sparse-registry"] trigger_files = ["src/cargo/sources/registry/http_remote.rs"] [autolabel."A-testing-cargo-itself"] trigger_files = [ "benches/", "crates/cargo-test-macro/", "crates/cargo-test-support/", ] [autolabel."A-timings"] trigger_files = [ "src/cargo/core/compiler/timings/", "src/cargo/util/cpu.rs", ] [autolabel."A-unstable"] trigger_files = ["src/cargo/core/features.rs"] [autolabel."A-vcs"] trigger_files = ["src/cargo/util/vcs.rs"] [autolabel."A-workspaces"] trigger_files = [ "benches/benchsuite/benches/workspace_initialization.rs", "src/cargo/core/workspace.rs", "src/cargo/util/workspace.rs" ] [autolabel."Command-add"] trigger_files = ["src/bin/cargo/commands/add.rs", "src/cargo/ops/cargo_add/"] [autolabel."Command-bench"] trigger_files = ["src/bin/cargo/commands/bench.rs"] [autolabel."Command-build"] trigger_files = ["src/bin/cargo/commands/build.rs"] [autolabel."Command-check"] trigger_files = ["src/bin/cargo/commands/check.rs"] [autolabel."Command-clean"] trigger_files = ["src/bin/cargo/commands/clean.rs", "src/cargo/ops/cargo_clean.rs"] [autolabel."Command-doc"] trigger_files = ["src/bin/cargo/commands/doc.rs", "src/cargo/ops/cargo_doc.rs"] [autolabel."Command-fetch"] trigger_files = ["src/bin/cargo/commands/fetch.rs", "src/cargo/ops/cargo_fetch.rs"] [autolabel."Command-fix"] trigger_files = [ "crates/rustfix/", "src/bin/cargo/commands/fix.rs", "src/cargo/ops/fix/", "src/cargo/util/diagnostic_server.rs", "src/cargo/util/lockserver.rs", ] [autolabel."Command-generate-lockfile"] trigger_files = ["src/bin/cargo/commands/generate_lockfile.rs"] [autolabel."Command-git-checkout"] trigger_files = ["src/bin/cargo/commands/git_checkout.rs"] [autolabel."Command-info"] trigger_files = ["src/bin/cargo/commands/info.rs", "src/cargo/ops/registry/info/"] [autolabel."Command-init"] trigger_files = ["src/bin/cargo/commands/init.rs"] [autolabel."Command-install"] trigger_files = ["src/bin/cargo/commands/install.rs", "src/cargo/ops/cargo_install.rs"] [autolabel."Command-locate-project"] trigger_files = ["src/bin/cargo/commands/locate_project.rs"] [autolabel."Command-login"] trigger_files = ["src/bin/cargo/commands/login.rs", "src/cargo/ops/registry/login.rs"] [autolabel."Command-logout"] trigger_files = ["src/bin/cargo/commands/logout.rs", "src/cargo/ops/registry/logout.rs"] [autolabel."Command-metadata"] trigger_files = ["src/bin/cargo/commands/metadata.rs", "src/cargo/ops/cargo_output_metadata.rs"] [autolabel."Command-new"] trigger_files = ["src/bin/cargo/commands/new.rs", "src/cargo/ops/cargo_new.rs"] [autolabel."Command-owner"] trigger_files = ["src/bin/cargo/commands/owner.rs", "src/cargo/ops/registry/owner.rs"] [autolabel."Command-package"] trigger_files = ["src/bin/cargo/commands/package.rs", "src/cargo/ops/cargo_package/"] [autolabel."Command-pkgid"] trigger_files = ["src/bin/cargo/commands/pkgid.rs", "src/cargo/ops/cargo_pkgid.rs"] [autolabel."Command-publish"] trigger_files = ["src/bin/cargo/commands/publish.rs", "src/cargo/ops/registry/publish.rs"] [autolabel."Command-read-manifest"] trigger_files = ["src/bin/cargo/commands/read_manifest.rs", "src/cargo/ops/cargo_read_manifest.rs"] [autolabel."Command-remove"] trigger_files = ["src/bin/cargo/commands/remove.rs", "src/cargo/ops/cargo_remove.rs"] [autolabel."Command-report"] trigger_files = ["src/bin/cargo/commands/report.rs", "src/cargo/ops/cargo_report/"] [autolabel."Command-run"] trigger_files = ["src/bin/cargo/commands/run.rs", "src/cargo/ops/cargo_run.rs"] [autolabel."Command-rustc"] trigger_files = ["src/bin/cargo/commands/rustc.rs"] [autolabel."Command-rustdoc"] trigger_files = ["src/bin/cargo/commands/rustdoc.rs"] [autolabel."Command-search"] trigger_files = ["src/bin/cargo/commands/search.rs", "src/cargo/ops/registry/search.rs"] [autolabel."Command-test"] trigger_files = ["src/bin/cargo/commands/test.rs", "src/cargo/ops/cargo_test.rs"] [autolabel."Command-tree"] trigger_files = ["src/bin/cargo/commands/tree.rs", "src/cargo/ops/tree/"] [autolabel."Command-uninstall"] trigger_files = ["src/bin/cargo/commands/uninstall.rs", "src/cargo/ops/cargo_uninstall.rs"] [autolabel."Command-update"] trigger_files = ["src/bin/cargo/commands/update.rs", "src/cargo/ops/cargo_update.rs"] [autolabel."Command-vendor"] trigger_files = ["src/bin/cargo/commands/vendor.rs", "src/cargo/ops/vendor.rs"] [autolabel."Command-verify-project"] trigger_files = ["src/bin/cargo/commands/verify_project.rs"] [autolabel."Command-version"] trigger_files = ["src/bin/cargo/commands/version.rs"] [autolabel."Command-yank"] trigger_files = ["src/bin/cargo/commands/yank.rs", "src/cargo/ops/registry/yank.rs"] ================================================ FILE: typos.toml ================================================ [files] extend-exclude = [ "crates/resolver-tests/*", "LICENSE-THIRD-PARTY", "tests/testsuite/script/rustc_fixtures", ] [default] extend-ignore-re = [ # Handles ssh keys "AAAA[0-9A-Za-z+/]+[=]{0,3}", # Handles paseto from login tests "k3[.](secret|public)[.][a-zA-Z0-9_-]+", ] extend-ignore-identifiers-re = [ # Handles git short SHA-1 hashes "[a-f0-9]{8,9}", ] extend-ignore-words-re = [ # words with length <= 4 chars is likely noise "^[a-zA-Z]{1,4}$", ] [default.extend-identifiers] # This comes from `windows_sys` ERROR_FILENAME_EXCED_RANGE = "ERROR_FILENAME_EXCED_RANGE" # `-Wwarnings` Wwarnings = "Wwarnings" # Name of a dependency flate2 = "flate2" [default.extend-words] filetimes = "filetimes" [type.cargo_command] extend-glob = ["cargo_command.rs"] [type.cargo_command.extend-words] biuld = "biuld" [type.random-sample] extend-glob = ["random-sample"] [type.random-sample.extend-words] objekt = "objekt" ================================================ FILE: windows.manifest.xml ================================================ UTF-8 true