lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

resolve.rs (7673B)


      1 use crate::error::RadrootsRuntimeDistributionError;
      2 use crate::model::{
      3     ArtifactAdapter, RadrootsRuntimeDistributionContract, RuntimeDistributionEntry, TargetSpec,
      4 };
      5 
      6 pub const RUNTIME_DISTRIBUTION_SCHEMA: &str = "radroots-runtime-distribution";
      7 
      8 #[derive(Debug, Clone, PartialEq, Eq)]
      9 pub struct RuntimeArtifactRequest<'a> {
     10     pub runtime_id: &'a str,
     11     pub os: &'a str,
     12     pub arch: &'a str,
     13     pub version: &'a str,
     14     pub channel: Option<&'a str>,
     15 }
     16 
     17 #[derive(Debug, Clone, PartialEq, Eq)]
     18 pub struct ResolvedRuntimeArtifact {
     19     pub runtime_id: String,
     20     pub release_unit: String,
     21     pub package_name: String,
     22     pub binary_name: Option<String>,
     23     pub artifact_adapter: String,
     24     pub channel: String,
     25     pub version: String,
     26     pub target_id: String,
     27     pub os: String,
     28     pub arch: String,
     29     pub archive_format: String,
     30     pub archive_extension: String,
     31     pub artifact_stem: String,
     32     pub artifact_file_name: String,
     33 }
     34 
     35 #[derive(Debug, Clone)]
     36 pub struct RadrootsRuntimeDistributionResolver {
     37     contract: RadrootsRuntimeDistributionContract,
     38 }
     39 
     40 impl RadrootsRuntimeDistributionResolver {
     41     pub fn parse_str(raw: &str) -> Result<Self, RadrootsRuntimeDistributionError> {
     42         let contract = toml::from_str::<RadrootsRuntimeDistributionContract>(raw)
     43             .map_err(|err| RadrootsRuntimeDistributionError::Parse(err.to_string()))?;
     44         Self::new(contract)
     45     }
     46 
     47     pub fn new(
     48         contract: RadrootsRuntimeDistributionContract,
     49     ) -> Result<Self, RadrootsRuntimeDistributionError> {
     50         if contract.schema != RUNTIME_DISTRIBUTION_SCHEMA {
     51             return Err(RadrootsRuntimeDistributionError::UnexpectedSchema {
     52                 expected: RUNTIME_DISTRIBUTION_SCHEMA,
     53                 found: contract.schema.clone(),
     54             });
     55         }
     56         Ok(Self { contract })
     57     }
     58 
     59     pub fn contract(&self) -> &RadrootsRuntimeDistributionContract {
     60         &self.contract
     61     }
     62 
     63     pub fn resolve_artifact(
     64         &self,
     65         request: &RuntimeArtifactRequest<'_>,
     66     ) -> Result<ResolvedRuntimeArtifact, RadrootsRuntimeDistributionError> {
     67         let runtime = self
     68             .contract
     69             .runtime
     70             .iter()
     71             .find(|runtime| runtime.id == request.runtime_id)
     72             .ok_or_else(|| {
     73                 RadrootsRuntimeDistributionError::UnknownRuntime(request.runtime_id.to_string())
     74             })?;
     75 
     76         if !runtime.human_installable {
     77             return Err(RadrootsRuntimeDistributionError::RuntimeNotInstallable(
     78                 runtime.id.clone(),
     79             ));
     80         }
     81 
     82         let channel = request.channel.unwrap_or(runtime.default_channel.as_str());
     83         self.ensure_channel_is_active(channel)?;
     84 
     85         let target_set_id = runtime.target_set.as_ref().ok_or_else(|| {
     86             RadrootsRuntimeDistributionError::MissingTargetSet(runtime.id.clone())
     87         })?;
     88 
     89         let adapter = self
     90             .contract
     91             .artifact_adapters
     92             .get(&runtime.artifact_adapter)
     93             .ok_or_else(
     94                 || RadrootsRuntimeDistributionError::UnknownArtifactAdapter {
     95                     runtime_id: runtime.id.clone(),
     96                     adapter_id: runtime.artifact_adapter.clone(),
     97                 },
     98             )?;
     99 
    100         let (target_id, target) =
    101             self.select_target(runtime, target_set_id, request.os, request.arch)?;
    102         let archive_format_id =
    103             self.resolve_archive_format_id(runtime, target_id, target, adapter)?;
    104         let archive_format = self
    105             .contract
    106             .archive_formats
    107             .get(&normalized_contract_key(archive_format_id))
    108             .ok_or_else(|| RadrootsRuntimeDistributionError::UnknownArchiveFormat {
    109                 target_id: target_id.to_string(),
    110                 archive_format_id: archive_format_id.to_string(),
    111             })?;
    112 
    113         let artifact_stem = format!("{}-{}-{}", runtime.release_unit, request.version, target_id);
    114         let artifact_file_name = format!("{artifact_stem}{}", archive_format.extension);
    115 
    116         Ok(ResolvedRuntimeArtifact {
    117             runtime_id: runtime.id.clone(),
    118             release_unit: runtime.release_unit.clone(),
    119             package_name: runtime.package_name.clone(),
    120             binary_name: runtime.binary_name.clone(),
    121             artifact_adapter: runtime.artifact_adapter.clone(),
    122             channel: channel.to_string(),
    123             version: request.version.to_string(),
    124             target_id: target_id.to_string(),
    125             os: request.os.to_string(),
    126             arch: request.arch.to_string(),
    127             archive_format: archive_format_id.to_string(),
    128             archive_extension: archive_format.extension.clone(),
    129             artifact_stem,
    130             artifact_file_name,
    131         })
    132     }
    133 
    134     fn ensure_channel_is_active(
    135         &self,
    136         channel: &str,
    137     ) -> Result<(), RadrootsRuntimeDistributionError> {
    138         if !self
    139             .contract
    140             .channels
    141             .defined
    142             .iter()
    143             .any(|entry| entry == channel)
    144         {
    145             return Err(RadrootsRuntimeDistributionError::UnknownChannel(
    146                 channel.to_string(),
    147             ));
    148         }
    149         if !self
    150             .contract
    151             .channels
    152             .active
    153             .iter()
    154             .any(|entry| entry == channel)
    155         {
    156             return Err(RadrootsRuntimeDistributionError::InactiveChannel(
    157                 channel.to_string(),
    158             ));
    159         }
    160         Ok(())
    161     }
    162 
    163     fn select_target<'a>(
    164         &'a self,
    165         runtime: &RuntimeDistributionEntry,
    166         target_set_id: &str,
    167         os: &str,
    168         arch: &str,
    169     ) -> Result<(&'a str, &'a TargetSpec), RadrootsRuntimeDistributionError> {
    170         let target_set = self
    171             .contract
    172             .target_sets
    173             .get(target_set_id)
    174             .ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform {
    175                 runtime_id: runtime.id.clone(),
    176                 os: os.to_string(),
    177                 arch: arch.to_string(),
    178             })?;
    179 
    180         let mut found_match = None;
    181         for target_id in &target_set.targets {
    182             let target = self.contract.targets.get(target_id).ok_or_else(|| {
    183                 RadrootsRuntimeDistributionError::UnknownTarget {
    184                     runtime_id: runtime.id.clone(),
    185                     target_set_id: target_set_id.to_string(),
    186                     target_id: target_id.clone(),
    187                 }
    188             })?;
    189 
    190             if target.os == os && target.arch == arch {
    191                 found_match = Some((target_id.as_str(), target));
    192                 break;
    193             }
    194         }
    195 
    196         found_match.ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform {
    197             runtime_id: runtime.id.clone(),
    198             os: os.to_string(),
    199             arch: arch.to_string(),
    200         })
    201     }
    202 
    203     fn resolve_archive_format_id<'a>(
    204         &self,
    205         runtime: &RuntimeDistributionEntry,
    206         target_id: &'a str,
    207         target: &'a TargetSpec,
    208         adapter: &'a ArtifactAdapter,
    209     ) -> Result<&'a str, RadrootsRuntimeDistributionError> {
    210         if let Some(format) = target.archive_format.as_deref() {
    211             return Ok(format);
    212         }
    213 
    214         if adapter.supported_archive_formats.len() == 1 {
    215             return Ok(adapter.supported_archive_formats[0].as_str());
    216         }
    217 
    218         Err(RadrootsRuntimeDistributionError::MissingArchiveFormat {
    219             runtime_id: runtime.id.clone(),
    220             target_id: target_id.to_string(),
    221         })
    222     }
    223 }
    224 
    225 fn normalized_contract_key(value: &str) -> String {
    226     value.replace('.', "_")
    227 }