Implement runtime-versioner audit tool (#3332)

This PR implements a build tool to audit runtime crate versions as part
of CI and release. If a runtime crate doesn't have the special
`0.0.0-smithy-rs-head` version number, then it is assumed to be
independently versioned, and the audit tool will verify it is version
bumped when any changes are made to it.

Given that there isn't a complete/reliable semver checking tool for Rust
yet, this tool isn't smart. It will rely on devs to correctly bump the
version number when that is done.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
This commit is contained in:
John DiSanti 2024-01-11 15:20:36 -08:00 committed by GitHub
parent ad133fd31a
commit 6ffd99000b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3282 additions and 587 deletions

View File

@ -92,6 +92,7 @@ jobs:
runner: smithy_ubuntu-latest_8-core
- action: check-rust-runtimes
runner: smithy_ubuntu-latest_8-core
fetch-depth: 0
- action: check-sdk-codegen-unit-tests
runner: ubuntu-latest
- action: check-server-codegen-integration-tests
@ -119,6 +120,8 @@ jobs:
with:
path: smithy-rs
ref: ${{ inputs.git_ref }}
# Defaults to 1 if not set
fetch-depth: ${{ matrix.test.fetch-depth }}
- name: Run ${{ matrix.test.action }}
uses: ./smithy-rs/.github/actions/docker-build
with:

View File

@ -358,11 +358,7 @@ tasks.register<ExecRustBuildTool>("fixManifests") {
toolPath = publisherToolPath
binaryName = "publisher"
arguments = mutableListOf("fix-manifests", "--location", outputDir.asFile.absolutePath).apply {
if (crateVersioner.independentVersioningEnabled()) {
add("--disable-version-number-validation")
}
}
arguments = mutableListOf("fix-manifests", "--location", outputDir.asFile.absolutePath)
}
tasks.register<ExecRustBuildTool>("hydrateReadme") {

View File

@ -40,8 +40,6 @@ interface VersionCrate {
moduleName: String,
service: AwsService,
): String
fun independentVersioningEnabled(): Boolean
}
class SynchronizedCrateVersioner(
@ -58,8 +56,6 @@ class SynchronizedCrateVersioner(
moduleName: String,
service: AwsService,
): String = sdkVersion
override fun independentVersioningEnabled(): Boolean = sdkVersion == LOCAL_DEV_VERSION
}
private data class SemVer(
@ -130,8 +126,6 @@ class IndependentCrateVersioner(
)
}
override fun independentVersioningEnabled(): Boolean = true
override fun decideCrateVersion(
moduleName: String,
service: AwsService,

View File

@ -82,10 +82,11 @@ RUN set -eux; \
cd smithy-rs; \
git checkout ${smithy_rs_commit_hash}; \
fi; \
cargo install --locked --path tools/ci-build/publisher; \
cargo install --locked --path tools/ci-build/changelogger; \
cargo install --locked --path tools/ci-build/crate-hasher; \
cargo install --locked --path tools/ci-build/difftags; \
cargo install --locked --path tools/ci-build/publisher; \
cargo install --locked --path tools/ci-build/runtime-versioner; \
cargo install --locked --path tools/ci-build/sdk-lints; \
cargo install --locked --path tools/ci-build/sdk-versioner; \
chmod g+rw -R /opt/cargo/registry
@ -166,7 +167,8 @@ RUN set -eux; \
python3 \
python3-devel \
python3-pip \
shadow-utils; \
shadow-utils \
tar; \
yum clean all; \
rm -rf /var/cache/yum; \
groupadd build; \

View File

@ -40,7 +40,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -621,7 +621,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -723,18 +723,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -895,7 +895,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -942,6 +942,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror",
"toml",
"tracing",
]
@ -985,9 +986,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.39"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -1043,6 +1044,26 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "time"
version = "0.3.30"
@ -1152,7 +1173,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1250,7 +1271,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@ -1284,7 +1305,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -51,7 +51,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -426,7 +426,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -829,7 +829,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -922,7 +922,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -990,9 +990,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
@ -1028,9 +1028,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -1212,7 +1212,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1316,6 +1316,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror",
"tokio",
"toml 0.5.11",
"tracing",
@ -1360,9 +1361,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.39"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -1430,22 +1431,22 @@ checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
[[package]]
name = "thiserror"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1500,7 +1501,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1596,7 +1597,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1748,7 +1749,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@ -1782,7 +1783,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -29,7 +29,7 @@ semver = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha256 = "1"
smithy-rs-tool-common = { version = "0.1", path = "../smithy-rs-tool-common", features = ["async-shell"] }
smithy-rs-tool-common = { version = "0.1", path = "../smithy-rs-tool-common", features = ["async"] }
tempfile = "3.3.0"
thiserror = "1.0"
tokio = { version = "1.20.1", features = ["full"] }

View File

@ -14,7 +14,6 @@ pub mod cargo;
pub mod fs;
pub mod package;
pub mod publish;
pub mod retry;
pub mod sort;
pub mod subcommand;
pub mod yank;

View File

@ -5,9 +5,9 @@
use crate::cargo;
use crate::package::PackageHandle;
use crate::retry::{run_with_retry, BoxError, ErrorClass};
use crates_io_api::{AsyncClient, Error};
use once_cell::sync::Lazy;
use smithy_rs_tool_common::retry::{run_with_retry, BoxError, ErrorClass};
use smithy_rs_tool_common::shell::ShellOperation;
use std::path::Path;
use std::time::Duration;

View File

@ -16,7 +16,6 @@ use anyhow::{bail, Context, Result};
use clap::Parser;
use semver::Version;
use smithy_rs_tool_common::ci::running_in_ci;
use smithy_rs_tool_common::package::PackageStability;
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
@ -40,17 +39,15 @@ pub struct FixManifestsArgs {
/// Checks manifests rather than fixing them
#[clap(long)]
check: bool,
/// Disable expected version number validation. This should only be used
/// when SDK crates are being generated with independent version numbers.
/// UNUSED. Kept for backwards compatibility. Can be removed in the future when
/// the older commits that rely on it have been synced over in a SDK release.
#[clap(long)]
disable_version_number_validation: bool,
}
pub async fn subcommand_fix_manifests(
FixManifestsArgs {
location,
check,
disable_version_number_validation,
location, check, ..
}: &FixManifestsArgs,
) -> Result<()> {
let mode = match check {
@ -61,7 +58,6 @@ pub async fn subcommand_fix_manifests(
let mut manifests = read_manifests(Fs::Real, manifest_paths).await?;
let versions = package_versions(&manifests)?;
validate::validate_before_fixes(&versions, *disable_version_number_validation)?;
fix_manifests(Fs::Real, &versions, &mut manifests, mode).await?;
validate::validate_after_fixes(location).await?;
info!("Successfully fixed manifests!");
@ -84,27 +80,6 @@ impl Manifest {
))),
}
}
fn stability(&self) -> Result<PackageStability> {
let value = self
.metadata
.get("package")
.and_then(|v| v.get("metadata"))
.and_then(|v| v.get("smithy-rs-release-tooling"))
.and_then(|v| v.get("stable"));
match value {
None => Ok(PackageStability::Unstable),
Some(value) => {
if value.as_bool().ok_or(anyhow::Error::msg(format!(
"unexpected stable setting: {value}"
)))? {
Ok(PackageStability::Stable)
} else {
Ok(PackageStability::Unstable)
}
}
}
}
}
struct Versions(BTreeMap<String, VersionWithMetadata>);
@ -133,32 +108,11 @@ impl Versions {
fn published(&self) -> VersionView {
VersionView(self, FilterType::PublishedOnly)
}
fn published_crates(&self) -> impl Iterator<Item = (&str, &Version)> + '_ {
self.0
.iter()
.filter(|(_, v)| v.publish)
.map(|(k, v)| (k.as_str(), &v.version))
}
fn get(&self, crate_name: &str) -> Option<&Version> {
self.0.get(crate_name).map(|v| &v.version)
}
fn stable(&self, crate_name: &str) -> Option<bool> {
match self.0.get(crate_name) {
Some(VersionWithMetadata { stability, .. }) => {
Some(*stability == PackageStability::Stable)
}
_ => None,
}
}
}
struct VersionWithMetadata {
version: Version,
publish: bool,
stability: PackageStability,
}
async fn read_manifests(fs: Fs, manifest_paths: Vec<PathBuf>) -> Result<Vec<Manifest>> {
@ -182,7 +136,6 @@ fn package_versions(manifests: &[Manifest]) -> Result<Versions> {
None => continue,
};
let publish = manifest.publish()?;
let stability = manifest.stability()?;
let name = package
.get("name")
.and_then(|name| name.as_str())
@ -196,14 +149,7 @@ fn package_versions(manifests: &[Manifest]) -> Result<Versions> {
anyhow::Error::msg(format!("{:?} is missing a package version", manifest.path))
})?;
let version = parse_version(&manifest.path, version)?;
versions.insert(
name.into(),
VersionWithMetadata {
version,
publish,
stability,
},
);
versions.insert(name.into(), VersionWithMetadata { version, publish });
}
Ok(Versions(versions))
}
@ -359,19 +305,16 @@ fn fix_manifest(versions: &Versions, manifest: &mut Manifest) -> Result<usize> {
mod tests {
use super::*;
fn make_versions<'a>(
versions: impl Iterator<Item = &'a (&'a str, &'a str, bool, PackageStability)>,
) -> Versions {
fn make_versions<'a>(versions: impl Iterator<Item = &'a (&'a str, &'a str, bool)>) -> Versions {
let map = versions
.into_iter()
.map(|(name, version, publish, stability)| {
.map(|(name, version, publish)| {
let publish = *publish;
(
name.to_string(),
VersionWithMetadata {
version: Version::parse(version).unwrap(),
publish,
stability: *stability,
},
)
})
@ -405,19 +348,9 @@ mod tests {
metadata,
};
let versions = &[
(
"local_build_something",
"0.2.0",
true,
PackageStability::Unstable,
),
(
"local_dev_something",
"0.1.0",
false,
PackageStability::Unstable,
),
("local_something", "1.1.3", false, PackageStability::Stable),
("local_build_something", "0.2.0", true),
("local_dev_something", "0.1.0", false),
("local_something", "1.1.3", false),
];
let versions = make_versions(versions.iter());
fix_manifest(&versions, &mut manifest).expect_err("depends on unpublished local something");
@ -451,19 +384,9 @@ mod tests {
metadata,
};
let versions = &[
(
"local_build_something",
"0.2.0",
true,
PackageStability::Unstable,
),
(
"local_dev_something",
"0.1.0",
false,
PackageStability::Unstable,
),
("local_something", "1.1.3", true, PackageStability::Stable),
("local_build_something", "0.2.0", true),
("local_dev_something", "0.1.0", false),
("local_something", "1.1.3", true),
];
let versions = make_versions(versions.iter());
@ -522,52 +445,4 @@ mod tests {
"aws-sdk-rust/examples/foo/bar/Cargo.toml"
));
}
#[test]
fn test_package_stability_from_manifest() {
fn verify_package_stability_for_manifest(
manifest: &[u8],
expected_stability: PackageStability,
) {
let metadata = toml::from_slice(manifest).unwrap();
let manifest = Manifest {
path: "test".into(),
metadata,
};
assert_eq!(expected_stability, manifest.stability().unwrap());
}
let stable_manifest = br#"
[package]
name = "test"
version = "1.0.0"
[package.metadata.smithy-rs-release-tooling]
stable = true
"#;
verify_package_stability_for_manifest(stable_manifest, PackageStability::Stable);
let explicitly_unstable_manifest = br#"
[package]
name = "test"
version = "0.1.0"
[package.metadata.smithy-rs-release-tooling]
stable = false
"#;
verify_package_stability_for_manifest(
explicitly_unstable_manifest,
PackageStability::Unstable,
);
let implicitly_unstable_manifest = br#"
[package]
name = "test"
version = "0.1.0"
"#;
verify_package_stability_for_manifest(
implicitly_unstable_manifest,
PackageStability::Unstable,
);
}
}

View File

@ -5,62 +5,10 @@
use crate::fs::Fs;
use crate::package::discover_and_validate_package_batches;
use crate::subcommand::fix_manifests::Versions;
use anyhow::{anyhow, bail, Result};
use semver::Version;
use smithy_rs_tool_common::package::PackageCategory;
use anyhow::Result;
use std::path::Path;
use tracing::info;
/// Validations that run before the manifests are fixed.
///
/// For now, this validates:
/// - `aws-smithy-` prefixed versions match `aws-` (NOT `aws-sdk-`) prefixed versions
pub(super) fn validate_before_fixes(
versions: &Versions,
disable_version_number_validation: bool,
) -> Result<()> {
// Later when we only generate independently versioned SDK crates, this flag can become permanent.
if disable_version_number_validation {
return Ok(());
}
info!("Pre-validation manifests...");
let expected_stable_runtime_version = versions
.get("aws-smithy-types")
.ok_or_else(|| anyhow!("`aws-smithy-types` crate missing"))?;
let expected_unstable_runtime_version = versions
.get("aws-smithy-http")
.ok_or_else(|| anyhow!("`aws-smithy-http` crate missing"))?;
for (name, version) in versions.published_crates() {
let category = PackageCategory::from_package_name(name);
if category == PackageCategory::SmithyRuntime || category == PackageCategory::AwsRuntime {
let expected_runtime_version = if let Some(true) = versions.stable(name) {
expected_stable_runtime_version
} else {
expected_unstable_runtime_version
};
confirm_version(name, expected_runtime_version, version)?;
}
}
Ok(())
}
fn confirm_version(name: &str, expected: &Version, actual: &Version) -> Result<()> {
if expected != actual {
bail!(
"Crate named `{}` should be at version `{}` but is at `{}`",
name,
expected,
actual
);
}
Ok(())
}
/// Validations that run after fixing the manifests.
///
/// These should match the validations that the `publish` subcommand runs.
@ -69,95 +17,3 @@ pub(super) async fn validate_after_fixes(location: &Path) -> Result<()> {
discover_and_validate_package_batches(Fs::Real, location).await?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::subcommand::fix_manifests::VersionWithMetadata;
use smithy_rs_tool_common::package::PackageStability;
use std::collections::BTreeMap;
use std::str::FromStr;
fn versions(versions: &[(&'static str, &'static str, PackageStability)]) -> Versions {
let mut map = BTreeMap::new();
for (name, version, stability) in versions {
map.insert(
(*name).into(),
VersionWithMetadata {
version: Version::from_str(version).unwrap(),
publish: true,
stability: *stability,
},
);
}
Versions(map)
}
#[track_caller]
fn expect_success(version_tuples: &[(&'static str, &'static str, PackageStability)]) {
validate_before_fixes(&versions(version_tuples), false).expect("success");
}
#[track_caller]
fn expect_failure(
message: &str,
version_tuples: &[(&'static str, &'static str, PackageStability)],
) {
if let Err(err) = validate_before_fixes(&versions(version_tuples), false) {
assert_eq!(message, format!("{}", err));
} else {
panic!("Expected validation failure");
}
}
#[test]
fn pre_validate() {
expect_success(&[
("aws-config", "1.5.2", PackageStability::Stable),
("aws-smithy-http", "0.35.1", PackageStability::Unstable),
("aws-sdk-s3", "1.5.2", PackageStability::Stable),
("aws-smithy-types", "1.5.2", PackageStability::Stable),
("aws-types", "1.5.2", PackageStability::Stable),
]);
expect_success(&[
("aws-smithy-types", "1.5.2", PackageStability::Stable),
("aws-smithy-http", "0.35.1", PackageStability::Unstable),
]);
expect_failure(
"Crate named `aws-smithy-runtime-api` should be at version `1.5.3` but is at `1.5.2`",
&[
("aws-smithy-runtime-api", "1.5.2", PackageStability::Stable),
("aws-smithy-types", "1.5.3", PackageStability::Stable),
("aws-smithy-http", "0.35.0", PackageStability::Unstable),
],
);
expect_success(&[
("aws-config", "1.5.2", PackageStability::Stable),
("aws-smithy-http", "0.35.0", PackageStability::Unstable),
("aws-sdk-s3", "1.5.2", PackageStability::Stable),
("aws-smithy-types", "1.5.2", PackageStability::Stable),
("aws-types", "1.5.2", PackageStability::Stable),
]);
expect_failure(
"Crate named `aws-types` should be at version `1.5.3` but is at `1.5.2`",
&[
("aws-config", "1.5.3", PackageStability::Stable),
("aws-sdk-s3", "1.5.3", PackageStability::Stable),
("aws-smithy-http", "0.35.0", PackageStability::Unstable),
("aws-smithy-types", "1.5.3", PackageStability::Stable),
("aws-types", "1.5.2", PackageStability::Stable),
],
);
expect_success(&[
("aws-config", "1.5.3", PackageStability::Stable),
("aws-sdk-s3", "1.5.3", PackageStability::Stable),
("aws-smithy-types", "1.5.3", PackageStability::Stable),
("aws-smithy-http", "0.35.0", PackageStability::Unstable),
]);
}
}

View File

@ -9,7 +9,6 @@ use crate::package::{
PackageHandle, PackageStats,
};
use crate::publish::{publish, CRATES_IO_CLIENT};
use crate::retry::{run_with_retry, BoxError, ErrorClass};
use crate::{cargo, SDK_REPO_CRATE_PATH, SDK_REPO_NAME};
use anyhow::{bail, Context, Result};
use clap::Parser;
@ -17,6 +16,7 @@ use crates_io_api::Error;
use dialoguer::Confirm;
use smithy_rs_tool_common::git;
use smithy_rs_tool_common::package::PackageCategory;
use smithy_rs_tool_common::retry::{run_with_retry, BoxError, ErrorClass};
use smithy_rs_tool_common::shell::ShellOperation;
use std::collections::HashSet;
use std::path::{Path, PathBuf};

View File

@ -4,7 +4,7 @@
*/
use crate::cargo;
use crate::retry::{run_with_retry, BoxError, ErrorClass};
use smithy_rs_tool_common::retry::{run_with_retry, BoxError, ErrorClass};
use smithy_rs_tool_common::shell::ShellOperation;
use std::time::Duration;
use tracing::info;

1635
tools/ci-build/runtime-versioner/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
[package]
name = "runtime-versioner"
version = "0.1.0"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
description = "Tool that manages runtime crate versions."
edition = "2021"
license = "Apache-2.0"
publish = false
[workspace]
[profile.release]
# prefer fast compile time over runtime performance
opt-level = 0
[dependencies]
anyhow = "1.0.75"
camino = "1.1.6"
clap = { version = "4.4.11", features = ["derive"] }
crates-index = "2.3.0"
reqwest = { version = "0.11.22", features = ["blocking"] }
smithy-rs-tool-common = { version = "0.1", path = "../smithy-rs-tool-common" }
tempfile = "3.9.0"
toml = { version = "0.5.8", features = ["preserve_order"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[dev-dependencies]
test-common = { path = "./test-common" }

View File

@ -0,0 +1,259 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use crate::{
index::CratesIndex,
repo::Repo,
tag::{previous_release_tag, release_tags},
util::utf8_path_buf,
Audit,
};
use anyhow::{anyhow, bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use smithy_rs_tool_common::{command::sync::CommandExt, release_tag::ReleaseTag};
use std::{
collections::{BTreeMap, BTreeSet},
fs,
process::Command,
};
pub fn audit(args: Audit) -> Result<()> {
let repo = Repo::new(args.smithy_rs_path.as_deref())?;
if !args.no_fetch {
// Make sure we have the latest release tags
fetch_smithy_rs_tags(&repo)?;
}
let release_tags = release_tags(&repo)?;
let previous_release_tag =
previous_release_tag(&repo, &release_tags, args.previous_release_tag.as_deref())?;
if release_tags.first() != Some(&previous_release_tag) {
tracing::warn!("there are newer releases since '{previous_release_tag}'");
}
let next_crates = discover_runtime_crates(&repo.root).context("next")?;
let previous_crates = resolve_previous_crates(&repo, previous_release_tag.as_str())?;
let crates = augment_runtime_crates(previous_crates, next_crates, args.fake_crates_io_index)?;
let mut errors = Vec::new();
for rt_crate in crates {
if let Err(err) = audit_crate(&repo, &previous_release_tag, rt_crate) {
errors.push(err);
}
}
if errors.is_empty() {
println!("SUCCESS");
Ok(())
} else {
for error in errors {
eprintln!("{error}");
}
bail!("there are audit failures in the runtime crates")
}
}
fn audit_crate(repo: &Repo, release_tag: &ReleaseTag, rt_crate: RuntimeCrate) -> Result<()> {
if rt_crate.changed_since_release(repo, release_tag)? {
// If this version has never been published before, then we're good.
// (This tool doesn't check semver compatibility.)
if !rt_crate.next_version_is_published() {
if let Some(previous_version) = rt_crate.previous_release_version {
tracing::info!(
"'{}' changed and was version bumped from {previous_version} to {}",
rt_crate.name,
rt_crate.next_release_version,
);
} else {
tracing::info!(
"'{}' is a new crate (or wasn't independently versioned before) and will publish at {}",
rt_crate.name,
rt_crate.next_release_version,
);
}
Ok(())
} else if rt_crate.previous_release_version.as_ref() != Some(&rt_crate.next_release_version)
{
Err(anyhow!(
"{crate_name} was changed and version bumped, but the new version \
number ({version}) has already been published to crates.io. Choose a new \
version number.",
crate_name = rt_crate.name,
version = rt_crate.next_release_version,
))
} else {
Err(anyhow!(
"{crate_name} changed since {release_tag} and requires a version bump",
crate_name = rt_crate.name
))
}
} else {
// If it didn't change at all since last release, then we're good.
Ok(())
}
}
struct RuntimeCrate {
name: String,
path: Utf8PathBuf,
previous_release_version: Option<String>,
next_release_version: String,
published_versions: Vec<String>,
}
impl RuntimeCrate {
/// True if the runtime crate's next version exists in crates.io
fn next_version_is_published(&self) -> bool {
self.published_versions
.iter()
.any(|version| self.next_release_version == *version)
}
/// True if this runtime crate changed since the given release tag.
fn changed_since_release(&self, repo: &Repo, release_tag: &ReleaseTag) -> Result<bool> {
let status = repo
.git(["diff", "--quiet", release_tag.as_str(), self.path.as_str()])
.status()
.with_context(|| format!("failed to git diff {}", self.name))?;
match status.code() {
Some(0) => Ok(false),
Some(1) => Ok(true),
code => bail!("unknown git diff result: {code:?}"),
}
}
}
/// Loads version information from crates.io and attaches it to the passed in runtime crates.
fn augment_runtime_crates(
previous_crates: BTreeMap<String, DiscoveredCrate>,
next_crates: BTreeMap<String, DiscoveredCrate>,
fake_crates_io_index: Option<Utf8PathBuf>,
) -> Result<Vec<RuntimeCrate>> {
let index = fake_crates_io_index
.map(CratesIndex::fake)
.map(Ok)
.unwrap_or_else(CratesIndex::real)?;
let all_keys: BTreeSet<_> = previous_crates.keys().chain(next_crates.keys()).collect();
let mut result = Vec::new();
for key in all_keys {
let previous_crate = previous_crates.get(key);
if let Some(next_crate) = next_crates.get(key) {
result.push(RuntimeCrate {
published_versions: index.published_versions(&next_crate.name)?,
name: next_crate.name.clone(),
previous_release_version: previous_crate.map(|c| c.version.clone()),
next_release_version: next_crate.version.clone(),
path: next_crate.path.clone(),
});
} else {
tracing::warn!("runtime crate '{key}' was removed and will not be published");
}
}
Ok(result)
}
struct DiscoveredCrate {
name: String,
version: String,
path: Utf8PathBuf,
}
/// Discovers runtime crates that are independently versioned.
/// For now, that just means the ones that don't have the special version number `0.0.0-smithy-rs-head`.
/// In the future, this can be simplified to just return all the runtime crates.
fn discover_runtime_crates(repo_root: &Utf8Path) -> Result<BTreeMap<String, DiscoveredCrate>> {
const ROOT_PATHS: &[&str] = &["rust-runtime", "aws/rust-runtime"];
let mut result = BTreeMap::new();
for &root in ROOT_PATHS {
let root = repo_root.join(root);
for entry in fs::read_dir(&root)
.context(root)
.context("failed to read dir")?
{
let entry = entry.context("failed to read dir entry")?;
if !entry.path().is_dir() {
continue;
}
let manifest_path = entry.path().join("Cargo.toml");
if !manifest_path.exists() {
continue;
}
let manifest: toml::Value =
toml::from_slice(&fs::read(&manifest_path).context("failed to read manifest")?)
.context("failed to parse manifest")?;
let publish = manifest["package"]
.get("publish")
.and_then(|p| p.as_bool())
.unwrap_or(true);
let version = manifest["package"]["version"]
.as_str()
.expect("version is a string");
if publish && version != "0.0.0-smithy-rs-head" {
let name: String = entry.path().file_name().unwrap().to_string_lossy().into();
result.insert(
name.clone(),
DiscoveredCrate {
name,
version: version.into(),
path: utf8_path_buf(entry.path()),
},
);
}
}
}
Ok(result)
}
fn resolve_previous_crates(
repo: &Repo,
previous_release_tag: &str,
) -> Result<BTreeMap<String, DiscoveredCrate>> {
// We checkout to a temp path so that this can be run with a dirty working tree
// (for running in local development).
let tempdir = tempfile::tempdir()?;
let tempdir_path = Utf8Path::from_path(tempdir.path()).unwrap();
let clone_path = tempdir_path.join("smithy-rs");
fs::create_dir_all(&clone_path).context("failed to create temp smithy-rs repo")?;
checkout_runtimes_to(repo, previous_release_tag, &clone_path)
.context("resolve previous crates")?;
discover_runtime_crates(&clone_path).context("resolve previous crates")
}
/// Fetches the latest tags from smithy-rs origin.
fn fetch_smithy_rs_tags(repo: &Repo) -> Result<()> {
let output = repo
.git(["remote", "get-url", "origin"])
.output()
.context("failed to verify origin git remote")?;
let origin_url = String::from_utf8(output.stdout)
.expect("valid utf-8")
.trim()
.to_string();
if origin_url != "git@github.com:smithy-lang/smithy-rs.git" {
bail!("smithy-rs origin must be 'git@github.com:smithy-lang/smithy-rs.git' in order to get the latest release tags");
}
repo.git(["fetch", "--tags", "origin"])
.expect_success_output("fetch tags")?;
Ok(())
}
fn checkout_runtimes_to(repo: &Repo, revision: &str, into: impl AsRef<Utf8Path>) -> Result<()> {
Command::new("git")
.arg("init")
.current_dir(into.as_ref())
.expect_success_output("init")?;
let tmp_repo = Repo::new(Some(into.as_ref()))?;
tmp_repo
.git(["remote", "add", "origin", repo.root.as_str()])
.expect_success_output("remote add origin")?;
tmp_repo
.git(["fetch", "origin", revision, "--depth", "1"])
.expect_success_output("fetch revision")?;
tmp_repo
.git(["reset", "--hard", "FETCH_HEAD"])
.expect_success_output("reset")?;
Ok(())
}

View File

@ -0,0 +1,109 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use anyhow::{anyhow, Context, Error, Result};
use camino::Utf8Path;
use crates_index::Crate;
use reqwest::StatusCode;
use smithy_rs_tool_common::retry::{run_with_retry_sync, ErrorClass};
use std::fs;
use std::{collections::HashMap, time::Duration};
pub struct CratesIndex(Inner);
enum Inner {
Fake(FakeIndex),
Real(crates_index::SparseIndex),
}
impl CratesIndex {
/// Returns a real sparse crates.io index.
pub fn real() -> Result<Self> {
Ok(Self(Inner::Real(
crates_index::SparseIndex::new_cargo_default()
.context("failed to initialize the sparse crates.io index")?,
)))
}
/// Returns a fake crates.io index from file, panicking if loading fails.
pub fn fake(path: impl AsRef<Utf8Path>) -> Self {
Self(Inner::Fake(FakeIndex::from_file(path)))
}
/// Retrieves the published versions for the given crate name.
pub fn published_versions(&self, crate_name: &str) -> Result<Vec<String>> {
match &self.0 {
Inner::Fake(index) => Ok(index.crates.get(crate_name).cloned().unwrap_or_default()),
Inner::Real(index) => Ok(run_with_retry_sync(
"retrieve published versions",
3,
Duration::from_secs(1),
|| published_versions(index, crate_name),
|_err| ErrorClass::Retry,
)?),
}
}
}
fn published_versions(index: &crates_index::SparseIndex, crate_name: &str) -> Result<Vec<String>> {
let url = index
.crate_url(crate_name)
.expect("crate name is not empty string");
let crate_meta: Option<Crate> = reqwest::blocking::get(url)
.map_err(Error::from)
.and_then(|response| {
let status = response.status();
response.bytes().map(|b| (status, b)).map_err(Error::from)
})
.and_then(|(status, bytes)| match status {
status if status.is_success() => {
Crate::from_slice(&bytes).map_err(Error::from).map(Some)
}
StatusCode::NOT_FOUND => Ok(None),
status => {
let body = String::from_utf8_lossy(&bytes);
Err(anyhow!(
"request to crates.io index failed ({status}):\n{body}"
))
}
})
.with_context(|| format!("failed to retrieve crates.io metadata for {crate_name}"))?;
Ok(crate_meta
.map(|meta| {
meta.versions()
.iter()
.map(|v| v.version().to_string())
.collect()
})
.unwrap_or_default())
}
/// Fake crates.io index for testing
pub struct FakeIndex {
crates: HashMap<String, Vec<String>>,
}
impl FakeIndex {
fn from_file(path: impl AsRef<Utf8Path>) -> FakeIndex {
let bytes = fs::read(path.as_ref()).unwrap();
let toml: toml::Value = toml::from_slice(&bytes).unwrap();
let crates: HashMap<String, Vec<_>> = toml["crates"]
.as_table()
.expect("missing crates table")
.into_iter()
.map(|(k, v)| {
(
k.into(),
v.as_array()
.expect("value must be array")
.iter()
.map(|v| v.as_str().expect("must be string").to_string())
.collect::<Vec<_>>(),
)
})
.collect();
FakeIndex { crates }
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use crate::{
repo::Repo,
tag::{previous_release_tag, release_tags},
};
use anyhow::Result;
use camino::Utf8PathBuf;
use clap::Parser;
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
mod audit;
mod index;
mod repo;
mod tag;
mod util;
#[derive(clap::Args, Clone)]
pub struct Audit {
/// Don't `git fetch` before auditing.
#[arg(long)]
no_fetch: bool,
/// Explicitly state the previous release's tag. Discovers it if not provided.
#[arg(long)]
previous_release_tag: Option<String>,
/// Path to smithy-rs. Defaults to current working directory.
#[arg(long)]
smithy_rs_path: Option<Utf8PathBuf>,
/// (For testing) Path to a fake crates.io index.
#[arg(long)]
fake_crates_io_index: Option<Utf8PathBuf>,
}
#[derive(clap::Args, Clone)]
pub struct PreviousReleaseTag {
/// Path to smithy-rs. Defaults to current working directory.
#[arg(long)]
smithy_rs_path: Option<Utf8PathBuf>,
}
#[derive(clap::Parser, Clone)]
#[clap(author, version, about)]
enum Command {
/// Audit the runtime crate versions in the smithy-rs repo at HEAD
///
/// Requires a full clone of smithy-rs. Will not work against shallow clones.
///
/// This audits that any runtime crate that has been changed since the last
/// release has been version bumped. It's not smart enough to know if the version
/// bump is correct in semver terms, but verifies that there was at least a
/// bump. A human will still need to verify the semver correctness of that bump.
Audit(Audit),
/// Outputs the previous release tag for the revision at HEAD.
PreviousReleaseTag(PreviousReleaseTag),
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
let command = Command::parse();
match command {
Command::Audit(args) => audit::audit(args),
Command::PreviousReleaseTag(args) => {
let repo = Repo::new(args.smithy_rs_path.as_deref())?;
let tags = release_tags(&repo)?;
let tag = previous_release_tag(&repo, &tags, None)?;
println!("{tag}");
Ok(())
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use crate::util::utf8_path_buf;
use anyhow::{Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use smithy_rs_tool_common::git::find_git_repository_root;
use std::{env, ffi::OsStr, process::Command};
/// Git repository
pub struct Repo {
pub root: Utf8PathBuf,
}
impl Repo {
pub fn new(maybe_root: Option<&Utf8Path>) -> Result<Self> {
Ok(Self {
root: repo_root(maybe_root)?,
})
}
/// Returns a `std::process::Command` set to run git in this repo with the given args
pub fn git<I, S>(&self, args: I) -> Command
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut cmd = Command::new("git");
cmd.current_dir(&self.root);
cmd.args(args);
cmd
}
}
fn repo_root(hint: Option<&Utf8Path>) -> Result<Utf8PathBuf> {
let cwd = utf8_path_buf(env::current_dir().context("failed to get current working directory")?);
Ok(utf8_path_buf(find_git_repository_root(
"smithy-rs",
hint.unwrap_or(&cwd),
)?))
}

View File

@ -0,0 +1,120 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use crate::repo::Repo;
use anyhow::{anyhow, bail, Context, Result};
use smithy_rs_tool_common::{command::sync::CommandExt, release_tag::ReleaseTag};
use std::str::FromStr;
use tracing::warn;
/// Discovers and returns the tag of the previous release for the revision at HEAD.
pub fn previous_release_tag(
repo: &Repo,
release_tags: &[ReleaseTag],
release_tag_override: Option<&str>,
) -> Result<ReleaseTag> {
let ancestor_tag = ancestor_tag(repo)?;
let tag_override = release_tag_override
.map(ReleaseTag::from_str)
.transpose()
.context("invalid release tag given")?;
if let Some(tag_override) = tag_override {
if !release_tags.contains(&tag_override) {
bail!("specified tag '{tag_override}' doesn't exist");
}
if !tag_is_ancestor(repo, &tag_override)? {
bail!("specified tag '{tag_override}' is not an ancestor to HEAD");
}
if tag_override != ancestor_tag {
warn!(
"expected previous release to be '{ancestor_tag}', \
but '{tag_override}' was specified. Proceeding with '{tag_override}'.",
);
}
Ok(tag_override)
} else {
Ok(ancestor_tag)
}
}
fn ancestor_tag(repo: &Repo) -> Result<ReleaseTag> {
let tag = repo
.git(["describe", "--tags"])
.expect_success_output("find the current ancestor release tag")?;
let maybe_release_tag = ReleaseTag::from_str(&tag);
let release_tag = match maybe_release_tag {
Ok(tag) => Some(tag),
Err(_) => strip_describe_tags_suffix(&tag)
.map(ReleaseTag::from_str)
.transpose()
.context("failed to find ancestor release tag")?,
};
release_tag.ok_or_else(|| anyhow!("failed to find ancestor release tag"))
}
// `git describe --tags` appends a suffix if the current commit is not the tagged commit
//
// Function assumes the given tag is known to be suffixed.
fn strip_describe_tags_suffix(tag: &str) -> Option<&str> {
// Example release tag with suffix: release-2023-12-01-42-g885048e40
tag.rsplitn(3, '-').nth(2)
}
/// Returns all release tags for the repo in descending order by time.
pub fn release_tags(repo: &Repo) -> Result<Vec<ReleaseTag>> {
let mut tags: Vec<_> = repo
.git(["tag"])
.expect_success_output("find the current ancestor release tag")?
.lines()
.flat_map(|tag| match ReleaseTag::from_str(tag) {
Ok(tag) => Some(tag),
Err(_) => {
if !tag.starts_with("v0.") {
warn!("ignoring tag '{tag}': doesn't look like a release tag");
}
None
}
})
.collect();
tags.sort_by(|a, b| b.cmp(a));
Ok(tags)
}
/// True if the given tag is an ancestor to HEAD.
fn tag_is_ancestor(repo: &Repo, tag: &ReleaseTag) -> Result<bool> {
let commit = commit_for_tag(repo, tag)?;
let status = repo
.git(["merge-base", "--is-ancestor", &commit, "HEAD"])
.expect_status_one_of("determine if a tag is the ancestor to HEAD", [0, 1])?;
Ok(status == 0)
}
/// Returns the commit hash for the given tag
fn commit_for_tag(repo: &Repo, tag: &ReleaseTag) -> Result<String> {
repo.git(["rev-list", "-n1", tag.as_str()])
.expect_success_output("retrieve commit hash for tag")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strip_git_describe_tags_suffix() {
assert_eq!(
Some("release-2023-12-01"),
strip_describe_tags_suffix("release-2023-12-01-42-g885048e40")
);
assert_eq!(
Some("release-2023-12-01"),
strip_describe_tags_suffix("release-2023-12-01-2-g885048e40")
);
assert_eq!(
Some("release-2023-12-01"),
strip_describe_tags_suffix("release-2023-12-01-123-g885048e40")
);
assert_eq!(None, strip_describe_tags_suffix("invalid"));
}
}

View File

@ -0,0 +1,16 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use anyhow::Context;
use camino::{Utf8Path, Utf8PathBuf};
use std::path::Path;
pub fn utf8_path_buf(path: impl AsRef<Path>) -> Utf8PathBuf {
let path: &Path = path.as_ref();
<&Utf8Path>::try_from(path)
.with_context(|| format!("gross path_buf: {:?}", path))
.unwrap()
.into()
}

View File

@ -0,0 +1,10 @@
[package]
name = "test-common"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
camino = "1.1.6"
tempfile = "3.8.1"
test_bin = "0.4.0"

View File

@ -0,0 +1,100 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use camino::{Utf8Path, Utf8PathBuf};
use std::process::Command;
use tempfile::TempDir;
#[derive(Debug)]
pub struct VersionerOutput {
pub status: i32,
pub stdout: String,
pub stderr: String,
}
pub struct TestBase {
_tmp: TempDir,
pub test_data: Utf8PathBuf,
pub root: Utf8PathBuf,
}
impl TestBase {
pub fn new(branch_name: &str) -> Self {
let tmp = TempDir::new().unwrap();
let root = Utf8PathBuf::try_from(tmp.path().join("test_base")).unwrap();
let test_data =
Utf8PathBuf::try_from(std::env::current_dir().unwrap().join("test_data")).unwrap();
let tar_path = test_data.join("test_base.git.tar.gz");
assert!(
Command::new("tar")
.args(["xfz", tar_path.as_str()])
.current_dir(tmp.path())
.status()
.unwrap()
.success(),
"untarring the test_base into the temp directory failed"
);
assert!(
Command::new("git")
.args(["clone", "test_base.git"])
.current_dir(tmp.path())
.status()
.unwrap()
.success(),
"cloning the test_base repo failed"
);
assert!(root.exists(), "test_base not found after cloning");
change_branch(&root, branch_name);
Self {
_tmp: tmp,
test_data,
root,
}
}
pub fn change_branch(&self, branch_name: &str) {
change_branch(&self.root, branch_name);
}
pub fn run_versioner(&self, args: &[&str], expect_failure: bool) -> VersionerOutput {
let mut cmd = test_bin::get_test_bin("runtime-versioner");
let cmd = cmd.args(args).current_dir(&self.root);
let output = cmd.output().expect("failed to execute runtime-versioner");
let status = output.status.code().unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("###\ncmd: {cmd:?}\nstatus: {status}\nstdout:\n{stdout}\nstderr:\n{stderr}\n\n");
if expect_failure {
assert!(
!output.status.success(),
"expected runtime-versioner to fail, but it succeeded"
);
} else {
assert!(
output.status.success(),
"expected runtime-versioner to succeed, but it failed"
);
}
VersionerOutput {
status,
stdout: stdout.into(),
stderr: stderr.into(),
}
}
}
fn change_branch(path: &Utf8Path, branch_name: &str) {
assert!(
Command::new("git")
.args(["checkout", branch_name])
.current_dir(path)
.status()
.unwrap()
.success(),
"changing to the correct test_base branch failed"
);
}

View File

@ -0,0 +1,2 @@
/test_base.git/
/test_base/

View File

@ -0,0 +1,22 @@
all:
echo "Use the pack/unpack targets."
# Unpacks test_base.git.tar.gz
#
# Note: doesn't directly clone the bare repository into a working tree,
# or otherwise it would lose all the different branches.
unpack:
rm -rf test_base
mkdir -p test_base
tar xfz test_base.git.tar.gz
mv test_base.git test_base/.git
(cd test_base && git config --unset core.bare)
(cd test_base && git reset --hard)
# Packs test_base.git.tar.gz
pack:
git clone --bare test_base
tar cfz test_base.git.tar.gz test_base.git
rm -rf test_base.git
.PHONY: all

View File

@ -0,0 +1,14 @@
Test base archive
=================
The `test_base.git.tar.gz` is an archived test git repository that looks like a
smithy-rs repo (with only the runtime crates), with some version numbers and
release tags.
It is a bare git repository (no working tree). To modify it, use the Makefile in
this directory to unpack it with `make unpack`. This will create a test_base directory
in test_data where you can make any changes you need to the repo. Then running
`make pack` will convert that modified repository back into the archive.
When making new test cases that need some extensive setup, it's best to create
a test-case specific branch in the test base.

View File

@ -0,0 +1,28 @@
[crates]
aws-config = ["1.0.0"]
aws-endpoint = ["0.60.0"]
aws-http = ["0.60.0"]
aws-hyper = ["0.60.0"]
aws-runtime = ["1.0.0"]
aws-runtime-api = ["1.0.0"]
aws-sig-auth = ["0.60.0"]
aws-sigv4 = ["1.0.0"]
aws-smithy-async = ["1.0.1"] # Oh no, aws-smithy-async already has 1.0.1 published!
aws-smithy-checksums = ["0.60.0"]
aws-smithy-client = ["0.60.0"]
aws-smithy-eventstream = ["0.60.0"]
aws-smithy-http = ["0.60.0"]
aws-smithy-http-auth = ["0.60.0"]
aws-smithy-http-server = ["0.60.0"]
aws-smithy-http-server-python = ["0.60.0"]
aws-smithy-http-server-typescript = ["0.60.0"]
aws-smithy-http-tower = ["0.60.0"]
aws-smithy-json = ["0.60.0"]
aws-smithy-protocol-test = ["0.60.0"]
aws-smithy-query = ["0.60.0"]
aws-smithy-runtime = ["1.0.0"]
aws-smithy-runtime-api = ["1.0.0"]
aws-smithy-types = ["1.0.0"]
aws-smithy-types-convert = ["0.60.0"]
aws-smithy-xml = ["0.60.0"]
aws-types = ["1.0.0"]

View File

@ -0,0 +1,28 @@
[crates]
aws-config = ["1.0.0"]
aws-endpoint = ["0.60.0"]
aws-http = ["0.60.0"]
aws-hyper = ["0.60.0"]
aws-runtime = ["1.0.0"]
aws-runtime-api = ["1.0.0"]
aws-sig-auth = ["0.60.0"]
aws-sigv4 = ["1.0.0"]
aws-smithy-async = ["1.0.0"]
aws-smithy-checksums = ["0.60.0"]
aws-smithy-client = ["0.60.0"]
aws-smithy-eventstream = ["0.60.0"]
aws-smithy-http = ["0.60.0"]
aws-smithy-http-auth = ["0.60.0"]
aws-smithy-http-server = ["0.60.0"]
aws-smithy-http-server-python = ["0.60.0"]
aws-smithy-http-server-typescript = ["0.60.0"]
aws-smithy-http-tower = ["0.60.0"]
aws-smithy-json = ["0.60.0"]
aws-smithy-protocol-test = ["0.60.0"]
aws-smithy-query = ["0.60.0"]
aws-smithy-runtime = ["1.0.0"]
aws-smithy-runtime-api = ["1.0.0"]
aws-smithy-types = ["1.0.0"]
aws-smithy-types-convert = ["0.60.0"]
aws-smithy-xml = ["0.60.0"]
aws-types = ["1.0.0"]

View File

@ -0,0 +1,80 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use test_common::{TestBase, VersionerOutput};
fn run_audit(test_base: &TestBase, index_name: &str, expect_failure: bool) -> VersionerOutput {
test_base.run_versioner(
&[
"audit",
"--no-fetch",
"--fake-crates-io-index",
test_base.test_data.join(index_name).as_str(),
],
expect_failure,
)
}
/// Test that the audit passes when all the runtime crates are at the
/// special `0.0.0-smithy-rs-head` version, indicating not to use
/// independent crate versions.
#[test]
fn all_smithy_rs_head() {
let test_base = TestBase::new("all_smithy_rs_head");
let result = run_audit(&test_base, "base_crates_io_index.toml", false);
assert!(result.stdout.contains("SUCCESS"));
}
/// Changing an independently versioned runtime crate and version bumping
/// it to a version that's never been published before succeeds.
#[test]
fn change_crate_with_bump() {
let test_base = TestBase::new("change_crate_with_bump");
let result = run_audit(&test_base, "base_crates_io_index.toml", false);
assert!(result.stdout.contains("SUCCESS"));
}
/// Changing an independently versioned runtime crate and version bumping
/// it to a version that's been published before (oops!) fails the audit.
#[test]
fn change_crate_with_bump_to_already_published_version() {
let test_base = TestBase::new("change_crate_with_bump");
let result = run_audit(&test_base, "already_published_version.toml", true);
assert!(result.stderr.contains(
"aws-smithy-async was changed and version bumped, \
but the new version number (1.0.1) has already been \
published to crates.io",
));
}
/// Changing an independent runtime crate without version bumping it fails the audit.
#[test]
fn change_crate_without_bump() {
let test_base = TestBase::new("change_crate_without_bump");
let result = run_audit(&test_base, "base_crates_io_index.toml", true);
assert!(result
.stderr
.contains("aws-smithy-async changed since release-2023-10-02 and requires a version bump"));
}
/// Adding a new crate that's never been published before passes audit.
#[test]
fn add_new_crate() {
let test_base = TestBase::new("add_new_crate");
let result = run_audit(&test_base, "base_crates_io_index.toml", false);
assert!(result.stderr.contains("'aws-smithy-newcrate' is a new crate (or wasn't independently versioned before) and will publish at 1.0.0"));
assert!(result.stdout.contains("SUCCESS"));
}
/// Removing an old crate that's been published before passes audit.
#[test]
fn remove_old_crate() {
let test_base = TestBase::new("remove_old_crate");
let result = run_audit(&test_base, "base_crates_io_index.toml", false);
assert!(result
.stderr
.contains("runtime crate 'aws-smithy-http' was removed and will not be published"));
assert!(result.stdout.contains("SUCCESS"));
}

View File

@ -0,0 +1,25 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use test_common::TestBase;
#[test]
fn previous_release_tag() {
let test_base = TestBase::new("all_smithy_rs_head");
assert_eq!(
"release-2023-10-01\n",
test_base
.run_versioner(&["previous-release-tag"], false)
.stdout
);
test_base.change_branch("change_crate_with_bump");
assert_eq!(
"release-2023-10-02\n",
test_base
.run_versioner(&["previous-release-tag"], false)
.stdout
);
}

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -567,7 +567,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -660,18 +660,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -845,7 +845,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -892,6 +892,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror",
"toml",
"tracing",
]
@ -935,9 +936,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.39"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -993,6 +994,26 @@ version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
[[package]]
name = "thiserror"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -1099,7 +1120,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1197,7 +1218,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@ -1231,7 +1252,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -9,7 +9,7 @@ publish = false
[workspace]
[features]
async-shell = ["tokio"]
async = ["tokio"]
[profile.release]
# prefer fast compile time over runtime performance
@ -24,6 +24,7 @@ reqwest = "0.11.10"
semver = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1.0.56"
tokio = { version = "1.20.1", features = ["rt", "macros"], optional = true }
toml = { version = "0.5.8", features = ["preserve_order"] }
tracing = "0.1"

View File

@ -0,0 +1,75 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! Utilities to simplify working with [`std::command::Command`].
/// Synchronous command extension functions.
pub mod sync {
use anyhow::{bail, Context, Result};
use std::process::Command;
/// Extension trait to make `Command` nicer to work with.
pub trait CommandExt {
/// Expects the command to exit with a successful status, and returns the command's stdout.
///
/// If the command fails, the `context` is used to help describe the problem.
/// It will always be formatted with "failed to {context}".
fn expect_success_output(&mut self, context: &str) -> Result<String>;
/// Expects the command to exit with one of the given statuses.
///
/// If the command fails, the `context` is used to help describe the problem.
/// It will always be formatted with "failed to {context}".
fn expect_status_one_of(
&mut self,
context: &str,
statuses: impl IntoIterator<Item = i32>,
) -> Result<i32>;
}
impl CommandExt for Command {
fn expect_success_output(&mut self, context: &str) -> Result<String> {
let output = self
.output()
.with_context(|| format!("failed to invoke {:?}", self))?;
let stdout = String::from_utf8(output.stdout)
.with_context(|| format!("command: {:?}", self))
.context("output had invalid utf-8")?;
if !output.status.success() {
bail!(
"failed to {context}\n\ncommand: {:?}\n\nstdout:\n{}\n\nstderr:\n{}\n",
self,
stdout,
String::from_utf8_lossy(&output.stderr),
);
}
Ok(stdout)
}
fn expect_status_one_of(
&mut self,
context: &str,
statuses: impl IntoIterator<Item = i32>,
) -> Result<i32> {
let output = self
.output()
.with_context(|| format!("failed to invoke {:?}", self))?;
let expected: Vec<_> = statuses.into_iter().collect();
let actual = output.status.code().unwrap();
if expected.contains(&actual) {
Ok(actual)
} else {
bail!(
"failed to {context}\n\n\
expected exit status to be one of {expected:?}, but got {actual}\n\n\
command: {:?}\n\nstdout:\n{}\n\nstderr:\n{}\n",
self,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
)
}
}
}
}

View File

@ -53,7 +53,7 @@ mod tests {
assert_eq!("some-tag", tag);
}
#[cfg(feature = "async-shell")]
#[cfg(feature = "async")]
#[tokio::test]
async fn get_current_tag_success_async() {
let tag = GetCurrentTag {

View File

@ -5,10 +5,12 @@
pub mod changelog;
pub mod ci;
pub mod command;
pub mod git;
#[macro_use]
pub mod macros;
pub mod package;
pub mod release_tag;
pub mod retry;
pub mod shell;
pub mod versions_manifest;

View File

@ -5,7 +5,6 @@
use std::error::Error;
use std::error::Error as StdError;
use std::future::Future;
use std::time::Duration;
use tracing::{error, info};
@ -24,6 +23,7 @@ pub enum RetryError {
FailedMaxAttempts(usize),
}
#[cfg(feature = "async")]
pub async fn run_with_retry<F, Ft, C, O, E>(
what: &str,
max_attempts: usize,
@ -33,7 +33,7 @@ pub async fn run_with_retry<F, Ft, C, O, E>(
) -> Result<O, RetryError>
where
F: Fn() -> Ft,
Ft: Future<Output = Result<O, E>> + Send,
Ft: std::future::Future<Output = Result<O, E>> + Send,
C: Fn(&E) -> ErrorClass,
E: Into<BoxError>,
{
@ -68,6 +68,49 @@ where
}
}
pub fn run_with_retry_sync<F, C, O, E>(
what: &str,
max_attempts: usize,
backoff: Duration,
op: F,
classify_error: C,
) -> Result<O, RetryError>
where
F: Fn() -> Result<O, E>,
C: Fn(&E) -> ErrorClass,
E: Into<BoxError>,
{
assert!(max_attempts > 0);
let mut attempt = 1;
loop {
let result = (op)();
match result {
Ok(output) => return Ok(output),
Err(err) => {
match classify_error(&err) {
ErrorClass::NoRetry => {
return Err(RetryError::FailedUnretryable(err.into()));
}
ErrorClass::Retry => {
info!(
"{} failed on attempt {} with retryable error: {:?}. Will retry after {:?}",
what, attempt, err.into(), backoff
);
}
}
}
}
// If we made it this far, we're retrying or failing at max retries
if attempt == max_attempts {
return Err(RetryError::FailedMaxAttempts(max_attempts));
}
attempt += 1;
std::thread::sleep(backoff);
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -179,4 +222,95 @@ mod tests {
}
assert_eq!(2, attempt.load(Ordering::Relaxed));
}
#[test]
fn fail_max_attempts_sync() {
let attempt = Arc::new(AtomicU8::new(1));
let result = {
let attempt = attempt.clone();
assert_send(run_with_retry_sync(
"test",
3,
Duration::from_millis(0),
{
let attempt = attempt.clone();
move || {
attempt.fetch_add(1, Ordering::Relaxed);
Result::<(), _>::Err(FakeError)
}
},
|_err| ErrorClass::Retry,
))
};
assert!(matches!(result, Err(RetryError::FailedMaxAttempts(3))));
// `attempt` holds the number of the next attempt, so 4 instead of 3 in this case
assert_eq!(4, attempt.load(Ordering::Relaxed));
}
#[test]
fn fail_then_succeed_sync() {
let attempt = Arc::new(AtomicU8::new(1));
let result = {
let attempt = attempt.clone();
run_with_retry_sync(
"test",
3,
Duration::from_millis(0),
{
let attempt = attempt.clone();
move || {
if attempt.fetch_add(1, Ordering::Relaxed) == 1 {
Err(FakeError)
} else {
Ok(2)
}
}
},
|_err| ErrorClass::Retry,
)
};
assert!(matches!(result, Ok(2)));
// `attempt` holds the number of the next attempt, so 3 instead of 2 in this case
assert_eq!(3, attempt.load(Ordering::Relaxed));
}
#[test]
fn unretryable_error_sync() {
let attempt = Arc::new(AtomicU8::new(1));
let result = {
let attempt = attempt.clone();
run_with_retry_sync(
"test",
3,
Duration::from_millis(0),
{
let attempt = attempt.clone();
move || {
if attempt.fetch_add(1, Ordering::Relaxed) == 1 {
Err(UnretryableError)
} else {
Ok(2)
}
}
},
|err| {
if matches!(err, UnretryableError) {
ErrorClass::NoRetry
} else {
ErrorClass::Retry
}
},
)
};
match result {
Err(RetryError::FailedUnretryable(err)) => {
assert!(err.downcast_ref::<UnretryableError>().is_some());
}
_ => panic!("should be an unretryable error"),
}
assert_eq!(2, attempt.load(Ordering::Relaxed));
}
}

View File

@ -3,6 +3,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
//! In general, the `crate::command` module should be preferred over this one going forward.
//!
//! This module was an attempt to make unit testing against command-line tools easier (especially
//! in regards to git), but over time we've been realizing it's easier to just set up fake git
//! repositories to run real commands against, or to mock out individual pieces of functionality
//! where needed rather than individual commands. This module requires a ton of boilerplate for
//! what is providing.
use anyhow::Result;
use async_trait::async_trait;
use std::process::Output;
@ -15,7 +23,7 @@ pub trait ShellOperation {
fn run(&self) -> Result<Self::Output>;
/// Runs the command asynchronously.
#[cfg(feature = "async-shell")]
#[cfg(feature = "async")]
async fn spawn(self) -> Result<Self::Output>
where
Self: Sized + 'static,

View File

@ -55,7 +55,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -66,7 +66,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1012,7 +1012,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1582,7 +1582,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -1782,18 +1782,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -2241,7 +2241,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -2367,6 +2367,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror",
"tokio",
"toml",
"tracing",
@ -2439,9 +2440,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.39"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -2508,22 +2509,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -2607,7 +2608,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -2679,7 +2680,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
]
[[package]]
@ -2863,7 +2864,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@ -2897,7 +2898,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -26,7 +26,7 @@ semver = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha1 = "0.10.1"
smithy-rs-tool-common = { version = "0.1", path = "../../ci-build/smithy-rs-tool-common", features = ["async-shell"] }
smithy-rs-tool-common = { version = "0.1", path = "../../ci-build/smithy-rs-tool-common", features = ["async"] }
tokio = { version = "1.20.1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3.15", features = ["env-filter", "fmt"] }

View File

@ -13,6 +13,9 @@ set -eux
cd smithy-rs
echo -e "# ${C_YELLOW}Auditing runtime crate version numbers...${C_RESET}"
runtime-versioner audit --no-fetch
for runtime_path in \
"rust-runtime" \
"aws/rust-runtime"

View File

@ -26,6 +26,7 @@ test_tool "tools/ci-build/changelogger" "${RUST_STABLE_VERSION}"
test_tool "tools/ci-build/crate-hasher" "${RUST_STABLE_VERSION}"
test_tool "tools/ci-build/difftags" "${RUST_STABLE_VERSION}"
test_tool "tools/ci-build/publisher" "${RUST_STABLE_VERSION}"
test_tool "tools/ci-build/runtime-versioner" "${RUST_STABLE_VERSION}"
test_tool "tools/ci-build/sdk-lints" "${RUST_STABLE_VERSION}"
test_tool "tools/ci-build/sdk-versioner" "${RUST_STABLE_VERSION}"
test_tool "tools/ci-build/smithy-rs-tool-common" "${RUST_STABLE_VERSION}"