Skip to content

Commit c5d6c8d

Browse files
authored
Implement initial tool for publishing to crates.io (#251)
* Implement initial tool for publishing to crates.io * CR feedback * Fix clippy lints * Add tools to CI * Only run SDK CI when changing the SDK * Revert "Only run SDK CI when changing the SDK"
1 parent acfa8b1 commit c5d6c8d

File tree

13 files changed

+1164
-1
lines changed

13 files changed

+1164
-1
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
on: [ pull_request ]
22

33
env:
4-
rust_version: 1.52.1
4+
rust_version: 1.53.0
55

66
name: CI
77

.github/workflows/tool-ci.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
on:
2+
pull_request:
3+
paths: 'tools/**'
4+
5+
env:
6+
rust_version: 1.53.0
7+
rust_toolchain_components: clippy,rustfmt
8+
9+
name: Tools CI
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
name: Compile, Test, and Lint the `tools/` path
15+
steps:
16+
- uses: actions/checkout@v2
17+
- uses: actions/cache@v2
18+
name: Cargo Cache
19+
with:
20+
path: |
21+
~/.cargo/registry
22+
~/.cargo/git
23+
tools/publisher/target
24+
key: tools-${{ runner.os }}-cargo-${{ hashFiles('tools/**/Cargo.toml') }}
25+
restore-keys: |
26+
tools-${{ runner.os }}-cargo-
27+
- uses: actions-rs/toolchain@v1
28+
with:
29+
toolchain: ${{ env.rust_version }}
30+
components: ${{ env.rust_toolchain_components }}
31+
default: true
32+
- name: Format Check
33+
run: rustfmt --check --edition 2018 $(find tools -name '*.rs' -print | grep -v /target/)
34+
- name: Cargo Test
35+
run: cargo test
36+
working-directory: tools/publisher
37+
- name: Cargo Clippy
38+
run: cargo clippy -- -D warnings
39+
working-directory: tools/publisher

tools/publisher/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "publisher"
3+
version = "0.1.0"
4+
authors = ["AWS Rust SDK Team <[email protected]>"]
5+
description = "Tool used to publish the AWS SDK to crates.io"
6+
edition = "2018"
7+
license = "Apache-2.0"
8+
publish = false
9+
10+
[dependencies]
11+
anyhow = "1.0"
12+
cargo_toml = "0.10.1"
13+
clap = "2.33"
14+
dialoguer = "0.8"
15+
num_cpus = "1.13"
16+
semver = "1.0"
17+
thiserror = "1.0"
18+
tokio = { version = "1.12", features = ["full"] }
19+
toml = "0.5.8"
20+
tracing = "0.1.29"
21+
tracing-subscriber = "0.2.25"

tools/publisher/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a tool that the SDK developer team uses to publish the AWS SDK to crates.io.

tools/publisher/src/cargo.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
//! Module for interacting with Cargo.
7+
8+
use anyhow::{Context, Result};
9+
use std::path::{Path, PathBuf};
10+
use std::process::{Command, Output};
11+
12+
macro_rules! cmd {
13+
[ $( $x:expr ),* ] => {
14+
{
15+
let mut cmd = Cmd::new();
16+
$(cmd.push($x);)*
17+
cmd
18+
}
19+
};
20+
}
21+
22+
/// Confirms that cargo exists on the path.
23+
pub async fn confirm_installed_on_path() -> Result<()> {
24+
cmd!["cargo", "--version"]
25+
.spawn()
26+
.await
27+
.context("cargo is not installed on the PATH")?;
28+
Ok(())
29+
}
30+
31+
/// Returns a `Cmd` that, when spawned, will asynchronously run `cargo publish` in the given crate path.
32+
pub fn publish_task(crate_path: &Path) -> Cmd {
33+
cmd!["cargo", "publish"].working_dir(crate_path)
34+
}
35+
36+
#[derive(Default)]
37+
pub struct Cmd {
38+
parts: Vec<String>,
39+
working_dir: Option<PathBuf>,
40+
}
41+
42+
impl Cmd {
43+
fn new() -> Cmd {
44+
Default::default()
45+
}
46+
47+
fn push(&mut self, part: impl Into<String>) {
48+
self.parts.push(part.into());
49+
}
50+
51+
fn working_dir(mut self, working_dir: impl AsRef<Path>) -> Self {
52+
self.working_dir = Some(working_dir.as_ref().into());
53+
self
54+
}
55+
56+
/// Returns a plan string that can be output to the user to describe the command.
57+
pub fn plan(&self) -> String {
58+
let mut plan = String::new();
59+
if let Some(working_dir) = &self.working_dir {
60+
plan.push_str(&format!("[in {:?}]: ", working_dir));
61+
}
62+
plan.push_str(&self.parts.join(" "));
63+
plan
64+
}
65+
66+
/// Runs the command asynchronously.
67+
pub async fn spawn(mut self) -> Result<Output> {
68+
let working_dir = self
69+
.working_dir
70+
.take()
71+
.unwrap_or_else(|| std::env::current_dir().unwrap());
72+
let mut command: Command = self.into();
73+
tokio::task::spawn_blocking(move || Ok(command.current_dir(working_dir).output()?)).await?
74+
}
75+
}
76+
77+
impl From<Cmd> for Command {
78+
fn from(cmd: Cmd) -> Self {
79+
assert!(!cmd.parts.is_empty());
80+
let mut command = Command::new(&cmd.parts[0]);
81+
for i in 1..cmd.parts.len() {
82+
command.arg(&cmd.parts[i]);
83+
}
84+
command
85+
}
86+
}

tools/publisher/src/fs.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
use anyhow::{Context, Result};
7+
use std::path::Path;
8+
use tokio::fs::File;
9+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
10+
11+
/// Abstraction of the filesystem to allow for more tests to be added in the future.
12+
#[derive(Clone, Debug)]
13+
pub enum Fs {
14+
Real,
15+
}
16+
17+
impl Fs {
18+
/// Reads entire file into `Vec<u8>`
19+
pub async fn read_file(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
20+
match self {
21+
Fs::Real => tokio_read_file(path.as_ref()).await,
22+
}
23+
}
24+
25+
/// Writes an entire file from a `&[u8]`
26+
pub async fn write_file(&self, path: impl AsRef<Path>, contents: &[u8]) -> Result<()> {
27+
match self {
28+
Fs::Real => tokio_write_file(path.as_ref(), contents).await,
29+
}
30+
}
31+
}
32+
33+
async fn tokio_read_file(path: &Path) -> Result<Vec<u8>> {
34+
let mut contents = Vec::new();
35+
let mut file = File::open(path)
36+
.await
37+
.with_context(|| format!("failed to open {:?}", path))?;
38+
file.read_to_end(&mut contents)
39+
.await
40+
.with_context(|| format!("failed to read {:?}", path))?;
41+
Ok(contents)
42+
}
43+
44+
async fn tokio_write_file(path: &Path, contents: &[u8]) -> Result<()> {
45+
let mut file = File::create(path)
46+
.await
47+
.with_context(|| format!("failed to create {:?}", path))?;
48+
file.write_all(contents)
49+
.await
50+
.with_context(|| format!("failed to write {:?}", path))?;
51+
Ok(())
52+
}

tools/publisher/src/main.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
use crate::subcommand::fix_manifests::subcommand_fix_manifests;
7+
use crate::subcommand::publish::subcommand_publish;
8+
use anyhow::Result;
9+
use clap::{crate_authors, crate_description, crate_name, crate_version};
10+
11+
mod cargo;
12+
mod fs;
13+
mod package;
14+
mod repo;
15+
mod sort;
16+
mod subcommand;
17+
18+
pub const REPO_NAME: &str = "aws-sdk-rust";
19+
pub const REPO_CRATE_PATH: &str = "sdk";
20+
21+
#[tokio::main]
22+
async fn main() -> Result<()> {
23+
tracing_subscriber::fmt()
24+
.with_env_filter(
25+
std::env::var("RUST_LOG").unwrap_or_else(|_| "error,publisher=info".to_owned()),
26+
)
27+
.init();
28+
29+
let matches = clap_app().get_matches();
30+
if let Some(_matches) = matches.subcommand_matches("publish") {
31+
subcommand_publish().await?;
32+
} else if let Some(_matches) = matches.subcommand_matches("fix-manifests") {
33+
subcommand_fix_manifests().await?;
34+
} else {
35+
clap_app().print_long_help().unwrap();
36+
}
37+
Ok(())
38+
}
39+
40+
fn clap_app() -> clap::App<'static, 'static> {
41+
clap::App::new(crate_name!())
42+
.version(crate_version!())
43+
.author(crate_authors!())
44+
.about(crate_description!())
45+
// In the future, there may be another subcommand for yanking
46+
.subcommand(
47+
clap::SubCommand::with_name("fix-manifests")
48+
.about("fixes path dependencies in manifests to also have version numbers"),
49+
)
50+
.subcommand(
51+
clap::SubCommand::with_name("publish").about("publishes the AWS SDK to crates.io"),
52+
)
53+
}

0 commit comments

Comments
 (0)