diff --git a/Cargo.lock b/Cargo.lock index 5a636d9..eb243b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,6 +514,7 @@ dependencies = [ "semver", "serde", "serde_json", + "toml", "ureq", ] @@ -567,6 +568,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + [[package]] name = "treeline" version = "0.1.0" diff --git a/cli/src/main.rs b/cli/src/main.rs index ca7e5a8..b82cebb 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -38,13 +38,11 @@ fn main() { const ONE_HOUR: u64 = 60 * 60; let tag = get_image_tag(Some(ONE_HOUR)).expect("Issue getting the image tag"); - info!("Using {}:{}", image, tag); - let command = match opts.subcmd { + match opts.subcmd { SubCommand::Pull(_) => { - println!("Found {tag}, we will be using {image}:{tag} for the build", tag = tag, image = image); - format!("docker pull {image}:{tag}", image = image, tag = tag,) + Runner::pull(&image, &tag); } SubCommand::Build(build_opts) => { @@ -57,12 +55,18 @@ fn main() { let runtime_dir = build_opts.runtime_dir.unwrap_or_else(|| PathBuf::from(&default_runtime_dir)); let tmpdir = env::temp_dir().join("cargo"); let digest = get_image_digest(&image, &tag).unwrap_or_default(); - let cache_mount = if !build_opts.no_cache { + let package = build_opts.package; + let profile = build_opts.profile; + let workdir = fs::canonicalize(&build_opts.path).unwrap(); + let _cache_mount = if !build_opts.no_cache { format!("-v {tmpdir}:/cargo-home", tmpdir = tmpdir.display()) } else { String::new() }; + let search_info = RuntimeCrateSearchInfo { workdir: workdir.to_owned(), options: None }; + let _rtm_crate = RuntimeCrate::search(&search_info); + debug!("app: '{}'", &app); debug!("json: '{}'", &json); debug!("chain: '{}'", &chain); @@ -72,58 +76,48 @@ fn main() { debug!("digest: '{}'", &digest); debug!("no-cache: '{}'", build_opts.no_cache); - let path = fs::canonicalize(&build_opts.path).unwrap(); - - format!( - "docker run --name srtool --rm \ - -e PACKAGE={package} \ - -e RUNTIME_DIR={runtime_dir} \ - -e BUILD_OPTS={c_build_opts} \ - -e DEFAULT_FEATURES={default_features} \ - -e PROFILE={profile} \ - -e IMAGE={digest} \ - -v {dir}:/build \ - {cache_mount} \ - {image}:{tag} build{app}{json}", - package = build_opts.package, - dir = path.display(), - cache_mount = cache_mount, - image = image, - tag = tag, - runtime_dir = runtime_dir.display(), - c_build_opts = build_opts.build_opts.unwrap_or_else(|| String::from("")), - default_features = build_opts.default_features.unwrap_or_else(|| String::from("")), - profile = build_opts.profile, - json = json, - app = app, - digest = digest, - ) + let specs = RunSpecs::new(&package, &runtime_dir, &profile, &image, &tag); + let opts = srtool_lib::BuildOpts { json: json == "json", app: app == "app", workdir }; + Runner::build(&specs, &opts); + + // format!( + // "docker run --name srtool --rm \ + // -e PACKAGE={package} \ + // -e RUNTIME_DIR={runtime_dir} \ + // -e BUILD_OPTS={c_build_opts} \ + // -e DEFAULT_FEATURES={default_features} \ + // -e PROFILE={profile} \ + // -e IMAGE={digest} \ + // -v {dir}:/build \ + // {cache_mount} \ + // {image}:{tag} build{app}{json}", + // package = build_opts.package, + // dir = path.display(), + // cache_mount = cache_mount, + // image = image, + // tag = tag, + // runtime_dir = runtime_dir.display(), + // c_build_opts = build_opts.build_opts.unwrap_or_else(|| String::from("")), + // default_features = build_opts.default_features.unwrap_or_else(|| String::from("")), + // profile = build_opts.profile, + // json = json, + // app = app, + // digest = digest, + // ) } SubCommand::Info(info_opts) => { - let path = fs::canonicalize(&info_opts.path).unwrap(); + // let path = fs::canonicalize(&info_opts.path).unwrap(); let chain = info_opts.package.replace("-runtime", ""); let default_runtime_dir = format!("runtime/{}", chain); let runtime_dir = info_opts.runtime_dir.unwrap_or_else(|| PathBuf::from(&default_runtime_dir)); - debug!("chain: '{}'", &chain); - debug!("default_runtime_dir: '{}'", &default_runtime_dir); - debug!("runtime_dir: '{}'", &runtime_dir.display()); - - format!( - "docker run --name srtool --rm \ - -v {dir}:/build \ - -e RUNTIME_DIR={runtime_dir} \ - {image}:{tag} info", - dir = path.display(), - runtime_dir = runtime_dir.display(), - image = image, - tag = tag, - ) + let specs = RunSpecs::new("", &runtime_dir, "release", &image, &tag); + Runner::info(&specs, &runtime_dir); } SubCommand::Version(_) => { - format!("docker run --name srtool --rm {image}:{tag} version", image = image, tag = tag,) + Runner::version(&image, &tag); } SubCommand::Verify(verify_opts) => { @@ -132,22 +126,16 @@ fn main() { let reader = BufReader::new(file); let content: V2 = serde_json::from_reader(reader).unwrap(); let digest_json = json!({ "V2": content }); - // let digest = Digest::from - // debug!("digest = {:#?}", digest); - // let specs = digest.get_run_specs(); - // debug!("specs = {:#?}", specs); + let digest = DigestJson::load(json!(digest_json)).unwrap(); + debug!("digest = {:#?}", digest); + let specs = digest.get_run_specs().unwrap(); + debug!("specs = {:#?}", specs); + let build_opts = srtool_lib::BuildOpts { json: true, app: true, workdir: "/projects/polkadot".into() }; + + Runner::build(&specs, &build_opts); todo!() } }; - - debug!("command = {:?}", command); - - if cfg!(target_os = "windows") { - Command::new("cmd").args(&["/C", command.as_str()]).output().expect("failed to execute process"); - } else { - let _ = - Command::new("sh").arg("-c").arg(command).spawn().expect("failed to execute process").wait_with_output(); - } } #[cfg(test)] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index b03808f..b4c8df0 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,4 +11,5 @@ log = "0.4" semver = {version = "1.0", features = ["serde"]} serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" +toml = "0.5.8" ureq = "2.1" diff --git a/lib/src/digest/digest_json.rs b/lib/src/digest/digest_json.rs new file mode 100644 index 0000000..5a94efc --- /dev/null +++ b/lib/src/digest/digest_json.rs @@ -0,0 +1,46 @@ +use super::digest_source::DigestSource; +use super::versionned_digest::Digest; +use serde_json::Result; +use serde_json::Value; +type Json = Value; + +pub struct DigestJson {} + +impl DigestSource for DigestJson { + fn load(src: Json) -> Result { + let digest: Digest = serde_json::from_str(&src.to_string())?; + Ok(digest) + } +} + +#[cfg(test)] +mod test_super { + use super::*; + use crate::{ + digest::digest_v2, + samples::{SAMPLE_V1, SAMPLE_V2}, + }; + use serde_json::json; + + #[test] + fn test_v1() { + let v1: Value = serde_json::from_str(SAMPLE_V1).unwrap(); + let digest = DigestJson::load(json!({ "V1": v1 })).unwrap(); + + match digest { + Digest::V1(v1) => assert!(v1.src == "git"), + Digest::V2(v2) => assert!(v2.info.src == digest_v2::Source::Git), + } + } + + #[test] + fn test_v2() { + let v2: Value = serde_json::from_str(SAMPLE_V2).unwrap(); + let digest = DigestJson::load(json!({ "V2": v2 })).unwrap(); + + match digest { + Digest::V1(v1) => assert!(v1.src == "git"), + Digest::V2(v2) => assert!(v2.info.src == digest_v2::Source::Git), + } + } +} diff --git a/lib/src/digest_source.rs b/lib/src/digest/digest_source.rs similarity index 100% rename from lib/src/digest_source.rs rename to lib/src/digest/digest_source.rs diff --git a/lib/src/digest_v1.rs b/lib/src/digest/digest_v1.rs similarity index 68% rename from lib/src/digest_v1.rs rename to lib/src/digest/digest_v1.rs index f4c33ce..85d5b4f 100644 --- a/lib/src/digest_v1.rs +++ b/lib/src/digest/digest_v1.rs @@ -1,8 +1,20 @@ use semver::Version; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::fmt::Display; +use std::str::FromStr; //TODO: in V2, in order to NOT break compatibility, some fields are duplicated. That must be reworked. The profile for instance should be in the Context only. +fn from_str<'de, T, D>(deserializer: D) -> Result +where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + T::from_str(&s).map_err(de::Error::custom) +} + /// A srtool digest. The schema of the data srtool produces may /// change over time. This struct can load all version and make /// the common and relevant data available. @@ -18,7 +30,9 @@ pub struct V1 { pub(crate) rustc: String, pub(crate) pkg: String, pub(crate) tmsp: String, - pub(crate) size: uisze, + + #[serde(deserialize_with = "from_str")] + pub(crate) size: usize, pub(crate) prop: String, pub(crate) ipfs: String, pub(crate) sha256: String, diff --git a/lib/src/digest_v2.rs b/lib/src/digest/digest_v2.rs similarity index 87% rename from lib/src/digest_v2.rs rename to lib/src/digest/digest_v2.rs index 1e205f0..6ae9bd6 100644 --- a/lib/src/digest_v2.rs +++ b/lib/src/digest/digest_v2.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use semver::Version; use serde::{Deserialize, Serialize}; @@ -29,25 +31,25 @@ pub struct GitInfo { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Info { /// Information about the tooling used for the build. - generator: Generator, + pub(crate) generator: Generator, /// Whether the build from an Archive or from a Git repo. - src: Source, + pub(crate) src: Source, /// The version of the crate/package to build - version: Version, + pub(crate) version: Version, /// Optionnal Git information if the src was Git - git: Option, + pub(crate) git: Option, /// Rust compiler version - rustc: String, + pub(crate) rustc: String, /// Package - pkg: String, + pub(crate) pkg: String, /// Profile. Always 'release'. - profile: String, + pub(crate) profile: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -62,7 +64,7 @@ pub struct DockerContext { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Context { pub(crate) docker: DockerContext, - pub(crate) runtime_dir: String, + pub(crate) runtime_dir: PathBuf, pub(crate) package: String, pub(crate) profile: String, } @@ -75,6 +77,4 @@ pub struct Context { pub struct V2 { pub(crate) info: Info, pub(crate) context: Context, - #[serde(alias = "src")] - pub(crate) source: Source, } diff --git a/lib/src/digest/mod.rs b/lib/src/digest/mod.rs new file mode 100644 index 0000000..35a5a45 --- /dev/null +++ b/lib/src/digest/mod.rs @@ -0,0 +1,11 @@ +mod digest_json; +mod digest_source; +mod digest_v1; +mod digest_v2; +mod versionned_digest; + +pub use digest_json::*; +pub use digest_source::*; +pub use digest_v1::*; +pub use digest_v2::*; +pub use versionned_digest::*; diff --git a/lib/src/digest.rs b/lib/src/digest/versionned_digest.rs similarity index 77% rename from lib/src/digest.rs rename to lib/src/digest/versionned_digest.rs index 4c16625..836c6cb 100644 --- a/lib/src/digest.rs +++ b/lib/src/digest/versionned_digest.rs @@ -7,21 +7,25 @@ // TODO: The code for the srtool digest needs to be moved under srtool-cargo once published. -use std::str::FromStr; - -use crate::{digest_v2::V2, run_specs::RunSpecs}; +use super::{V1, V2}; +use crate::run_specs::RunSpecs; use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::str::FromStr; #[derive(Debug, PartialEq, Serialize, Deserialize)] pub enum Digest { + V1(V1), V2(V2), } +/// Use a DigestSource such as DigestJson to load a Digest impl Digest { pub fn get_run_specs(&self) -> Result { match self { + // TODO: what we could do for V1 if really needed if to let the user provide the missing information + Digest::V1(_v1) => panic!("Older V1 digests do not contain enough information to generate runspecs"), Digest::V2(v2) => Ok(RunSpecs { package: v2.context.package.to_owned(), runtime_dir: v2.context.runtime_dir.to_owned(), @@ -36,7 +40,7 @@ impl Digest { } } - pub fn get_version(json: Value) -> Option { + fn get_version(json: Value) -> Option { let version_v1 = &json["gen"].as_str().unwrap_or_default().split('v').nth(1); let version_v2 = &json["info"]["generator"]["version"].as_str(); @@ -78,8 +82,10 @@ impl From for Digest { #[cfg(test)] mod test_digest { + use serde_json::json; + use super::*; - use crate::samples::*; + use crate::{samples::*, DigestJson, DigestSource}; #[test] fn test_version_from_json_v1() { @@ -104,4 +110,20 @@ mod test_digest { let v4: Value = serde_json::from_str(SAMPLE_V4).unwrap(); assert_eq!(Digest::get_version(v4), None); } + + #[test] + #[should_panic] + fn test_get_run_specs_v1() { + let v1: Value = serde_json::from_str(SAMPLE_V1).unwrap(); + let digest = DigestJson::load(json!({ "V1": v1 })).unwrap(); + let _rs = digest.get_run_specs(); + } + + #[test] + fn test_get_run_specs_v2() { + let v2: Value = serde_json::from_str(SAMPLE_V2).unwrap(); + let digest = DigestJson::load(json!({ "V2": v2 })).unwrap(); + let rs = digest.get_run_specs(); + println!("rs = {:#?}", rs); + } } diff --git a/lib/src/digest_json.rs b/lib/src/digest_json.rs deleted file mode 100644 index 7bd1d1b..0000000 --- a/lib/src/digest_json.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::{digest::Digest, digest_source::DigestSource}; -use serde_json::Result; -use serde_json::Value; -type Json = Value; - -pub struct DigestJson {} - -impl DigestSource for DigestJson { - fn load(src: Json) -> Result { - let digest: Digest = serde_json::from_str(&src.to_string())?; - Ok(digest) - } -} - -#[cfg(test)] -mod test_super { - use super::*; - use crate::digest_v2; - use serde_json::json; - - #[test] - fn test_() { - let json = json!({ - "V2": { - "context": { - "docker": { - "image": "paritytech/srtool", - "full_tag": "1.53.0-0.9.15", - }, - "runtime_dir": "runtime/polkadot", - "package": "polkadot-runtime", - "profile": "release", - }, - "source": "git", - "info": { - - "generator": { - "name": "srtool", - "version": "0.9.15", - }, - "src": "git", - "version": "0.9.7", - "git": { - "commit": "5d35bac7408a4cb12a578764217d06f3920b36aa", - "tag": "v0.9.7-rc3", - "branch": "heads/v0.9.7-rc3", - }, - "rustc": "rustc 1.53.0 (53cb7b09b 2021-06-17)", - "pkg": "polkadot-runtime", - "profile": "release", - }, - } - }); - let digest = DigestJson::load(json).unwrap(); - - match digest { - Digest::V2(v2) => assert!(v2.source == digest_v2::Source::Git), - } - } -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 445a1e5..47d118c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,16 +1,16 @@ mod digest; -mod digest_json; -mod digest_source; -mod digest_v2; + mod run_specs; +mod runner; +mod runtime_crate; mod rustc_version; mod samples; mod srtool_tag; pub use digest::*; -pub use digest_json::*; -pub use digest_source::*; -pub use digest_v2::*; +pub use run_specs::*; +pub use runner::*; +pub use runtime_crate::*; pub use srtool_tag::*; use log::{debug, info}; diff --git a/lib/src/run_specs.rs b/lib/src/run_specs.rs index c71e8f4..622a208 100644 --- a/lib/src/run_specs.rs +++ b/lib/src/run_specs.rs @@ -2,12 +2,15 @@ //! However, V2 does not contain enough in the Context and has some of the information under //! the `Info` key. So we bring everything together as `RunSpecs`. +use std::path::{Path, PathBuf}; + +#[derive(Debug)] pub struct RunSpecs { /// Name of the crate of the runtime pub(crate) package: String, /// Path to the runtime crate relative to the root of the repository - pub(crate) runtime_dir: String, + pub(crate) runtime_dir: PathBuf, /// Usually `release` pub(crate) profile: String, @@ -23,3 +26,37 @@ pub struct RunSpecs { pub(crate) tag: String, pub(crate) cache_mount: bool, } + +impl RunSpecs { + pub fn new(package: &str, runtime_dir: &Path, profile: &str, image: &str, tag: &str) -> Self { + Self { + package: package.to_string(), + runtime_dir: runtime_dir.to_owned(), + profile: profile.to_string(), + image: image.to_string(), + tag: tag.to_string(), + image_sha256: String::new(), // TODO + cargo_build_opts: Vec::new(), // TODO, + default_features: Vec::new(), // TODO + cache_mount: true, // TODO + } + } +} + +#[cfg(test)] +/// Default is only used as convenience for the tests. +impl Default for RunSpecs { + fn default() -> Self { + Self { + package: "polkadot-runtime".to_string(), + runtime_dir: PathBuf::from("runtime/polkadot"), + profile: "release".to_string(), + image: "paritytech/srtool".to_string(), + image_sha256: String::new(), + cargo_build_opts: vec![], + default_features: vec![], + tag: "1.53.0-0.9.15".to_string(), + cache_mount: true, + } + } +} diff --git a/lib/src/runner.rs b/lib/src/runner.rs new file mode 100644 index 0000000..b6e64e5 --- /dev/null +++ b/lib/src/runner.rs @@ -0,0 +1,153 @@ +//! The runner is effectively a wrapper around docker + +use crate::{get_image_digest, run_specs::RunSpecs}; +use log::debug; +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +pub struct BuildOpts { + pub json: bool, + pub app: bool, + pub workdir: PathBuf, +} + +pub struct Runner; + +impl Runner { + /// Pulls the image + pub fn pull(image: &str, tag: &str) { + debug!("Found {tag}, we will be using {image}:{tag} for the build", tag = tag, image = image); + let cmd = format!("docker pull {image}:{tag}", image = image, tag = tag); + Runner::run(cmd); + } + + /// Invoke the build + pub fn build(specs: &RunSpecs, opts: &BuildOpts) { + println!("Found {tag}, we will be using {image}:{tag} for the build", tag = specs.tag, image = specs.image); + + let app = if opts.app { " --app" } else { "" }; + let json = if opts.json { " --json" } else { "" }; + let chain = specs.package.replace("-runtime", ""); + let default_runtime_dir = format!("runtime/{}", chain); + let runtime_dir = specs.runtime_dir.to_owned(); + let tmpdir = env::temp_dir().join("cargo"); + let digest = get_image_digest(&specs.image, &specs.tag).unwrap_or_default(); + let cache_mount = if specs.cache_mount { + format!("-v {tmpdir}:/cargo-home", tmpdir = tmpdir.display()) + } else { + String::new() + }; + + debug!("app: '{}'", &app); + debug!("json: '{}'", &json); + debug!("chain: '{}'", &chain); + debug!("default_runtime_dir: '{}'", &default_runtime_dir); + debug!("runtime_dir: '{}'", &runtime_dir.display()); + debug!("tmpdir: '{}'", &tmpdir.display()); + debug!("digest: '{}'", &digest); + debug!("cache-mount: '{}'", specs.cache_mount); + + let path = fs::canonicalize(&opts.workdir).unwrap(); + + let cmd = format!( + "docker run --name srtool --rm \ + -e PACKAGE={package} \ + -e RUNTIME_DIR={runtime_dir} \ + -e BUILD_OPTS={c_build_opts} \ + -e DEFAULT_FEATURES={default_features} \ + -e PROFILE={profile} \ + -e IMAGE={digest} \ + -v {dir}:/build \ + {cache_mount} \ + {image}:{tag} build{app}{json}", + package = specs.package, + dir = path.display(), + cache_mount = cache_mount, + image = specs.image, + tag = specs.tag, + runtime_dir = runtime_dir.display(), + c_build_opts = specs.cargo_build_opts.join(" "), + default_features = specs.default_features.join(" "), + profile = specs.profile, + json = json, + app = app, + digest = digest, + ); + Runner::run(cmd); + } + + /// Show infos + pub fn info(specs: &RunSpecs, workdir: &Path) { + // let path = fs::canonicalize(&workdir).unwrap(); + let chain = specs.package.replace("-runtime", ""); + // let default_runtime_dir = format!("runtime/{}", chain); + + debug!("specs: '{:#?}'", &specs); + debug!("chain: '{}'", &chain); + + let cmd = format!( + "docker run --name srtool --rm \ + -v {dir}:/build \ + -e RUNTIME_DIR={runtime_dir} \ + {image}:{tag} info", + dir = workdir.display(), + runtime_dir = specs.runtime_dir.display(), + image = specs.image, + tag = specs.tag, + ); + Runner::run(cmd); + } + + /// Get version + pub fn version(image: &str, tag: &str) { + let cmd = format!("docker run --name srtool --rm {image}:{tag} version", image = image, tag = tag); + Runner::run(cmd); + } + + /// Run the docker command that is passed + fn run(cmd: String) { + if cfg!(target_os = "windows") { + Command::new("cmd").args(&["/C", cmd.as_str()]).output().expect("failed to execute process"); + } else { + let _ = + Command::new("sh").arg("-c").arg(cmd).spawn().expect("failed to execute process").wait_with_output(); + } + } +} + +#[cfg(test)] +mod test_runner { + use super::*; + + #[test] + fn test_pull() { + let specs = RunSpecs::default(); + Runner::pull(&specs.image, &specs.tag); + } + + #[test] + fn test_version() { + let specs = RunSpecs::default(); + Runner::version(&specs.image, &specs.tag); + } + + #[test] + #[ignore = "local data"] + fn test_info() { + let specs = RunSpecs::default(); + let workdir = PathBuf::from("/projects/polkadot"); + Runner::info(&specs, &workdir); + } + + #[test] + #[ignore = "local data + long running"] + fn test_build() { + let specs = RunSpecs::default(); + let workdir = PathBuf::from("/projects/polkadot"); + let opts = BuildOpts { json: true, app: true, workdir }; + Runner::build(&specs, &opts); + } +} diff --git a/lib/src/runtime_crate.rs b/lib/src/runtime_crate.rs new file mode 100644 index 0000000..79114e6 --- /dev/null +++ b/lib/src/runtime_crate.rs @@ -0,0 +1,148 @@ +use std::{fs, path::PathBuf}; +use toml::Value; + +use std::error::Error; + +/// This sctruct holds the information required to know which +/// runtime to build. +#[derive(Debug)] +pub struct RuntimeCrate { + workdir: PathBuf, + runtime_dir: PathBuf, + package: String, + chain: String, +} + +#[derive(Debug)] +pub enum RuntimeCrateSearchOption { + RuntimeDir(String), + Package(String), + ChainName(String), +} + +/// This is the data to provide to search for the runtime. +/// Note that the only reliable way is to pass workdir + runtime_dir. +/// All other options and combinations have more chances to fail. +#[derive(Debug)] +pub struct RuntimeCrateSearchInfo { + pub workdir: PathBuf, + pub options: Option, +} + +impl RuntimeCrate { + /// This function helps find the runtime crate based on *some* information. + /// The result will be Ok if and only if the search critera lead to one single + /// result. In any other cases, it will return an error. + pub fn search(input: &RuntimeCrateSearchInfo) -> Result> { + match &input.options { + Some(opts) => match opts { + // This is the less fuzzy option + RuntimeCrateSearchOption::RuntimeDir(runtime_dir) => { + let runtime_dir = PathBuf::from(runtime_dir); + let cargo_toml = input.workdir.join(&runtime_dir).join("Cargo.toml"); + let toml_content: Value = fs::read_to_string(cargo_toml)?.parse()?; + let package = toml_content["package"]["name"].as_str().expect("Failed getting the package name"); + let chain = package.replace("-runtime", ""); + Ok(RuntimeCrate { + workdir: input.workdir.to_owned(), + runtime_dir, + package: package.to_string(), + chain, + }) + } + RuntimeCrateSearchOption::Package(package) => { + let chain = package.replace("-runtime", ""); + let runtime_dir = PathBuf::from("runtime").join(&chain); + let cargo_toml = input.workdir.join(&runtime_dir).join("Cargo.toml"); + let _: Value = fs::read_to_string(cargo_toml)?.parse()?; + Ok(RuntimeCrate { + workdir: input.workdir.to_owned(), + runtime_dir, + package: package.to_string(), + chain, + }) + } + RuntimeCrateSearchOption::ChainName(chain) => { + let package = format!("{}-runtime", chain); + let runtime_dir = PathBuf::from("runtime").join(&chain); + let cargo_toml = input.workdir.join(&runtime_dir).join("Cargo.toml"); + let _: Value = fs::read_to_string(cargo_toml)?.parse()?; + + Ok(RuntimeCrate { + workdir: input.workdir.to_owned(), + runtime_dir, + package, + chain: chain.to_string(), + }) + } + }, + None => todo!("This feature is not implemented yet, please pass one other search criteria"), + } + } +} + +#[cfg(test)] +mod test_runtime_crate { + use super::*; + + #[test] + #[ignore = "local data"] + #[should_panic] // Not implemented yet, this may end up passing later + fn test_search_workdir_only() { + let _ = RuntimeCrate::search(&RuntimeCrateSearchInfo { workdir: "/projects/polkadot".into(), options: None }); + } + + #[test] + #[ignore = "local data"] + #[should_panic] // Not implemented yet + fn test_search_bad_workdir_only() { + // Should fail for now + let _ = RuntimeCrate::search(&RuntimeCrateSearchInfo { workdir: "/tmp".into(), options: None }); + } + + #[test] + #[ignore = "local data"] + fn test_search_workdir_runtime() { + // The best way + let res = RuntimeCrate::search(&RuntimeCrateSearchInfo { + workdir: "/projects/polkadot".into(), + options: Some(RuntimeCrateSearchOption::RuntimeDir("runtime/polkadot".into())), + }); + assert!(res.is_ok()); + println!("res = {:#?}", res); + } + + #[test] + #[ignore = "local data"] + fn test_search_workdir_bad_runtime_dir() { + // Should fail + let res = RuntimeCrate::search(&RuntimeCrateSearchInfo { + workdir: "/projects/polkadot".into(), + options: Some(RuntimeCrateSearchOption::RuntimeDir("foobar".into())), + }); + assert!(res.is_err()); + println!("res = {:#?}", res); + } + + #[test] + #[ignore = "local data"] + fn test_search_workdir_package() { + let res = RuntimeCrate::search(&RuntimeCrateSearchInfo { + workdir: "/projects/polkadot".into(), + options: Some(RuntimeCrateSearchOption::Package("polkadot-runtime".into())), + }); + assert!(res.is_ok()); + println!("res = {:#?}", res); + } + + #[test] + #[ignore = "local data"] + fn test_search_workdir_chain_name() { + let res = RuntimeCrate::search(&RuntimeCrateSearchInfo { + workdir: "/projects/polkadot".into(), + options: Some(RuntimeCrateSearchOption::ChainName("polkadot".into())), + }); + assert!(res.is_ok()); + println!("res = {:#?}", res); + } +} diff --git a/lib/src/samples.rs b/lib/src/samples.rs index 4d6441c..50c7ff4 100644 --- a/lib/src/samples.rs +++ b/lib/src/samples.rs @@ -120,4 +120,8 @@ pub const SAMPLE_V3: &str = r#"{ }"#; #[cfg(test)] -pub const SAMPLE_V4: &str = r#"{}"#; +pub const SAMPLE_V4: &str = r#"{ + "V4": { + "version": "1.2.3" + } +}"#;