Skip to content

Commit

Permalink
Continue work on publish command.
Browse files Browse the repository at this point in the history
  • Loading branch information
corey committed Mar 17, 2024
1 parent b647ef1 commit 600cc85
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 974 deletions.
1,063 changes: 117 additions & 946 deletions rust/origen/Cargo.lock

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions rust/origen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ md-5 = "0.9"
normpath = "0.2.0"
cfg-if = "1"
wait-timeout = "0.2.0"
futures = {version = "0.3.15", features = ["executor"]}
tokio = {version = "1.29", features = ["full"] }
reqwest = {version = "0.11.3", features = ["blocking", "json"]}

[build-dependencies]
built = "0.5.2"
Expand Down
9 changes: 8 additions & 1 deletion rust/origen/cli/src/commands/develop_origen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ mod fmt;
mod update_supported_python;
mod publish;

use origen::Result;
use origen::{Result, STATUS};
use super::_prelude::*;
pub const BASE_CMD: &'static str = "develop_origen";

use std::path::PathBuf;

lazy_static! {
static ref GH_OWNER: &'static str = "Origen-SDK";
static ref GH_REPO: &'static str = "o2";
static ref PUBLISH_BRANCH: &'static str = "master";
static ref PUBLISH_WORKFLOW: &'static str = "publish.yml";
static ref ORIGEN_OM_REQ_PATH: [&'static str; 4] = ["tool", "poetry", "dependencies", "origen_metal"];
static ref OM_PYPI_PKG_NAME: &'static str = "origen-metal";
static ref ORIGEN_PYPI_PKG_NAME: &'static str = "origen";
static ref OM_PYPROJECT_PATH: PathBuf = STATUS.origen_wksp_root.join("python").join("origen_metal").join("pyproject.toml");
static ref ORIGEN_PYPROJECT_PATH: PathBuf = STATUS.origen_wksp_root.join("python").join("origen").join("pyproject.toml");
static ref CLI_TOML_LOC: PathBuf = STATUS.origen_wksp_root.join("rust").join("origen").join("cli").join("cargo.toml");
}

gen_core_cmd_funcs__no_exts__no_app_opts!(
Expand Down
123 changes: 103 additions & 20 deletions rust/origen/cli/src/commands/develop_origen/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use origen::utility::github::{dispatch_workflow, get_latest_workflow_dispatch, g
use origen_metal::utils::terminal::confirm_with_user;
use origen_metal::utils::revision_control::RevisionControlAPI;
use std::process::exit;
use origen_metal::utils::terminal::{redln, greenln};
use origen_metal::utils::terminal::{redln, greenln, yellowln};
use std::thread;
use std::time::Duration;
use super::{OM_PYPI_PKG_NAME, ORIGEN_PYPI_PKG_NAME};
use origen_metal::utils::pypi::{is_package_version_available, is_package_version_available_on_test_server};

pub const BASE_CMD: &'static str = "publish";

Expand Down Expand Up @@ -58,7 +60,7 @@ pub (crate) fn publish_cmd<'a>() -> SubCmd<'a> {
.arg(
Arg::new("no_pypi_release")
.long("no_pypi_release")
.action(SetArgFalse)
.action(SetArgTrue)
.help("Do NOT release to pypi, even if Origen or OM versions are updated")
)
.arg(
Expand All @@ -80,13 +82,22 @@ pub (crate) fn publish_cmd<'a>() -> SubCmd<'a> {
.arg(
Arg::new("version_update_only")
.long("version_update_only")
.visible_alias("version-update-only")
.visible_alias("versions_only")
.visible_alias("versions-only")
.action(SetArgTrue)
.help("Only updates the version files. Does not check in or launch publishing action.")
.conflicts_with("release_to_pypi_test")
.conflicts_with("no_pypi_release")
.conflicts_with("github_release")
)
.arg(
Arg::new("allow_local_changes")
.long("allow_local_changes")
.alias("allow-local-changes")
.action(SetArgTrue)
.help("Allow publishing even with local changes")
)
}},
core_subcmd__no_exts__no_app_opts!(
"monitor",
Expand Down Expand Up @@ -151,13 +162,16 @@ pub(crate) fn run(invocation: &clap::ArgMatches) -> Result<()> {
greenln("On master branch with attached HEAD");
}

// TODO PublishO2
// Ensure no local changes
let status = git.status(None)?;
if status.is_modified() {
status.summarize();
redln("Changes found in workspace. Please check in your changes or stash them before rerunning");
break 'checks Ok(false);
status.summarize();
if *invocation.get_one::<bool>("allow_local_changes").unwrap() {
yellowln("Allowing releases with local changes...");
} else {
if status.is_modified() {
redln("Changes found in workspace. Please check in your changes or stash them before rerunning");
break 'checks Ok(false);
}
}

// Ensure up-to-date with remote
Expand All @@ -171,13 +185,9 @@ pub(crate) fn run(invocation: &clap::ArgMatches) -> Result<()> {
// TODO PublishO2 Ensure a regression test passed with this commit

// Get current versions
// TODO PublishO2 Cleanup - move paths to shared location
let om_pyproject_path = STATUS.origen_wksp_root.join("python").join("origen_metal").join("pyproject.toml");
let origen_pyproject_path = STATUS.origen_wksp_root.join("python").join("origen").join("pyproject.toml");
let cli_toml_loc = STATUS.origen_wksp_root.join("rust").join("origen").join("cli").join("cargo.toml");
let mut py_om_ver = Version::from_pyproject_with_toml_handle(om_pyproject_path)?;
let mut py_origen_ver = Version::from_pyproject_with_toml_handle(origen_pyproject_path)?;
let mut cli_ver = Version::from_cargo_with_toml_handle(cli_toml_loc)?;
let mut py_om_ver = Version::from_pyproject_with_toml_handle(super::OM_PYPROJECT_PATH.clone())?;
let mut py_origen_ver = Version::from_pyproject_with_toml_handle(super::ORIGEN_PYPROJECT_PATH.clone())?;
let mut cli_ver = Version::from_cargo_with_toml_handle(super::CLI_TOML_LOC.clone())?;

// Extract release types
fn extract_release(invoc: &clap::ArgMatches, cli_name: &str, ver: &mut VersionWithTOML) -> Result<bool> {
Expand Down Expand Up @@ -233,14 +243,84 @@ pub(crate) fn run(invocation: &clap::ArgMatches) -> Result<()> {
if let Some(new) = new_req.as_ref() {
let old_req = py_origen_ver.get_other(&*super::ORIGEN_OM_REQ_PATH)?.to_string();
displayln!("Origen's OM requirement: Updating to '{}' (from '{}')", new, old_req);

// A new Origen version would be pushed, but no Origen version increment was found.
if !release_py_origen {
yellowln("Origen's OM version requirement would be updated but no Origen release was indicated");
if !confirm_with_user(Some("Proceed without an Origen release?"))? {
displayln!("Exiting without sending release request...");
break 'checks Ok(false);
}
}
} else {
displayln!("Origen's OM requirement: No update to Origen's OM minimum version");
}

// Check if the CLI was updated, but no Origen version update.
if !release_py_origen && update_cli {
yellowln("The CLI was updated but no Origen release was indicated");
if !confirm_with_user(Some("Proceed without an Origen release?"))? {
displayln!("Exiting without sending release request...");
break 'checks Ok(false);
}
}

if !confirm_with_user(Some("Proceed with release?"))? {
displayln!("Exiting without sending release request...");
break 'checks Ok(false);
}

// Ensure the proposed versions are available to release
// let release_to_pypi = (!invocation.get_one::<bool>("no_pypi_release").unwrap() || invocation.get_one::<bool>("force_pypi_release").unwrap()) && (update_origen_package || update_om_package);
let release_to_pypi = (!invocation.get_one::<bool>("no_pypi_release").unwrap()) && (release_py_origen || release_py_om);
let release_to_pypi_test = *invocation.get_one::<bool>("release_to_pypi_test").unwrap() && (release_py_origen || release_py_om);
if release_to_pypi && release_py_om {
displayln!("Checking that OM version '{}' is available...", py_om_ver.version());
if !is_package_version_available(*OM_PYPI_PKG_NAME, py_om_ver.version().to_string())? {
redln(&format!(
"Version '{}' is already in use for PyPi package '{}'. Cannot release duplicate version.",
py_om_ver.version(),
*OM_PYPI_PKG_NAME
));
break 'checks Ok(false);
}
greenln(&format!("Version '{}' is available!", py_om_ver.version()));
}
if release_to_pypi_test && release_py_om {
displayln!("Checking that OM version '{}' is available on the test server...", py_om_ver.version());
if !is_package_version_available_on_test_server(*OM_PYPI_PKG_NAME, py_om_ver.version().to_string())? {
redln(&format!(
"Version '{}' is already in use for PyPi test server package '{}'. Cannot release duplicate version.",
py_om_ver.version(),
*OM_PYPI_PKG_NAME
));
}
greenln(&format!("Version '{}' is available on the test server!", py_om_ver.version()));
}
if release_to_pypi && release_py_origen {
displayln!("Checking that Origen version '{}' is available...", py_origen_ver.version());
if !is_package_version_available(*ORIGEN_PYPI_PKG_NAME, py_origen_ver.version().to_string())? {
redln(&format!(
"Version '{}' is already in use for PyPi package '{}'. Cannot release duplicate version.",
py_origen_ver.version(),
*ORIGEN_PYPI_PKG_NAME
));
break 'checks Ok(false);
}
greenln(&format!("Version '{}' is available!", py_origen_ver.version()));
}
if release_to_pypi_test && release_py_origen {
displayln!("Checking that Origen version '{}' is available on the test server...", py_origen_ver.version());
if !is_package_version_available_on_test_server(*ORIGEN_PYPI_PKG_NAME, py_origen_ver.version().to_string())? {
redln(&format!(
"Version '{}' is already in use for PyPi test server package '{}'. Cannot release duplicate version.",
py_origen_ver.version(),
*ORIGEN_PYPI_PKG_NAME
));
}
greenln(&format!("Version '{}' is available on the test server!", py_origen_ver.version()));
}

// Update the TOMLs
// Make sure all versions updated successfully before any checking in
fn update_toml(should_update: bool, ver: &mut VersionWithTOML) -> Result<()> {
Expand All @@ -256,6 +336,7 @@ pub(crate) fn run(invocation: &clap::ArgMatches) -> Result<()> {
py_origen_ver.set_other(&*super::ORIGEN_OM_REQ_PATH, new)?;
}
update_toml(update_origen_package, &mut py_origen_ver)?;
update_toml(update_cli, &mut cli_ver)?;

if *invocation.get_one::<bool>("version_update_only").unwrap() {
displayln!("Stopping release after version update...");
Expand Down Expand Up @@ -286,22 +367,24 @@ pub(crate) fn run(invocation: &clap::ArgMatches) -> Result<()> {

// Send Github actions request to build and release
let mut inputs = indexmap::IndexMap::new();
inputs.insert("origen_metal_python_package", update_om_package.to_string());
inputs.insert("origen_python_package", update_origen_package.to_string());
inputs.insert("publish_pypi", (!invocation.get_one::<bool>("no_pypi_release").unwrap() && (update_origen_package || update_om_package)).to_string());
inputs.insert("origen_metal_python_package", release_py_om.to_string());
inputs.insert("origen_python_package", release_py_origen.to_string());
inputs.insert("publish_pypi", release_to_pypi.to_string());
inputs.insert("publish_pypi_test", invocation.get_one::<bool>("release_to_pypi_test").unwrap().to_string());
inputs.insert("publish_github_release", invocation.get_one::<bool>("github_release").unwrap().to_string());
displayln!("Sending request to GitHub Actions to build and release...");
displayln!("Request GitHub Actions to build and release with...");
for (k, v) in &inputs {
displayln!(" {}: {}", k, v);
}
if !confirm_with_user(Some("Send publish workflow request?"))? {
displayln!("Exiting without sending release request...");
break 'checks Ok(false);
}
dispatch_workflow(*super::GH_OWNER, *super::GH_REPO, *super::PUBLISH_WORKFLOW, *super::PUBLISH_BRANCH, Some(inputs))?;

Ok::<bool, origen_metal::Error>(true)
} {
Ok(publishing) => {
redln("Automation is incomplete");

// Unlock branch
unlock_publish_branch(true)?;
if publishing {
Expand Down
2 changes: 1 addition & 1 deletion rust/origen/src/utility/github.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use origen_metal::{Result, Outcome, octocrab};
use origen_metal::{Result, Outcome, octocrab, futures};
use std::collections::HashMap;

pub fn with_blocking_calls<F, V>(mut f: F) -> Result<V>
Expand Down
1 change: 1 addition & 0 deletions rust/origen_metal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ num-traits = "0.2.15"
serde_json = "1.0"
octocrab = "0.28.0"
reqwest = {version = "0.11.3", features = ["blocking", "json"]}
futures = {version = "0.3.15", features = ["executor"]}
lettre = {version = "=0.10.4", features = ["builder"]}
aes-gcm = "0.8"
semver = "1.0.1"
Expand Down
3 changes: 3 additions & 0 deletions rust/origen_metal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub extern crate octocrab;
pub extern crate tera;
pub extern crate toml_edit;
pub extern crate dialoguer;
pub extern crate reqwest;
pub extern crate futures;
pub extern crate serde_json;
#[macro_use]
extern crate serde;
#[macro_use]
Expand Down
3 changes: 2 additions & 1 deletion rust/origen_metal/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ pub mod outcome;
pub mod revision_control;
pub mod terminal;
pub mod mailer;
pub mod version;
pub mod version;
pub mod pypi;
48 changes: 48 additions & 0 deletions rust/origen_metal/src/utils/pypi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// Wrappers around the PyPi JSON API instead of pip
/// https://warehouse.pypa.io/api-reference/json.html
use crate::Result;

lazy_static! {
pub static ref PYPI_URL_BASE: &'static str = "https://pypi.org";
pub static ref PYPI_TEST_URL_BASE: &'static str = "https://test.pypi.org";
}

pub fn get_package_details<S: AsRef<str>>(package_name: S) -> Result<serde_json::Value> {
get_package_details_from(package_name, *PYPI_URL_BASE)
}

pub fn get_package_details_from_test_server<S: AsRef<str>>(package_name: S) -> Result<serde_json::Value> {
get_package_details_from(package_name, *PYPI_TEST_URL_BASE)
}

pub fn get_package_details_from<S1: AsRef<str>, S2: AsRef<str>>(package_name: S1, url: S2) -> Result<serde_json::Value>
{
let url = format!("{}/pypi/{}/json", url.as_ref(), package_name.as_ref());
let response = reqwest::blocking::get(&url)?;
Ok(response.json::<serde_json::Value>()?)
}

pub fn get_package_versions<S: AsRef<str>>(package_name: S) -> Result<Vec<String>> {
get_package_versions_from(package_name, *PYPI_URL_BASE)
}

pub fn get_package_versions_from_test_server<S: AsRef<str>>(package_name: S) -> Result<Vec<String>> {
get_package_versions_from(package_name, *PYPI_TEST_URL_BASE)
}

pub fn get_package_versions_from<S1: AsRef<str>, S2: AsRef<str>>(package_name: S1, url: S2) -> Result<Vec<String>> {
let res = get_package_details_from(package_name.as_ref(), url)?;
let versions = res["releases"].as_object().unwrap().keys().map( |k| k.to_string()).collect::<Vec<String>>();
Ok(versions)
}

pub fn is_package_version_available<S1: AsRef<str>, S2: AsRef<str>>(package_name: S1, version: S2) -> Result<bool> {
let pkgs = get_package_versions(package_name)?;
Ok(pkgs.iter().find(|v| v == &version.as_ref()).is_none())
}

pub fn is_package_version_available_on_test_server<S1: AsRef<str>, S2: AsRef<str>>(package_name: S1, version: S2) -> Result<bool> {
let pkgs = get_package_versions_from_test_server(package_name)?;
Ok(pkgs.iter().find(|v| v == &version.as_ref()).is_none())
}
30 changes: 29 additions & 1 deletion rust/origen_metal/src/utils/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ impl Version {
Self::new(ver, VersionSpec::Pep440)
}

// TODO: Tests
pub fn convert_to_pep440(&mut self) {
self.spec = VersionSpec::Pep440;
}

// TODO: Tests
pub fn convert_to_semver(&mut self) {
self.spec = VersionSpec::Semver;
}

fn split_prerelease(pre: &str) -> Result<(&str, usize)> {
match pre.find(|c: char| c.is_digit(10)) {
Some(i) => {
Expand Down Expand Up @@ -376,7 +386,9 @@ impl Version {
}

pub fn from_cargo_with_toml_handle(cargo_toml: PathBuf) -> Result<VersionWithTOML> {
VersionWithTOML::new(cargo_toml, &*CARGO_PATH)
let mut ver = VersionWithTOML::new(cargo_toml, &*CARGO_PATH)?;
ver.convert_to_semver();
Ok(ver)
}

pub fn to_pep440(&self) -> Result<Self> {
Expand Down Expand Up @@ -571,6 +583,22 @@ impl VersionWithTOML {
&self.source
}

// TODO: Tests
pub fn convert_to_pep440(&mut self) {
self.orig_version.convert_to_pep440();
if let Some(v) = &mut self.new_version {
v.convert_to_pep440();
}
}

// TODO: Tests
pub fn convert_to_semver(&mut self) {
self.orig_version.convert_to_semver();
if let Some(v) = &mut self.new_version {
v.convert_to_semver();
}
}

pub fn version(&self) -> &Version {
if let Some(v) = self.new_version.as_ref() {
v
Expand Down
3 changes: 1 addition & 2 deletions rust/pyapi/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 600cc85

Please sign in to comment.