From 910e0434b6ae9a6dbcc79e58f7fda2dea5fa7c46 Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Sun, 25 Feb 2024 15:45:33 -0500 Subject: [PATCH] refactor: Move templates to their own crate (#83) This PR logically separates out parts of the code to their own crates. This will be useful for future Tauri App development. --- Cargo.lock | 120 +++++----- Cargo.toml | 82 +++++-- Earthfile | 2 +- recipe/Cargo.toml | 26 +++ {src => recipe/src}/akmods_info.rs | 0 recipe/src/image_inspection.rs | 23 ++ recipe/src/lib.rs | 11 + recipe/src/module.rs | 133 +++++++++++ recipe/src/module_ext.rs | 54 +++++ src/module_recipe.rs => recipe/src/recipe.rs | 214 +----------------- src/commands.rs | 5 +- src/commands/bug_report.rs | 56 +---- src/commands/build.rs | 49 ++-- src/commands/build/build_strategy.rs | 7 +- src/commands/local.rs | 11 +- src/commands/template.rs | 96 +------- src/lib.rs | 13 -- template/Cargo.toml | 25 ++ template/src/lib.rs | 137 +++++++++++ .../templates}/Containerfile.j2 | 2 +- {templates => template/templates}/export.sh | 0 .../templates}/github_issue.j2 | 0 .../templates}/modules/akmods/akmods.j2 | 0 .../modules/containerfile/README.md | 0 .../modules/containerfile/containerfile.j2 | 0 .../modules/containerfile/module.yml | 0 .../templates}/modules/files/README.md | 0 .../templates}/modules/files/files.j2 | 0 .../templates}/modules/files/module.yml | 0 .../templates}/modules/modules.j2 | 0 utils/Cargo.toml | 25 ++ .../utils.rs => utils/src/command_output.rs | 28 +-- {src => utils/src}/constants.rs | 0 src/ops.rs => utils/src/lib.rs | 13 +- 34 files changed, 620 insertions(+), 512 deletions(-) create mode 100644 recipe/Cargo.toml rename {src => recipe/src}/akmods_info.rs (100%) create mode 100644 recipe/src/image_inspection.rs create mode 100644 recipe/src/lib.rs create mode 100644 recipe/src/module.rs create mode 100644 recipe/src/module_ext.rs rename src/module_recipe.rs => recipe/src/recipe.rs (50%) create mode 100644 template/Cargo.toml create mode 100644 template/src/lib.rs rename {templates => template/templates}/Containerfile.j2 (96%) rename {templates => template/templates}/export.sh (100%) rename {templates => template/templates}/github_issue.j2 (100%) rename {templates => template/templates}/modules/akmods/akmods.j2 (100%) rename {templates => template/templates}/modules/containerfile/README.md (100%) rename {templates => template/templates}/modules/containerfile/containerfile.j2 (100%) rename {templates => template/templates}/modules/containerfile/module.yml (100%) rename {templates => template/templates}/modules/files/README.md (100%) rename {templates => template/templates}/modules/files/files.j2 (100%) rename {templates => template/templates}/modules/files/module.yml (100%) rename {templates => template/templates}/modules/modules.j2 (100%) create mode 100644 utils/Cargo.toml rename src/commands/utils.rs => utils/src/command_output.rs (83%) rename {src => utils/src}/constants.rs (100%) rename src/ops.rs => utils/src/lib.rs (86%) diff --git a/Cargo.lock b/Cargo.lock index 1e3818d..42ac859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,9 +130,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "askama" @@ -147,7 +147,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "serde_yaml 0.9.31", + "serde_yaml 0.9.32", ] [[package]] @@ -281,31 +281,27 @@ name = "blue-build" version = "0.8.0" dependencies = [ "anyhow", - "askama", - "chrono", + "blue-build-recipe", + "blue-build-template", + "blue-build-utils", "clap", "clap-verbosity-flag", "clap_complete", "clap_complete_nushell", "colorized", - "derive_builder", - "directories", "dunce", "env_logger", - "format_serde_error", "futures-util", "fuzzy-matcher", - "indexmap 2.2.3", "log", "open", "os_info", "podman-api", - "process_control", "requestty", "rusty-hook", "serde", "serde_json", - "serde_yaml 0.9.31", + "serde_yaml 0.9.32", "shadow-rs", "signal-hook", "signal-hook-tokio", @@ -315,6 +311,51 @@ dependencies = [ "urlencoding", "users", "uuid", +] + +[[package]] +name = "blue-build-recipe" +version = "0.8.0" +dependencies = [ + "anyhow", + "blue-build-utils", + "chrono", + "format_serde_error", + "indexmap 2.2.3", + "log", + "serde", + "serde_json", + "serde_yaml 0.9.32", + "typed-builder", +] + +[[package]] +name = "blue-build-template" +version = "0.8.0" +dependencies = [ + "askama", + "blue-build-recipe", + "blue-build-utils", + "log", + "serde", + "serde_json", + "serde_yaml 0.9.32", + "typed-builder", + "uuid", +] + +[[package]] +name = "blue-build-utils" +version = "0.8.0" +dependencies = [ + "anyhow", + "directories", + "format_serde_error", + "log", + "process_control", + "serde", + "serde_json", + "serde_yaml 0.9.32", "which", ] @@ -649,21 +690,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - [[package]] name = "crossterm" version = "0.25.0" @@ -859,37 +885,6 @@ dependencies = [ "serde", ] -[[package]] -name = "derive_builder" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f59169f400d8087f238c5c0c7db6a28af18681717f3b623227d92f397e938c7" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ec317cc3e7ef0928b0ca6e4a634a4d6c001672ae210438cf114a83e56b018d" -dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder_macro" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870368c3fb35b8031abb378861d4460f573b92238ec2152c927a21f77e3e0127" -dependencies = [ - "derive_builder_core", - "syn 1.0.109", -] - [[package]] name = "digest" version = "0.10.7" @@ -2518,14 +2513,13 @@ dependencies = [ [[package]] name = "process_control" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e056a69288d0a211f4c74c48391c6eb86e714fdcb9dc58a9f34302da9c20bf" +checksum = "4d18334c4a4b2770ee894e63cf466d5a9ea449cf29e321101b0b135a747afb6f" dependencies = [ - "crossbeam-channel", "libc", "signal-hook", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3073,9 +3067,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.31" +version = "0.9.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" dependencies = [ "indexmap 2.2.3", "itoa", diff --git a/Cargo.toml b/Cargo.toml index 56c0bbf..7e878d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,51 +1,80 @@ -[package] -name = "blue-build" +[workspace] +members = [ "utils", "recipe","template"] + +[workspace.package] version = "0.8.0" -edition = "2021" description = "A CLI tool built for creating Containerfile templates based on the Ublue Community Project" +edition = "2021" repository = "https://github.com/blue-build/cli" license = "Apache-2.0" categories = ["command-line-utilities"] + +[workspace.dependencies] +anyhow = "1" +format_serde_error = "0.3.0" +log = "0.4" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_yaml = "0.9.30" +typed-builder = "0.18.1" +uuid = { version = "1.7.0", features = ["v4"] } + +[workspace.lints.rust] +unsafe_code = "forbid" + +[workspace.lints.clippy] +correctness = "warn" +suspicious = "warn" +perf = "warn" +style = "warn" +nursery = "warn" + +[package] +name = "blue-build" build = "build.rs" +version.workspace = true +edition.workspace = true +description.workspace = true +repository.workspace = true +license.workspace = true + [dependencies] -anyhow = "1" -askama = { version = "0.12", features = ["serde-json", "serde-yaml"] } -chrono = "0.4" +blue-build-recipe = { path = "./recipe" } +blue-build-template = { path = "./template" } +blue-build-utils = { path = "./utils" } clap = { version = "4", features = ["derive", "cargo", "unicode"] } clap-verbosity-flag = "2" clap_complete = "4" clap_complete_nushell = "4" colorized = "1" -derive_builder = "0.13" -directories = "5" env_logger = "0.11" -format_serde_error = "0.3.0" -futures-util = { version = "0.3", optional = true } fuzzy-matcher = "0.3" -indexmap = { version = "2", features = ["serde"] } -log = "0.4" open = "5" -# update os module config and tests when upgrading os_info -os_info = "3.7" -podman-api = { version = "0.10.0", optional = true } -process_control = { version = "4.0.3", features = ["crossbeam-channel"] } +os_info = "3.7" # update os module config and tests when upgrading os_info requestty = { version = "0.5", features = ["macros", "termion"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -serde_yaml = "0.9.30" +shadow-rs = { version = "0.26" } +urlencoding = "2.1.3" +users = "0.11.0" + +# Optional Dependencies +futures-util = { version = "0.3", optional = true } +podman-api = { version = "0.10.0", optional = true } signal-hook = { version = "0.3.17", optional = true } signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3", ], optional = true } -shadow-rs = { version = "0.26" } sigstore = { version = "0.8.0", optional = true } tokio = { version = "1", features = ["full"], optional = true } -typed-builder = "0.18.1" -urlencoding = "2.1.3" -users = "0.11.0" -uuid = { version = "1.7.0", features = ["v4"] } -which = "6" + +# Workspace dependencies +anyhow.workspace = true +log.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_yaml.workspace = true +typed-builder.workspace = true +uuid.workspace = true [features] default = [] @@ -66,6 +95,9 @@ rusty-hook = "0.11.2" shadow-rs = { version = "0.26.1", default-features = false } dunce = "1.0.4" +[lints] +workspace = true + [profile.release] lto = true codegen-units = 1 diff --git a/Earthfile b/Earthfile index c4766e7..0cbb800 100644 --- a/Earthfile +++ b/Earthfile @@ -54,7 +54,7 @@ common: FROM ghcr.io/blue-build/earthly-lib/cargo-builder WORKDIR /app - COPY --keep-ts --dir src/ templates/ /app + COPY --keep-ts --dir src/ template/ recipe/ utils/ /app COPY --keep-ts Cargo.* /app COPY --keep-ts *.md /app COPY --keep-ts LICENSE /app diff --git a/recipe/Cargo.toml b/recipe/Cargo.toml new file mode 100644 index 0000000..0edfc5d --- /dev/null +++ b/recipe/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "blue-build-recipe" +version.workspace = true +edition.workspace = true +description.workspace = true +repository.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +blue-build-utils = { path = "../utils" } +chrono = "0.4" +indexmap = { version = "2", features = ["serde"] } + +anyhow.workspace = true +format_serde_error.workspace = true +log.workspace = true +serde.workspace = true +serde_yaml.workspace = true +serde_json.workspace = true +typed-builder.workspace = true + +[lints] +workspace = true + diff --git a/src/akmods_info.rs b/recipe/src/akmods_info.rs similarity index 100% rename from src/akmods_info.rs rename to recipe/src/akmods_info.rs diff --git a/recipe/src/image_inspection.rs b/recipe/src/image_inspection.rs new file mode 100644 index 0000000..d7323ff --- /dev/null +++ b/recipe/src/image_inspection.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; +use serde_json::Value; +use std::collections::HashMap; + +#[derive(Deserialize, Debug, Clone)] +pub struct ImageInspection { + #[serde(alias = "Labels")] + labels: HashMap, +} + +impl ImageInspection { + pub fn get_version(&self) -> Option { + Some( + self.labels + .get("org.opencontainers.image.version")? + .as_str() + .map(std::string::ToString::to_string)? + .split('.') + .take(1) + .collect(), + ) + } +} diff --git a/recipe/src/lib.rs b/recipe/src/lib.rs new file mode 100644 index 0000000..e429674 --- /dev/null +++ b/recipe/src/lib.rs @@ -0,0 +1,11 @@ +pub mod akmods_info; +pub mod image_inspection; +pub mod module; +pub mod module_ext; +pub mod recipe; + +pub use akmods_info::*; +pub use image_inspection::*; +pub use module::*; +pub use module_ext::*; +pub use recipe::*; diff --git a/recipe/src/module.rs b/recipe/src/module.rs new file mode 100644 index 0000000..283286c --- /dev/null +++ b/recipe/src/module.rs @@ -0,0 +1,133 @@ +use std::{borrow::Cow, process}; + +use indexmap::IndexMap; +use log::{error, trace}; +use serde::{Deserialize, Serialize}; +use serde_yaml::Value; +use typed_builder::TypedBuilder; + +use crate::{AkmodsInfo, ModuleExt}; + +#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder)] +pub struct Module<'a> { + #[builder(default, setter(into, strip_option))] + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub module_type: Option>, + + #[builder(default, setter(into, strip_option))] + #[serde(rename = "from-file", skip_serializing_if = "Option::is_none")] + pub from_file: Option>, + + #[builder(default, setter(into, strip_option))] + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option>, + + #[serde(flatten)] + #[builder(default, setter(into))] + pub config: IndexMap, +} + +impl<'a> Module<'a> { + #[must_use] + pub fn get_modules(modules: &[Self]) -> Vec { + modules + .iter() + .flat_map(|module| { + module.from_file.as_ref().map_or_else( + || vec![module.clone()], + |file_name| match ModuleExt::parse_module_from_file(file_name) { + Err(e) => { + error!("Failed to get module from {file_name}: {e}"); + vec![] + } + Ok(module_ext) => Self::get_modules(&module_ext.modules), + }, + ) + }) + .collect() + } + + #[must_use] + pub fn get_module_type_list(&'a self, typ: &str, list_key: &str) -> Option> { + if self.module_type.as_ref()? == typ { + Some( + self.config + .get(list_key)? + .as_sequence()? + .iter() + .filter_map(|t| Some(t.as_str()?.to_owned())) + .collect(), + ) + } else { + None + } + } + + #[must_use] + pub fn get_containerfile_list(&'a self) -> Option> { + self.get_module_type_list("containerfile", "containerfiles") + } + + #[must_use] + pub fn get_containerfile_snippets(&'a self) -> Option> { + self.get_module_type_list("containerfile", "snippets") + } + + pub fn print_module_context(&'a self) -> String { + serde_json::to_string(self).unwrap_or_else(|e| { + error!("Failed to parse module!!!!!: {e}"); + process::exit(1); + }) + } + + pub fn get_files_list(&'a self) -> Option> { + Some( + self.config + .get("files")? + .as_sequence()? + .iter() + .filter_map(|entry| entry.as_mapping()) + .flatten() + .filter_map(|(src, dest)| { + Some(( + format!("./config/files/{}", src.as_str()?), + dest.as_str()?.to_string(), + )) + }) + .collect(), + ) + } + + pub fn generate_akmods_info(&'a self, os_version: &str) -> AkmodsInfo { + trace!("generate_akmods_base({self:#?}, {os_version})"); + + let base = self + .config + .get("base") + .map(|b| b.as_str().unwrap_or_default()); + let nvidia_version = self + .config + .get("nvidia-version") + .map(|v| v.as_u64().unwrap_or_default()); + + AkmodsInfo::builder() + .images(match (base, nvidia_version) { + (Some(b), Some(nv)) if !b.is_empty() && nv > 0 => ( + format!("akmods:{b}-{os_version}"), + Some(format!("akmods-nvidia:{b}-{os_version}-{nv}")), + ), + (Some(b), _) if !b.is_empty() => (format!("akmods:{b}-{os_version}"), None), + (_, Some(nv)) if nv > 0 => ( + format!("akmods:main-{os_version}"), + Some(format!("akmods-nvidia:main-{os_version}")), + ), + _ => (format!("akmods:main-{os_version}"), None), + }) + .stage_name(format!( + "{}{}", + base.unwrap_or("main"), + nvidia_version.map_or_else(String::default, |nv| format!("-{nv}")) + )) + .build() + } +} diff --git a/recipe/src/module_ext.rs b/recipe/src/module_ext.rs new file mode 100644 index 0000000..53d8496 --- /dev/null +++ b/recipe/src/module_ext.rs @@ -0,0 +1,54 @@ +use std::{borrow::Cow, collections::HashSet, fs, path::PathBuf}; + +use anyhow::Result; +use log::trace; +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; + +use crate::{AkmodsInfo, Module}; + +#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)] +pub struct ModuleExt<'a> { + #[builder(default, setter(into))] + pub modules: Cow<'a, [Module<'a>]>, +} + +impl ModuleExt<'_> { + /// # Parse a module file returning a [`ModuleExt`] + /// + /// # Errors + /// Can return an `anyhow` Error if the file cannot be read or deserialized + /// into a [`ModuleExt`] + pub fn parse_module_from_file(file_name: &str) -> Result { + let file_path = PathBuf::from("config").join(file_name); + let file_path = if file_path.is_absolute() { + file_path + } else { + std::env::current_dir()?.join(file_path) + }; + + let file = fs::read_to_string(file_path)?; + + serde_yaml::from_str::(&file).map_or_else( + |_| -> Result { + let module = serde_yaml::from_str::(&file) + .map_err(blue_build_utils::serde_yaml_err(&file))?; + Ok(Self::builder().modules(vec![module]).build()) + }, + Ok, + ) + } + + pub fn get_akmods_info_list(&self, os_version: &str) -> Vec { + trace!("get_akmods_image_list({self:#?}, {os_version})"); + + let mut seen = HashSet::new(); + + self.modules + .iter() + .filter(|module| module.module_type.as_ref().is_some_and(|t| t == "akmods")) + .map(|module| module.generate_akmods_info(os_version)) + .filter(|image| seen.insert(image.clone())) + .collect() + } +} diff --git a/src/module_recipe.rs b/recipe/src/recipe.rs similarity index 50% rename from src/module_recipe.rs rename to recipe/src/recipe.rs index 2320183..6077405 100644 --- a/src/module_recipe.rs +++ b/recipe/src/recipe.rs @@ -1,26 +1,16 @@ -use std::{ - borrow::Cow, - collections::{HashMap, HashSet}, - env, fs, - path::{Path, PathBuf}, - process::{self, Command}, -}; +use std::{borrow::Cow, env, fs, path::Path, process::Command}; use anyhow::Result; +use blue_build_utils::constants::*; use chrono::Local; use format_serde_error::SerdeError; use indexmap::IndexMap; -use log::{debug, error, info, trace, warn}; +use log::{debug, info, trace, warn}; use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; use serde_yaml::Value; use typed_builder::TypedBuilder; -use crate::{ - akmods_info::AkmodsInfo, - constants::*, - ops::{self, check_command_exists}, -}; +use crate::{ImageInspection, Module, ModuleExt}; #[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)] pub struct Recipe<'a> { @@ -146,8 +136,8 @@ impl<'a> Recipe<'a> { debug!("Recipe contents: {file}"); - let mut recipe = - serde_yaml::from_str::(&file).map_err(ops::serde_yaml_err(&file))?; + let mut recipe = serde_yaml::from_str::(&file) + .map_err(blue_build_utils::serde_yaml_err(&file))?; recipe.modules_ext.modules = Module::get_modules(&recipe.modules_ext.modules).into(); @@ -157,7 +147,7 @@ impl<'a> Recipe<'a> { pub fn get_os_version(&self) -> String { trace!("Recipe::get_os_version()"); - if check_command_exists("skopeo").is_err() { + if blue_build_utils::check_command_exists("skopeo").is_err() { warn!("The 'skopeo' command doesn't exist, falling back to version defined in recipe"); return self.image_version.to_string(); } @@ -205,193 +195,3 @@ impl<'a> Recipe<'a> { }) } } - -#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)] -pub struct ModuleExt<'a> { - #[builder(default, setter(into))] - pub modules: Cow<'a, [Module<'a>]>, -} - -impl ModuleExt<'_> { - /// # Parse a module file returning a [`ModuleExt`] - /// - /// # Errors - /// Can return an `anyhow` Error if the file cannot be read or deserialized - /// into a [`ModuleExt`] - pub fn parse_module_from_file(file_name: &str) -> Result { - let file_path = PathBuf::from("config").join(file_name); - let file_path = if file_path.is_absolute() { - file_path - } else { - std::env::current_dir()?.join(file_path) - }; - - let file = fs::read_to_string(file_path)?; - - serde_yaml::from_str::(&file).map_or_else( - |_| -> Result { - let module = - serde_yaml::from_str::(&file).map_err(ops::serde_yaml_err(&file))?; - Ok(Self::builder().modules(vec![module]).build()) - }, - Ok, - ) - } - - pub fn get_akmods_info_list(&self, os_version: &str) -> Vec { - trace!("get_akmods_image_list({self:#?}, {os_version})"); - - let mut seen = HashSet::new(); - - self.modules - .iter() - .filter(|module| module.module_type.as_ref().is_some_and(|t| t == "akmods")) - .map(|module| module.generate_akmods_info(os_version)) - .filter(|image| seen.insert(image.clone())) - .collect() - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder)] -pub struct Module<'a> { - #[builder(default, setter(into, strip_option))] - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub module_type: Option>, - - #[builder(default, setter(into, strip_option))] - #[serde(rename = "from-file", skip_serializing_if = "Option::is_none")] - pub from_file: Option>, - - #[builder(default, setter(into, strip_option))] - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option>, - - #[serde(flatten)] - #[builder(default, setter(into))] - pub config: IndexMap, -} - -impl<'a> Module<'a> { - #[must_use] - pub fn get_modules(modules: &[Self]) -> Vec { - modules - .iter() - .flat_map(|module| { - module.from_file.as_ref().map_or_else( - || vec![module.clone()], - |file_name| match ModuleExt::parse_module_from_file(file_name) { - Err(e) => { - error!("Failed to get module from {file_name}: {e}"); - vec![] - } - Ok(module_ext) => Self::get_modules(&module_ext.modules), - }, - ) - }) - .collect() - } - - #[must_use] - pub fn get_module_type_list(&'a self, typ: &str, list_key: &str) -> Option> { - if self.module_type.as_ref()? == typ { - Some( - self.config - .get(list_key)? - .as_sequence()? - .iter() - .filter_map(|t| Some(t.as_str()?.to_owned())) - .collect(), - ) - } else { - None - } - } - - #[must_use] - pub fn get_containerfile_list(&'a self) -> Option> { - self.get_module_type_list("containerfile", "containerfiles") - } - - #[must_use] - pub fn get_containerfile_snippets(&'a self) -> Option> { - self.get_module_type_list("containerfile", "snippets") - } - - pub fn print_module_context(&'a self) -> String { - serde_json::to_string(self).unwrap_or_else(|e| { - error!("Failed to parse module!!!!!: {e}"); - process::exit(1); - }) - } - - pub fn get_files_list(&'a self) -> Option> { - Some( - self.config - .get("files")? - .as_sequence()? - .iter() - .filter_map(|entry| entry.as_mapping()) - .flatten() - .filter_map(|(src, dest)| { - Some(( - format!("./config/files/{}", src.as_str()?), - dest.as_str()?.to_string(), - )) - }) - .collect(), - ) - } - - pub fn generate_akmods_info(&'a self, os_version: &str) -> AkmodsInfo { - trace!("generate_akmods_base({self:#?}, {os_version})"); - - let base = self - .config - .get("base") - .map(|b| b.as_str().unwrap_or_default()); - let nvidia_version = self - .config - .get("nvidia-version") - .map(|v| v.as_u64().unwrap_or_default()); - - AkmodsInfo::builder() - .images(match (base, nvidia_version) { - (Some(b), Some(nv)) if !b.is_empty() && nv > 0 => ( - format!("akmods:{b}-{os_version}"), - Some(format!("akmods-nvidia:{b}-{os_version}-{nv}")), - ), - (Some(b), _) if !b.is_empty() => (format!("akmods:{b}-{os_version}"), None), - (_, Some(nv)) if nv > 0 => ( - format!("akmods:main-{os_version}"), - Some(format!("akmods-nvidia:main-{os_version}")), - ), - _ => (format!("akmods:main-{os_version}"), None), - }) - .stage_name(format!( - "{}{}", - base.unwrap_or("main"), - nvidia_version.map_or_else(String::default, |nv| format!("-{nv}")) - )) - .build() - } -} - -#[derive(Deserialize, Debug, Clone)] -struct ImageInspection { - #[serde(alias = "Labels")] - labels: HashMap, -} - -impl ImageInspection { - pub fn get_version(&self) -> Option { - Some( - self.labels - .get("org.opencontainers.image.version")? - .as_str() - .map(std::string::ToString::to_string)? - .split('.') - .take(1) - .collect(), - ) - } -} diff --git a/src/commands.rs b/src/commands.rs index 6f38402..9289e6e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,6 +3,8 @@ use log::error; use clap::{command, crate_authors, Parser, Subcommand}; use clap_verbosity_flag::{InfoLevel, Verbosity}; +use crate::shadow; + pub mod bug_report; pub mod build; pub mod completions; @@ -10,7 +12,6 @@ pub mod completions; pub mod init; pub mod local; pub mod template; -pub mod utils; pub trait BlueBuildCommand { /// Runs the command and returns a result @@ -29,8 +30,6 @@ pub trait BlueBuildCommand { } } -shadow_rs::shadow!(shadow); - #[derive(Parser, Debug)] #[clap( name = "BlueBuild", diff --git a/src/commands/bug_report.rs b/src/commands/bug_report.rs index d8eed66..be5dcdd 100644 --- a/src/commands/bug_report.rs +++ b/src/commands/bug_report.rs @@ -1,17 +1,17 @@ -use askama::Template; +use blue_build_recipe::Recipe; +use blue_build_template::{GithubIssueTemplate, Template}; +use blue_build_utils::constants::*; use clap::Args; use clap_complete::Shell; use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; use log::{debug, error, trace}; use requestty::question::{completions, Completions}; -use std::borrow::Cow; use std::time::Duration; use typed_builder::TypedBuilder; -use super::utils::exec_cmd; use super::BlueBuildCommand; -use crate::{constants::*, module_recipe::Recipe, shadow}; +use crate::shadow; #[derive(Default, Debug, Clone, TypedBuilder, Args)] pub struct BugReportRecipe { @@ -272,7 +272,7 @@ fn get_shell_version(shell: &str) -> String { error!("Powershell is not supported."); None } - _ => exec_cmd(shell, &["--version"], time_limit), + _ => blue_build_utils::exec_cmd(shell, &["--version"], time_limit), } .map_or_else( || UNKNOWN_VERSION.to_string(), @@ -284,52 +284,6 @@ fn get_shell_version(shell: &str) -> String { // Git // ============================================================================= // -#[derive(Debug, Clone, Template, TypedBuilder)] -#[template(path = "github_issue.j2", escape = "md")] -struct GithubIssueTemplate<'a> { - #[builder(setter(into))] - bb_version: Cow<'a, str>, - - #[builder(setter(into))] - build_rust_channel: Cow<'a, str>, - - #[builder(setter(into))] - build_time: Cow<'a, str>, - - #[builder(setter(into))] - git_commit_hash: Cow<'a, str>, - - #[builder(setter(into))] - os_name: Cow<'a, str>, - - #[builder(setter(into))] - os_version: Cow<'a, str>, - - #[builder(setter(into))] - pkg_branch_tag: Cow<'a, str>, - - #[builder(setter(into))] - recipe: Cow<'a, str>, - - #[builder(setter(into))] - rust_channel: Cow<'a, str>, - - #[builder(setter(into))] - rust_version: Cow<'a, str>, - - #[builder(setter(into))] - shell_name: Cow<'a, str>, - - #[builder(setter(into))] - shell_version: Cow<'a, str>, - - #[builder(setter(into))] - terminal_name: Cow<'a, str>, - - #[builder(setter(into))] - terminal_version: Cow<'a, str>, -} - fn get_pkg_branch_tag() -> String { format!("{} ({})", shadow::BRANCH, shadow::LAST_TAG) } diff --git a/src/commands/build.rs b/src/commands/build.rs index 1e0965c..4c0cd0f 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -8,6 +8,8 @@ use std::{ }; use anyhow::{anyhow, bail, Result}; +use blue_build_recipe::Recipe; +use blue_build_utils::constants::*; use clap::Args; use colorized::{Color, Colors}; use log::{debug, info, trace, warn}; @@ -35,12 +37,7 @@ use tokio::{ sync::oneshot::{self, Sender}, }; -use crate::{ - commands::template::TemplateCommand, - constants::{self, *}, - module_recipe::Recipe, - ops, -}; +use crate::commands::template::TemplateCommand; use super::BlueBuildCommand; @@ -181,19 +178,19 @@ impl BlueBuildCommand for BuildCommand { // -> If it does => *Ask* to add to .gitignore and remove from git // -> If it doesn't => *Ask* to continue and override the file - let container_file_path = Path::new(constants::CONTAINER_FILE); + let container_file_path = Path::new(CONTAINER_FILE); if !self.force && container_file_path.exists() { - let gitignore = fs::read_to_string(constants::GITIGNORE_PATH)?; + let gitignore = fs::read_to_string(GITIGNORE_PATH)?; let is_ignored = gitignore .lines() - .any(|line: &str| line.contains(constants::CONTAINER_FILE)); + .any(|line: &str| line.contains(CONTAINER_FILE)); if !is_ignored { let containerfile = fs::read_to_string(container_file_path)?; let has_label = containerfile.lines().any(|line| { - let label = format!("LABEL {}", constants::BUILD_ID_LABEL); + let label = format!("LABEL {}", BUILD_ID_LABEL); line.to_string().trim().starts_with(&label) }); @@ -211,9 +208,9 @@ impl BlueBuildCommand for BuildCommand { if let Ok(answer) = requestty::prompt_one(question) { if answer.as_bool().unwrap_or(false) { - ops::append_to_file( - constants::GITIGNORE_PATH, - &format!("/{}", constants::CONTAINER_FILE), + blue_build_utils::append_to_file( + GITIGNORE_PATH, + &format!("/{}", CONTAINER_FILE), )?; } } @@ -231,15 +228,15 @@ impl BlueBuildCommand for BuildCommand { .unwrap_or_else(|| PathBuf::from(RECIPE_PATH)); #[cfg(not(feature = "podman-api"))] - if let Err(e1) = ops::check_command_exists("buildah") { - ops::check_command_exists("podman").map_err(|e2| { + if let Err(e1) = blue_build_utils::check_command_exists("buildah") { + blue_build_utils::check_command_exists("podman").map_err(|e2| { anyhow!("Need either 'buildah' or 'podman' commands to proceed: {e1}, {e2}") })?; } if self.push { - ops::check_command_exists("cosign")?; - ops::check_command_exists("skopeo")?; + blue_build_utils::check_command_exists("cosign")?; + blue_build_utils::check_command_exists("skopeo")?; check_cosign_files()?; } @@ -420,8 +417,8 @@ impl BuildCommand { info!("Logging into the registry, {registry}"); let login_output = match ( - ops::check_command_exists("buildah"), - ops::check_command_exists("podman"), + blue_build_utils::check_command_exists("buildah"), + blue_build_utils::check_command_exists("podman"), ) { (Ok(()), _) => { trace!("buildah login -u {username} -p [MASKED] {registry}"); @@ -550,8 +547,8 @@ impl BuildCommand { info!("Building image {full_image}"); let status = match ( - ops::check_command_exists("buildah"), - ops::check_command_exists("podman"), + blue_build_utils::check_command_exists("buildah"), + blue_build_utils::check_command_exists("podman"), ) { (Ok(()), _) => { trace!("buildah build -t {full_image}"); @@ -588,7 +585,7 @@ impl BuildCommand { let retry_count = if retry { self.retry_count } else { 0 }; // Push images with retries (1s delay between retries) - ops::retry(retry_count, 1000, || push_images(tags, image_name))?; + blue_build_utils::retry(retry_count, 1000, || push_images(tags, image_name))?; sign_images(image_name, tags.first().map(String::as_str))?; } @@ -963,8 +960,8 @@ fn tag_images(tags: &[String], image_name: &str, full_image: &str) -> Result<()> let tag_image = format!("{image_name}:{tag}"); let status = match ( - ops::check_command_exists("buildah"), - ops::check_command_exists("podman"), + blue_build_utils::check_command_exists("buildah"), + blue_build_utils::check_command_exists("podman"), ) { (Ok(()), _) => { trace!("buildah tag {full_image} {tag_image}"); @@ -1001,8 +998,8 @@ fn push_images(tags: &[String], image_name: &str) -> Result<()> { let tag_image = format!("{image_name}:{tag}"); let status = match ( - ops::check_command_exists("buildah"), - ops::check_command_exists("podman"), + blue_build_utils::check_command_exists("buildah"), + blue_build_utils::check_command_exists("podman"), ) { (Ok(()), _) => { trace!("buildah push {tag_image}"); diff --git a/src/commands/build/build_strategy.rs b/src/commands/build/build_strategy.rs index bb9bc64..5facf8f 100644 --- a/src/commands/build/build_strategy.rs +++ b/src/commands/build/build_strategy.rs @@ -4,10 +4,9 @@ use std::{ }; use anyhow::{bail, Result}; +use blue_build_utils::constants::*; use log::trace; -use crate::{constants::*, ops}; - #[cfg(feature = "podman-api")] #[derive(Debug, Clone, Default)] pub enum BuildStrategy { @@ -29,8 +28,8 @@ impl BuildStrategy { PathBuf::from(RUN_PODMAN_SOCK), PathBuf::from(VAR_RUN_PODMAN_PODMAN_SOCK), PathBuf::from(VAR_RUN_PODMAN_SOCK), - ops::check_command_exists("buildah"), - ops::check_command_exists("podman"), + blue_build_utils::check_command_exists("buildah"), + blue_build_utils::check_command_exists("podman"), ) { (Ok(xdg_runtime), _, _, _, _, _) if Path::new(&format!("{xdg_runtime}/podman/podman.sock")).exists() => diff --git a/src/commands/local.rs b/src/commands/local.rs index b019afd..328487c 100644 --- a/src/commands/local.rs +++ b/src/commands/local.rs @@ -5,17 +5,14 @@ use std::{ }; use anyhow::{bail, Result}; +use blue_build_recipe::Recipe; +use blue_build_utils::constants::*; use clap::Args; use log::{debug, info, trace}; use typed_builder::TypedBuilder; use users::{Users, UsersCache}; -use crate::{ - commands::build::BuildCommand, - constants::{ARCHIVE_SUFFIX, LOCAL_BUILD}, - module_recipe::Recipe, - ops, -}; +use crate::commands::build::BuildCommand; use super::BlueBuildCommand; @@ -148,7 +145,7 @@ impl BlueBuildCommand for RebaseCommand { fn check_can_run() -> Result<()> { trace!("check_can_run()"); - ops::check_command_exists("rpm-ostree")?; + blue_build_utils::check_command_exists("rpm-ostree")?; let cache = UsersCache::new(); if cache.get_current_uid() != 0 { diff --git a/src/commands/template.rs b/src/commands/template.rs index 8df4b54..4d9fac2 100644 --- a/src/commands/template.rs +++ b/src/commands/template.rs @@ -1,57 +1,16 @@ -use std::{ - env, fs, - path::{Path, PathBuf}, - process, -}; +use std::path::PathBuf; use anyhow::{anyhow, Result}; -use askama::Template; +use blue_build_recipe::Recipe; +use blue_build_template::{ContainerFileTemplate, Template}; +use blue_build_utils::constants::*; use clap::Args; -use log::{debug, error, info, trace}; +use log::{debug, info, trace}; use typed_builder::TypedBuilder; use uuid::Uuid; -use crate::{constants::*, module_recipe::Recipe}; - use super::BlueBuildCommand; -#[derive(Debug, Clone, Template, TypedBuilder)] -#[template(path = "Containerfile.j2", escape = "none")] -pub struct ContainerFileTemplate<'a> { - recipe: &'a Recipe<'a>, - - #[builder(setter(into))] - recipe_path: &'a Path, - - #[builder(setter(into))] - build_id: Uuid, - - #[builder(default)] - export_script: ExportsTemplate, -} - -#[derive(Debug, Clone, Default, Template)] -#[template(path = "export.sh", escape = "none")] -pub struct ExportsTemplate; - -impl ExportsTemplate { - fn print_script(&self) -> String { - trace!("print_script({self})"); - - format!( - "\"{}\"", - self.render() - .unwrap_or_else(|e| { - error!("Failed to render export.sh script: {e}"); - process::exit(1); - }) - .replace('\n', "\\n") - .replace('\"', "\\\"") - .replace('$', "\\$") - ) - } -} - #[derive(Debug, Clone, Args, TypedBuilder)] pub struct TemplateCommand { /// The recipe file to create a template from @@ -127,48 +86,3 @@ impl TemplateCommand { // ======================================================== // // ========================= Helpers ====================== // // ======================================================== // - -fn has_cosign_file() -> bool { - trace!("has_cosign_file()"); - std::env::current_dir() - .map(|p| p.join(COSIGN_PATH).exists()) - .unwrap_or(false) -} - -#[must_use] -fn print_containerfile(containerfile: &str) -> String { - trace!("print_containerfile({containerfile})"); - debug!("Loading containerfile contents for {containerfile}"); - - let path = format!("config/containerfiles/{containerfile}/Containerfile"); - - let file = fs::read_to_string(&path).unwrap_or_else(|e| { - error!("Failed to read file {path}: {e}"); - process::exit(1); - }); - - debug!("Containerfile contents {path}:\n{file}"); - - file -} - -fn get_github_repo_owner() -> Option { - Some(env::var(GITHUB_REPOSITORY_OWNER).ok()?.to_lowercase()) -} - -fn get_gitlab_registry_path() -> Option { - Some( - format!( - "{}/{}/{}", - env::var(CI_REGISTRY).ok()?, - env::var(CI_PROJECT_NAMESPACE).ok()?, - env::var(CI_PROJECT_NAME).ok()?, - ) - .to_lowercase(), - ) -} - -fn modules_exists() -> bool { - let mod_path = Path::new("modules"); - mod_path.exists() && mod_path.is_dir() -} diff --git a/src/lib.rs b/src/lib.rs index b73f5df..f12e4e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,6 @@ //! The root library for blue-build. -#![warn( - clippy::correctness, - clippy::suspicious, - clippy::perf, - clippy::style, - clippy::nursery -)] #![doc = include_str!("../README.md")] -#![forbid(unsafe_code)] -#![allow(clippy::module_name_repetitions)] shadow_rs::shadow!(shadow); -pub mod akmods_info; pub mod commands; -pub mod constants; -pub mod module_recipe; -mod ops; diff --git a/template/Cargo.toml b/template/Cargo.toml new file mode 100644 index 0000000..cdf309b --- /dev/null +++ b/template/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "blue-build-template" +version.workspace = true +edition.workspace = true +description.workspace = true +repository.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +askama = { version = "0.12", features = ["serde-json", "serde-yaml"] } +blue-build-recipe = { path = "../recipe" } +blue-build-utils = { path = "../utils" } + +log.workspace = true +serde.workspace = true +serde_yaml.workspace = true +serde_json.workspace = true +typed-builder.workspace = true +uuid.workspace = true + +[lints] +workspace = true + diff --git a/template/src/lib.rs b/template/src/lib.rs new file mode 100644 index 0000000..d5e2d0e --- /dev/null +++ b/template/src/lib.rs @@ -0,0 +1,137 @@ +use std::{borrow::Cow, env, fs, path::Path, process}; + +use blue_build_recipe::Recipe; +use blue_build_utils::constants::*; +use log::{debug, error, trace}; +use typed_builder::TypedBuilder; +use uuid::Uuid; + +pub use askama::Template; + +#[derive(Debug, Clone, Template, TypedBuilder)] +#[template(path = "Containerfile.j2", escape = "none")] +pub struct ContainerFileTemplate<'a> { + recipe: &'a Recipe<'a>, + + #[builder(setter(into))] + recipe_path: &'a Path, + + #[builder(setter(into))] + build_id: Uuid, + + #[builder(default)] + export_script: ExportsTemplate, +} + +#[derive(Debug, Clone, Default, Template)] +#[template(path = "export.sh", escape = "none")] +pub struct ExportsTemplate; + +impl ExportsTemplate { + fn print_script(&self) -> String { + trace!("print_script({self})"); + + format!( + "\"{}\"", + self.render() + .unwrap_or_else(|e| { + error!("Failed to render export.sh script: {e}"); + process::exit(1); + }) + .replace('\n', "\\n") + .replace('\"', "\\\"") + .replace('$', "\\$") + ) + } +} + +#[derive(Debug, Clone, Template, TypedBuilder)] +#[template(path = "github_issue.j2", escape = "md")] +pub struct GithubIssueTemplate<'a> { + #[builder(setter(into))] + bb_version: Cow<'a, str>, + + #[builder(setter(into))] + build_rust_channel: Cow<'a, str>, + + #[builder(setter(into))] + build_time: Cow<'a, str>, + + #[builder(setter(into))] + git_commit_hash: Cow<'a, str>, + + #[builder(setter(into))] + os_name: Cow<'a, str>, + + #[builder(setter(into))] + os_version: Cow<'a, str>, + + #[builder(setter(into))] + pkg_branch_tag: Cow<'a, str>, + + #[builder(setter(into))] + recipe: Cow<'a, str>, + + #[builder(setter(into))] + rust_channel: Cow<'a, str>, + + #[builder(setter(into))] + rust_version: Cow<'a, str>, + + #[builder(setter(into))] + shell_name: Cow<'a, str>, + + #[builder(setter(into))] + shell_version: Cow<'a, str>, + + #[builder(setter(into))] + terminal_name: Cow<'a, str>, + + #[builder(setter(into))] + terminal_version: Cow<'a, str>, +} + +fn has_cosign_file() -> bool { + trace!("has_cosign_file()"); + std::env::current_dir() + .map(|p| p.join(COSIGN_PATH).exists()) + .unwrap_or(false) +} + +#[must_use] +fn print_containerfile(containerfile: &str) -> String { + trace!("print_containerfile({containerfile})"); + debug!("Loading containerfile contents for {containerfile}"); + + let path = format!("config/containerfiles/{containerfile}/Containerfile"); + + let file = fs::read_to_string(&path).unwrap_or_else(|e| { + error!("Failed to read file {path}: {e}"); + process::exit(1); + }); + + debug!("Containerfile contents {path}:\n{file}"); + + file +} + +fn get_github_repo_owner() -> Option { + Some(env::var(GITHUB_REPOSITORY_OWNER).ok()?.to_lowercase()) +} + +fn get_gitlab_registry_path() -> Option { + Some( + format!( + "{}/{}/{}", + env::var(CI_REGISTRY).ok()?, + env::var(CI_PROJECT_NAMESPACE).ok()?, + env::var(CI_PROJECT_NAME).ok()?, + ) + .to_lowercase(), + ) +} + +fn modules_exists() -> bool { + let mod_path = Path::new("modules"); + mod_path.exists() && mod_path.is_dir() +} diff --git a/templates/Containerfile.j2 b/template/templates/Containerfile.j2 similarity index 96% rename from templates/Containerfile.j2 rename to template/templates/Containerfile.j2 index c2408c8..21f8de8 100644 --- a/templates/Containerfile.j2 +++ b/template/templates/Containerfile.j2 @@ -23,7 +23,7 @@ RUN printf {{ export_script.print_script() }} >> /exports.sh && chmod +x /export FROM {{ recipe.base_image }}:{{ recipe.image_version }} -LABEL {{ crate::constants::BUILD_ID_LABEL }}="{{ build_id }}" +LABEL {{ blue_build_utils::constants::BUILD_ID_LABEL }}="{{ build_id }}" LABEL org.opencontainers.image.title="{{ recipe.name }}" LABEL org.opencontainers.image.description="{{ recipe.description }}" LABEL io.artifacthub.package.readme-url=https://raw.githubusercontent.com/blue-build/cli/main/README.md diff --git a/templates/export.sh b/template/templates/export.sh similarity index 100% rename from templates/export.sh rename to template/templates/export.sh diff --git a/templates/github_issue.j2 b/template/templates/github_issue.j2 similarity index 100% rename from templates/github_issue.j2 rename to template/templates/github_issue.j2 diff --git a/templates/modules/akmods/akmods.j2 b/template/templates/modules/akmods/akmods.j2 similarity index 100% rename from templates/modules/akmods/akmods.j2 rename to template/templates/modules/akmods/akmods.j2 diff --git a/templates/modules/containerfile/README.md b/template/templates/modules/containerfile/README.md similarity index 100% rename from templates/modules/containerfile/README.md rename to template/templates/modules/containerfile/README.md diff --git a/templates/modules/containerfile/containerfile.j2 b/template/templates/modules/containerfile/containerfile.j2 similarity index 100% rename from templates/modules/containerfile/containerfile.j2 rename to template/templates/modules/containerfile/containerfile.j2 diff --git a/templates/modules/containerfile/module.yml b/template/templates/modules/containerfile/module.yml similarity index 100% rename from templates/modules/containerfile/module.yml rename to template/templates/modules/containerfile/module.yml diff --git a/templates/modules/files/README.md b/template/templates/modules/files/README.md similarity index 100% rename from templates/modules/files/README.md rename to template/templates/modules/files/README.md diff --git a/templates/modules/files/files.j2 b/template/templates/modules/files/files.j2 similarity index 100% rename from templates/modules/files/files.j2 rename to template/templates/modules/files/files.j2 diff --git a/templates/modules/files/module.yml b/template/templates/modules/files/module.yml similarity index 100% rename from templates/modules/files/module.yml rename to template/templates/modules/files/module.yml diff --git a/templates/modules/modules.j2 b/template/templates/modules/modules.j2 similarity index 100% rename from templates/modules/modules.j2 rename to template/templates/modules/modules.j2 diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000..bf82609 --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "blue-build-utils" +version.workspace = true +edition.workspace = true +description.workspace = true +repository.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +directories = "5" +process_control = { version = "4.0.3", features = ["crossbeam-channel"] } +which = "6" + +anyhow.workspace = true +format_serde_error.workspace = true +log.workspace = true +serde.workspace = true +serde_yaml.workspace = true +serde_json.workspace = true + +[lints] +workspace = true + diff --git a/src/commands/utils.rs b/utils/src/command_output.rs similarity index 83% rename from src/commands/utils.rs rename to utils/src/command_output.rs index 3e87bb4..cc28192 100644 --- a/src/commands/utils.rs +++ b/utils/src/command_output.rs @@ -1,19 +1,12 @@ +use std::{ + ffi::OsStr, + fmt::Debug, + io::{Error, ErrorKind, Result}, + process::{Command, Stdio}, + time::{Duration, Instant}, +}; + use process_control::{ChildExt, Control}; -use std::ffi::OsStr; -use std::fmt::Debug; -use std::io::{Error, ErrorKind, Result}; -use std::path::PathBuf; -use std::process::{Command, Stdio}; -use std::time::{Duration, Instant}; - -#[must_use] -pub fn home_dir() -> Option { - directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf()) -} - -// ================================================================================================= // -// CommandOutput -// ================================================================================================= // #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommandOutput { @@ -27,7 +20,7 @@ pub struct CommandOutput { /// # /// # Errors /// -pub fn create_command>(binary_name: T) -> Result { +fn create_command>(binary_name: T) -> Result { let binary_name = binary_name.as_ref(); log::trace!("Creating Command for binary {:?}", binary_name); @@ -42,7 +35,6 @@ pub fn create_command>(binary_name: T) -> Result { } }; - #[allow(clippy::disallowed_methods)] let mut cmd = Command::new(full_path); cmd.stderr(Stdio::piped()) .stdout(Stdio::piped()) @@ -71,7 +63,7 @@ fn internal_exec_cmd + Debug, U: AsRef + Debug>( exec_timeout(&mut cmd, time_limit) } -pub fn exec_timeout(cmd: &mut Command, time_limit: Duration) -> Option { +fn exec_timeout(cmd: &mut Command, time_limit: Duration) -> Option { let start = Instant::now(); let process = match cmd.spawn() { Ok(process) => process, diff --git a/src/constants.rs b/utils/src/constants.rs similarity index 100% rename from src/constants.rs rename to utils/src/constants.rs diff --git a/src/ops.rs b/utils/src/lib.rs similarity index 86% rename from src/ops.rs rename to utils/src/lib.rs index c5fe0c3..fcfd210 100644 --- a/src/ops.rs +++ b/utils/src/lib.rs @@ -1,9 +1,13 @@ -use std::{io::Write, process::Command}; +pub mod command_output; +pub mod constants; + +use std::{io::Write, path::PathBuf, process::Command, thread, time::Duration}; use anyhow::{anyhow, Result}; use format_serde_error::SerdeError; use log::{debug, trace}; -use std::{thread, time::Duration}; + +pub use command_output::*; pub fn check_command_exists(command: &str) -> Result<()> { trace!("check_command_exists({command})"); @@ -68,3 +72,8 @@ where }; } } + +#[must_use] +pub fn home_dir() -> Option { + directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf()) +}