use std::{env::var, iter::once, path::PathBuf};
use crate::{Error, PackageArtifacts, Result, HOST_DIRNAME};
use cargo_metadata::{MetadataCommand, Package};
use cargo_subcommand::Subcommand;
use serde::{Deserialize, Serialize};
use serde_json::from_value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageSpec {
#[serde(rename = "package-name")]
pub package_name: String,
#[serde(rename = "package-number")]
pub package_number: isize,
pub name: String,
pub description: String,
pub host: String,
pub version: String,
#[serde(rename = "build-id")]
pub build_id: isize,
#[serde(rename = "build-id-namespace")]
pub build_id_namespace: String,
pub confidentiality: String,
#[serde(default)]
pub files: Vec<(String, String)>,
#[serde(rename = "type")]
pub typ: String,
pub disabled: bool,
#[serde(rename = "doc-title")]
pub doc_title: String,
#[serde(rename = "make-targets")]
pub make_targets: Vec<String>,
#[serde(rename = "include-release-notes")]
pub include_release_notes: bool,
#[serde(rename = "ip-plans")]
pub ip_plans: Vec<String>,
#[serde(rename = "legacy-doc-make-targets")]
pub legacy_doc_make_targets: Vec<String>,
#[serde(rename = "release-notes")]
pub release_notes: Vec<String>,
#[serde(rename = "access-labels")]
pub access_labels: Vec<String>,
}
impl PackageSpec {
pub fn from_subcommand(subcommand: &Subcommand) -> Result<Self> {
let manifest_spec = ManifestPackageSpec::from_subcommand(subcommand)?;
Ok(Self {
package_name: manifest_spec.package_name.ok_or_else(|| {
Error::PackageMetadataFieldNotFound {
field_name: "package_name".to_string(),
}
})?,
package_number: manifest_spec.package_number.ok_or_else(|| {
Error::PackageMetadataFieldNotFound {
field_name: "package_number".to_string(),
}
})?,
name: manifest_spec
.name
.ok_or_else(|| Error::PackageMetadataFieldNotFound {
field_name: "name".to_string(),
})?,
description: manifest_spec.description.ok_or_else(|| {
Error::PackageMetadataFieldNotFound {
field_name: "description".to_string(),
}
})?,
host: manifest_spec
.host
.ok_or_else(|| Error::PackageMetadataFieldNotFound {
field_name: "host".to_string(),
})?,
version: manifest_spec
.version
.ok_or_else(|| Error::PackageMetadataFieldNotFound {
field_name: "version".to_string(),
})?,
build_id: manifest_spec.build_id.ok_or_else(|| {
Error::PackageMetadataFieldNotFound {
field_name: "build_id".to_string(),
}
})?,
build_id_namespace: manifest_spec.build_id_namespace.ok_or_else(|| {
Error::PackageMetadataFieldNotFound {
field_name: "build_id_namespace".to_string(),
}
})?,
confidentiality: manifest_spec.confidentiality.ok_or_else(|| {
Error::PackageMetadataFieldNotFound {
field_name: "confidentiality".to_string(),
}
})?,
files: manifest_spec.files.clone(),
typ: manifest_spec
.typ
.ok_or_else(|| Error::PackageMetadataFieldNotFound {
field_name: "type".to_string(),
})?,
disabled: manifest_spec.disabled,
doc_title: manifest_spec.doc_title.ok_or_else(|| {
Error::PackageMetadataFieldNotFound {
field_name: "doc_title".to_string(),
}
})?,
make_targets: manifest_spec.make_targets.clone(),
include_release_notes: manifest_spec.include_release_notes,
ip_plans: manifest_spec.ip_plans.clone(),
legacy_doc_make_targets: manifest_spec.legacy_doc_make_targets.clone(),
release_notes: manifest_spec.release_notes.clone(),
access_labels: manifest_spec.access_labels.clone(),
})
}
pub fn with_artifacts(mut self, artifacts: &PackageArtifacts) -> Self {
self.files = artifacts.files.clone();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ManifestPackageSpec {
#[serde(rename = "package-name", default)]
package_name: Option<String>,
#[serde(rename = "package-number", default)]
package_number: Option<isize>,
#[serde(default)]
name: Option<String>,
#[serde(default)]
description: Option<String>,
#[serde(default)]
host: Option<String>,
#[serde(default)]
version: Option<String>,
#[serde(rename = "build-id", default)]
build_id: Option<isize>,
#[serde(rename = "build-id-namespace", default)]
build_id_namespace: Option<String>,
#[serde(default)]
confidentiality: Option<String>,
#[serde(default)]
files: Vec<(String, String)>,
#[serde(rename = "type", default)]
typ: Option<String>,
#[serde(default)]
disabled: bool,
#[serde(rename = "doc-title", default)]
doc_title: Option<String>,
#[serde(rename = "make-targets", default)]
make_targets: Vec<String>,
#[serde(rename = "include-release-notes", default)]
include_release_notes: bool,
#[serde(rename = "ip-plans", default)]
ip_plans: Vec<String>,
#[serde(rename = "legacy-doc-make-targets", default)]
legacy_doc_make_targets: Vec<String>,
#[serde(rename = "release-notes", default)]
release_notes: Vec<String>,
#[serde(rename = "access-labels", default)]
access_labels: Vec<String>,
}
impl ManifestPackageSpec {
pub fn default_type() -> String {
"addon".to_string()
}
}
impl ManifestPackageSpec {
pub fn from_package(package: &Package) -> Result<Self> {
let mut spec: ManifestPackageSpec = if let Some(spec) = package.metadata.get("simics") {
from_value(spec.clone()).map_err(Error::from)?
} else {
ManifestPackageSpec::default()
};
if spec.package_number.is_none() {
spec.package_number = Some(0);
}
if spec.package_name.is_none() {
spec.package_name = Some(package.name.clone());
}
if spec.name.is_none() {
spec.name = Some(package.name.clone());
}
if spec.description.is_none() {
spec.description = package.description.clone();
}
if spec.host.is_none() {
spec.host = Some(HOST_DIRNAME.to_string());
}
if spec.version.is_none() {
spec.version = Some(package.version.to_string());
}
if spec.build_id.is_none() {
spec.build_id = Some(
package
.version
.to_string()
.chars()
.filter(|c| c.is_numeric())
.collect::<String>()
.parse()
.map_err(Error::from)?,
)
}
if spec.build_id_namespace.is_none() {
spec.build_id_namespace = Some(package.name.clone());
}
if spec.confidentiality.is_none() {
spec.confidentiality = Some("Public".to_string());
}
if spec.typ.is_none() {
spec.typ = Some("addon".to_string());
}
if spec.doc_title.is_none() {
spec.doc_title = Some(package.name.clone());
}
if let Ok(package_name) = var("SIMICS_PACKAGE_PACKAGE_NAME") {
spec.package_name = Some(package_name);
}
if let Ok(package_number) = var("SIMICS_PACKAGE_PACKAGE_NUMBER") {
spec.package_number = Some(package_number.parse().map_err(Error::from)?);
}
if let Ok(package_name) = var("SIMICS_PACKAGE_NAME") {
spec.name = Some(package_name);
}
if let Ok(description) = var("SIMICS_PACKAGE_DESCRIPTION") {
spec.description = Some(description);
}
if let Ok(host) = var("SIMICS_PACKAGE_HOST") {
spec.host = Some(host);
}
if let Ok(version) = var("SIMICS_PACKAGE_VERSION") {
spec.version = Some(version);
}
if let Ok(build_id) = var("SIMICS_PACKAGE_BUILD_ID") {
spec.build_id = Some(build_id.parse().map_err(Error::from)?);
}
if let Ok(build_id_namespace) = var("SIMICS_PACKAGE_BUILD_ID_NAMESPACE") {
spec.build_id_namespace = Some(build_id_namespace);
}
if let Ok(confidentiality) = var("SIMICS_PACKAGE_CONFIDENTIALITY") {
spec.confidentiality = Some(confidentiality);
}
if let Ok(typ) = var("SIMICS_PACKAGE_TYPE") {
spec.typ = Some(typ);
}
if let Ok(doc_title) = var("SIMICS_PACKAGE_DOC_TITLE") {
spec.doc_title = Some(doc_title);
}
Ok(spec)
}
pub fn from_subcommand(subcommand: &Subcommand) -> Result<Self> {
Self::from_package(
MetadataCommand::new()
.manifest_path(subcommand.manifest())
.no_deps()
.exec()?
.packages
.iter()
.find(|p| p.name == subcommand.package())
.ok_or_else(|| Error::PackageNotFound {
name: subcommand.package().to_string(),
})?,
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageSpecs(pub Vec<PackageSpec>);
impl PackageSpecs {
pub fn from_subcommand(subcommand: &Subcommand) -> Result<Self> {
Ok(Self(vec![PackageSpec::from_subcommand(subcommand)?]))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IspmMetadata {
pub name: String,
#[serde(rename = "packageNumber")]
pub package_number: isize,
pub version: String,
#[serde(rename = "packageName")]
pub package_name: String,
pub kind: String,
pub host: String,
pub confidentiality: String,
#[serde(rename = "buildId")]
pub build_id: String,
#[serde(rename = "buildIdNamespace")]
pub build_id_namespace: String,
pub description: String,
#[serde(rename = "uncompressedSize")]
pub uncompressed_size: usize,
}
impl From<&PackageSpec> for IspmMetadata {
fn from(value: &PackageSpec) -> Self {
let value = value.clone();
Self {
name: value.name,
package_number: value.package_number,
version: value.version,
package_name: value.package_name,
kind: value.typ,
host: value.host,
confidentiality: value.confidentiality,
build_id: value.build_id.to_string(),
build_id_namespace: value.build_id_namespace,
description: value.description,
uncompressed_size: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PackageInfo {
pub name: String,
pub description: String,
pub version: String,
pub host: String,
#[serde(rename = "package-name")]
pub package_name: String,
#[serde(rename = "package-number")]
pub package_number: isize,
#[serde(rename = "build-id")]
pub build_id: isize,
#[serde(rename = "build-id-namespace")]
pub build_id_namespace: String,
#[serde(rename = "type")]
pub typ: String,
#[serde(rename = "extra-version", default)]
pub extra_version: String,
pub confidentiality: String,
#[serde(skip)]
pub files: Vec<String>,
}
impl From<&PackageSpec> for PackageInfo {
fn from(value: &PackageSpec) -> Self {
let dirname = format!("simics-{}-{}", value.package_name, value.version);
let self_file = PathBuf::from(dirname)
.join("packageinfo")
.join(format!("{}-{}", value.package_name, value.host));
Self {
name: value.name.clone(),
description: value.description.clone(),
version: value.version.clone(),
host: value.host.clone(),
package_name: value.package_name.clone(),
package_number: value.package_number,
build_id: value.build_id,
build_id_namespace: value.build_id_namespace.clone(),
typ: value.typ.clone(),
confidentiality: value.confidentiality.clone(),
files: value
.files
.iter()
.map(|f| f.0.clone())
.chain(once(self_file.to_str().unwrap_or_default().to_string()))
.collect(),
..Default::default()
}
}
}
impl PackageInfo {
pub fn files(&self) -> String {
"files:\n".to_string()
+ &self
.files
.iter()
.map(|f| format!(" {}", f))
.collect::<Vec<String>>()
.join("\n")
+ "\n"
}
}