embed-licensing 0.3.1

Embed licensing information of dependencies to comply with free software licenses
Documentation
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// SPDX-License-Identifier: MPL-2.0

use std::cell::RefCell;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::rc::Rc;

use cargo_platform::Platform;
use tempfile::{tempdir, TempDir};
use toml_edit::{value, DocumentMut};

pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

#[derive(Debug)]
pub(crate) struct SimpleManifest {
    pub(crate) name: String,
    pub(crate) version: String,
    pub(crate) license: Option<spdx::Expression>,
    pub(crate) license_file: Option<(PathBuf, String)>,
    pub(crate) authors: Vec<String>,
    pub(crate) repository: Option<String>,
    pub(crate) dependencies: RefCell<Vec<(Option<Platform>, Rc<TestPackage>)>>,
    pub(crate) dev_dependencies: RefCell<Vec<(Option<Platform>, Rc<TestPackage>)>>,
    pub(crate) build_dependencies: RefCell<Vec<(Option<Platform>, Rc<TestPackage>)>>,
}

#[derive(Debug)]
pub(crate) struct TestPackage {
    pub(crate) dir: TempDir,
    pub(crate) manifest: SimpleManifest,
}

fn set_dependency(
    doc: &mut DocumentMut,
    kind: &str,
    dependency: &(Option<Platform>, Rc<TestPackage>),
) {
    let (platform, dependency) = dependency;

    let val = value(dependency.dir.path().to_str().unwrap());

    if let Some(platform) = platform {
        let target_spec = match platform {
            Platform::Name(name) => name.as_str(),
            Platform::Cfg(cfg) => &format!("cfg({cfg})"),
        };
        doc["target"][target_spec][kind][&dependency.manifest.name]["path"] = val;
    } else {
        doc[kind][&dependency.manifest.name]["path"] = val;
    }
}

impl TestPackage {
    pub(crate) fn new(manifest: SimpleManifest) -> Result<Self> {
        let pkg = Self {
            dir: tempdir()?,
            manifest,
        };

        pkg.update_files()?;

        Ok(pkg)
    }

    pub(crate) fn update_files(&self) -> Result<()> {
        let mut doc = DocumentMut::new();
        doc["package"]["name"] = value(&self.manifest.name);
        doc["package"]["version"] = value(&self.manifest.version);
        if let Some(license) = &self.manifest.license {
            doc["package"]["license"] = value(license.to_string());
        }
        if let Some((path, contents)) = &self.manifest.license_file {
            let mut license_file = File::create(self.dir.path().join(path))?;
            write!(license_file, "{}", contents)?;
            doc["package"]["license-file"] = value(path.to_str().unwrap());
        }
        doc["package"]["authors"] =
            value(toml_edit::Array::from_iter(self.manifest.authors.iter()));
        if let Some(repository) = &self.manifest.repository {
            doc["package"]["repository"] = value(repository);
        }

        for dependency in self.manifest.dependencies.borrow().iter() {
            set_dependency(&mut doc, "dependencies", dependency);
        }

        for dev_dependency in self.manifest.dev_dependencies.borrow().iter() {
            set_dependency(&mut doc, "dev-dependencies", dev_dependency);
        }

        for build_dependency in self.manifest.build_dependencies.borrow().iter() {
            set_dependency(&mut doc, "build-dependencies", build_dependency);
        }

        write!(File::create(self.dir.path().join("Cargo.toml"))?, "{}", doc)?;
        create_dir_all(self.dir.path().join("src"))?;
        File::create(self.dir.path().join("src").join("lib.rs"))?;

        Command::new("cargo")
            .args(["generate-lockfile", "--quiet"])
            .current_dir(self.dir.path())
            .spawn()?;

        Ok(())
    }

    pub(crate) fn manifest_path(&self) -> PathBuf {
        self.dir.path().join("Cargo.toml")
    }
}