diff --git a/hipcheck/src/cli.rs b/hipcheck/src/cli.rs index 14e23a3e..144a7246 100644 --- a/hipcheck/src/cli.rs +++ b/hipcheck/src/cli.rs @@ -7,6 +7,7 @@ use crate::{ error::Context, error::Result, hc_error, + plugin::SupportedArch, session::pm, shell::{color_choice::ColorChoice, verbosity::Verbosity}, source, @@ -416,6 +417,9 @@ pub struct CheckArgs { #[clap(subcommand)] command: Option, + #[arg(long = "arch")] + pub arch: Option, + #[arg(short = 't', long = "target")] pub target_type: Option, #[arg( diff --git a/hipcheck/src/engine.rs b/hipcheck/src/engine.rs index 0a4ab53b..39ae909c 100644 --- a/hipcheck/src/engine.rs +++ b/hipcheck/src/engine.rs @@ -5,8 +5,8 @@ use crate::{ cache::plugin::HcPluginCache, hc_error, plugin::{ - get_plugin_key, retrieve_plugins, Plugin, PluginManifest, PluginResponse, QueryResult, - CURRENT_ARCH, + get_plugin_key, retrieve_plugins, try_get_current_arch, Plugin, PluginManifest, + PluginResponse, QueryResult, }, policy::PolicyFile, util::fs::{find_file_by_name, read_string}, @@ -225,6 +225,8 @@ pub fn start_plugins( /* jitter_percent */ 10, )?; + let current_arch = try_get_current_arch()?; + // retrieve, verify and extract all required plugins let required_plugin_names = retrieve_plugins(&policy_file.plugins.0, plugin_cache)?; @@ -241,11 +243,11 @@ pub fn start_plugins( let contents = read_string(&plugin_kdl)?; let plugin_manifest = PluginManifest::from_str(contents.as_str())?; let entrypoint = plugin_manifest - .get_entrypoint(CURRENT_ARCH) + .get_entrypoint(current_arch) .ok_or_else(|| { hc_error!( "Could not find {} entrypoint for {}/{} {}", - CURRENT_ARCH, + current_arch, plugin_id.publisher.0, plugin_id.name.0, plugin_id.version.0 diff --git a/hipcheck/src/main.rs b/hipcheck/src/main.rs index 6c45f02e..f58a1b57 100644 --- a/hipcheck/src/main.rs +++ b/hipcheck/src/main.rs @@ -33,7 +33,7 @@ use crate::{ cli::Format, config::WeightTreeProvider, error::{Context as _, Error, Result}, - plugin::{Plugin, PluginExecutor, PluginWithConfig}, + plugin::{try_set_arch, Plugin, PluginExecutor, PluginWithConfig}, report::report_builder::{build_report, Report}, session::Session, setup::{resolve_and_transform_source, SourceType}, @@ -118,6 +118,13 @@ fn main() -> ExitCode { /// Run the `check` command. fn cmd_check(args: &CheckArgs, config: &CliConfig) -> ExitCode { + // Before we do any analysis, set the user-provided arch + if let Some(arch) = args.arch { + if let Err(e) = try_set_arch(arch) { + Shell::print_error(&e, Format::Human); + return ExitCode::FAILURE; + } + } let target = match args.to_target_seed() { Ok(target) => target, Err(e) => { diff --git a/hipcheck/src/plugin/download_manifest.rs b/hipcheck/src/plugin/download_manifest.rs index 1182c151..82c0f55c 100644 --- a/hipcheck/src/plugin/download_manifest.rs +++ b/hipcheck/src/plugin/download_manifest.rs @@ -8,7 +8,7 @@ use crate::{ plugin::{ retrieval::{download_plugin, extract_plugin}, supported_arch::SupportedArch, - CURRENT_ARCH, + try_get_current_arch, }, util::kdl::{extract_data, ParseKdlNode}, util::{ @@ -288,6 +288,8 @@ impl DownloadManifestEntry { version: &PluginVersion, downloaded_plugins: &'a mut HashSet, ) -> Result<&'a HashSet, Error> { + let current_arch = try_get_current_arch()?; + let plugin_id = PluginId::new(publisher.clone(), name.clone(), version.clone()); if downloaded_plugins.contains(&plugin_id) { @@ -323,7 +325,7 @@ impl DownloadManifestEntry { publisher.0, name.0, version.0, - CURRENT_ARCH + current_arch, ) })?; diff --git a/hipcheck/src/plugin/mod.rs b/hipcheck/src/plugin/mod.rs index 338971cd..2bddf7d9 100644 --- a/hipcheck/src/plugin/mod.rs +++ b/hipcheck/src/plugin/mod.rs @@ -15,7 +15,7 @@ pub use plugin_manifest::{PluginManifest, PluginName, PluginPublisher, PluginVer pub use retrieval::retrieve_plugins; use serde_json::Value; use std::collections::HashMap; -pub use supported_arch::CURRENT_ARCH; +pub use supported_arch::{try_get_current_arch, try_set_arch, SupportedArch}; use tokio::sync::Mutex; pub async fn initialize_plugins( diff --git a/hipcheck/src/plugin/supported_arch.rs b/hipcheck/src/plugin/supported_arch.rs index c70432e2..058b503d 100644 --- a/hipcheck/src/plugin/supported_arch.rs +++ b/hipcheck/src/plugin/supported_arch.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 +use crate::error::Result; use crate::hc_error; -use std::{fmt::Display, str::FromStr}; +use clap::ValueEnum; +use std::{fmt::Display, result::Result as StdResult, str::FromStr, sync::OnceLock}; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, ValueEnum)] /// Officially supported target triples, as of RFD #0004 /// /// NOTE: these architectures correspond to the offically supported Rust platforms @@ -18,36 +20,70 @@ pub enum SupportedArch { X86_64UnknownLinuxGnu, } -/// Architecture `hc` was built for -pub const CURRENT_ARCH: SupportedArch = { - #[cfg(target_arch = "x86_64")] - { - #[cfg(target_os = "macos")] - { - SupportedArch::X86_64AppleDarwin +pub const DETECTED_ARCH: Option = { + if cfg!(target_arch = "x86_64") { + if cfg!(target_os = "macos") { + Some(SupportedArch::X86_64AppleDarwin) + } else if cfg!(target_os = "linux") { + Some(SupportedArch::X86_64UnknownLinuxGnu) + } else if cfg!(target_os = "windows") { + Some(SupportedArch::X86_64PcWindowsMsvc) + } else { + None } - #[cfg(target_os = "linux")] - { - SupportedArch::X86_64UnknownLinuxGnu - } - #[cfg(target_os = "windows")] - { - SupportedArch::X86_64PcWindowsMsvc - } - } - #[cfg(target_arch = "aarch64")] - { - #[cfg(target_os = "macos")] - { - SupportedArch::Aarch64AppleDarwin + } else if cfg!(target_arch = "aarch64") { + if cfg!(target_os = "macos") { + Some(SupportedArch::Aarch64AppleDarwin) + } else { + None } + } else { + None } }; +pub static USER_PROVIDED_ARCH: OnceLock = OnceLock::new(); + +/// Get the target architecture for plugins. If the user provided a target, +/// return that. Otherwise, if the `hc` binary was compiled for a supported +/// architecture, return that. Otherwise return None. +pub fn get_current_arch() -> Option { + if let Some(arch) = USER_PROVIDED_ARCH.get() { + Some(*arch) + } else if DETECTED_ARCH.is_some() { + DETECTED_ARCH + } else { + None + } +} + +/// Like `get_current_arch()`, but returns an error message suggesting the +/// user specifies a target on the CLI +pub fn try_get_current_arch() -> Result { + if let Some(arch) = get_current_arch() { + Ok(arch) + } else { + Err(hc_error!("Could not resolve the current machine to one of the Hipcheck supported architectures. Please specify --arch on the commandline.")) + } +} + +pub fn try_set_arch(arch: SupportedArch) -> Result<()> { + let set_arch = USER_PROVIDED_ARCH.get_or_init(|| arch); + if *set_arch == arch { + Ok(()) + } else { + Err(hc_error!( + "Architecture could not be set to {}, has already been set to {}", + arch, + set_arch + )) + } +} + impl FromStr for SupportedArch { type Err = crate::Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> StdResult { match s { "aarch64-apple-darwin" => Ok(Self::Aarch64AppleDarwin), "x86_64-apple-darwin" => Ok(Self::X86_64AppleDarwin),