use std::collections::{BTreeMap, HashMap};
use axoasset::{LocalAsset, RemoteAsset};
use axoproject::platforms::KNOWN_TARGET_TRIPLES;
use camino::Utf8PathBuf;
use indexmap::IndexMap;
use serde::{Serialize, Serializer};
use crate::config::ArtifactsConfig;
use crate::config::Config;
use crate::errors::*;
use inference::KNOWN_SCRIPT_EXTS;
pub mod inference;
pub type TargetTriple = String;
pub type Targ = str;
pub type AppName = String;
pub type FileName = String;
#[derive(Debug, Default, Clone, Serialize)]
pub struct ReleaseArtifacts {
#[serde(skip)]
pub(crate) app_name: Option<String>,
#[serde(serialize_with = "flatten_files")]
files: IndexMap<FileName, File>,
installers: Vec<Installer>,
targets: BTreeMap<TargetTriple, Vec<InstallerIdx>>,
}
#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FileIdx(usize);
#[derive(Debug, Clone, Serialize)]
pub struct File {
pub name: FileName,
pub download_url: String,
pub view_path: Option<String>,
pub checksum_file: Option<FileIdx>,
#[serde(skip)]
pub infer: bool,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize)]
pub struct InstallerIdx(pub usize);
#[derive(Debug, Clone, Serialize)]
pub struct Installer {
pub label: String,
pub description: String,
pub app_name: Option<String>,
#[serde(skip)]
pub targets: HashMap<TargetTriple, InstallerPreference>,
pub method: InstallMethod,
#[serde(skip)]
pub display: DisplayPreference,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub enum InstallerPreference {
Preferred,
Native,
Script,
Custom,
Archive,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub enum DisplayPreference {
Preferred,
Additional,
Hidden,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum InstallMethod {
Download {
file: FileIdx,
},
Run {
file: Option<FileIdx>,
run_hint: String,
},
}
impl ReleaseArtifacts {
pub fn new(app_name: Option<AppName>) -> Self {
Self {
app_name,
..Self::default()
}
}
pub fn add_file(&mut self, file: File) -> FileIdx {
let idx = FileIdx(self.files.len());
let old = self.files.insert(file.name.clone(), file);
assert!(
old.is_none(),
"release had two files with the same name ({})??",
&self.files[idx.0].name
);
idx
}
pub fn add_installer(&mut self, installer: Installer) -> InstallerIdx {
let idx = InstallerIdx(self.installers.len());
self.installers.push(installer);
idx
}
pub fn file(&self, idx: FileIdx) -> &File {
self.files
.get_index(idx.0)
.expect("invalid FileIdx (did you remove an entry?)")
.1
}
pub fn file_mut(&mut self, idx: FileIdx) -> &mut File {
self.files
.get_index_mut(idx.0)
.expect("invalid FileIdx (did you remove an entry?)")
.1
}
pub fn file_idx(&self, name: &FileName) -> Option<FileIdx> {
self.files.get_index_of(name).map(FileIdx)
}
pub fn file_indices(&self) -> impl Iterator<Item = FileIdx> {
(0..self.files.len()).map(FileIdx)
}
pub fn files(&self) -> impl Iterator<Item = &File> {
self.files.values()
}
pub fn installer(&self, idx: InstallerIdx) -> &Installer {
&self.installers[idx.0]
}
pub fn installers(&self) -> impl Iterator<Item = (InstallerIdx, &Installer)> {
self.installers
.iter()
.enumerate()
.map(|(idx, ins)| (InstallerIdx(idx), ins))
}
pub fn installers_by_target(&self) -> &BTreeMap<TargetTriple, Vec<InstallerIdx>> {
&self.targets
}
pub fn add_package_managers(&mut self, config: &ArtifactsConfig) {
if config.package_managers.has_npm() {
if let Some(installer) = self
.installers
.iter_mut()
.find(|installer| installer.label == "npm")
{
installer.display = DisplayPreference::Hidden;
}
}
for (label, script) in &config.package_managers.preferred {
let mut installer = simple_run_installer(label, script);
installer.display = DisplayPreference::Preferred;
self.add_installer(installer);
}
for (label, script) in &config.package_managers.additional {
let mut installer = simple_run_installer(label, script);
installer.display = DisplayPreference::Additional;
self.add_installer(installer);
}
}
pub fn select_installers(&mut self, artifacts_config: &ArtifactsConfig) {
for installer in &mut self.installers {
if artifacts_config.hidden.contains(&installer.label) {
installer.display = DisplayPreference::Hidden;
}
}
for target in KNOWN_TARGET_TRIPLES.iter().copied().flatten().copied() {
let mut installers = vec![];
for (idx, installer) in self.installers() {
if installer.display != DisplayPreference::Preferred {
continue;
}
if let Some(preference) = installer.targets.get(target) {
installers.push((idx, preference));
}
}
installers.sort_by(|(idx_a, pref_a), (idx_b, pref_b)| {
let installer_a = self.installer(*idx_a);
let installer_b = self.installer(*idx_b);
pref_a
.cmp(pref_b)
.then_with(|| installer_a.label.cmp(&installer_b.label))
});
let installers: Vec<_> = installers.into_iter().map(|(i, _pref)| i).collect();
if !installers.is_empty() {
self.targets.insert(target.to_owned(), installers);
}
}
}
pub fn make_scripts_viewable(&mut self, config: &Config) -> Result<()> {
for file in self.files.values_mut() {
if KNOWN_SCRIPT_EXTS.iter().any(|ext| file.name.ends_with(ext)) {
let path = write_source(config, file)?;
file.view_path = Some(path);
}
}
Ok(())
}
}
pub fn preference_to_targets(
targets: Vec<TargetTriple>,
preference: InstallerPreference,
) -> HashMap<TargetTriple, InstallerPreference> {
let targets = if targets.is_empty() {
KNOWN_TARGET_TRIPLES
.iter()
.copied()
.flatten()
.copied()
.map(|t| t.to_owned())
.collect()
} else {
targets
};
targets.into_iter().map(|t| (t, preference)).collect()
}
fn flatten_files<S>(files: &IndexMap<FileName, File>, s: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let files: Vec<_> = files.values().collect();
files.serialize(s)
}
fn write_source(config: &Config, file: &File) -> Result<String> {
let file_path = format!("{}.txt", &file.name);
let full_file_path = Utf8PathBuf::from(&config.build.dist_dir).join(&file_path);
if !full_file_path.exists() {
let file_string_future = RemoteAsset::load_string(&file.download_url);
let file_string = tokio::runtime::Handle::current().block_on(file_string_future)?;
LocalAsset::write_new(&file_string, &full_file_path)?;
}
Ok(file_path)
}
fn simple_run_installer(label: &str, script: &str) -> Installer {
let run_hint = script.to_owned();
Installer {
label: label.to_owned(),
description: String::new(),
app_name: None,
targets: preference_to_targets(vec![], InstallerPreference::Custom),
method: InstallMethod::Run {
file: None,
run_hint,
},
display: DisplayPreference::Preferred,
}
}