sancus_lib/
third_party_licenses.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6//
7// SPDX-License-Identifier: MIT OR Apache-2.0
8//
9// SPDX-FileCopyrightText: 2024 X-Software GmbH <opensource@x-software.com>
10
11use anyhow::{Context, Result};
12use log::*;
13use serde::{Deserialize, Serialize};
14use std::path::Path;
15
16use crate::license_info::LicenseInfo;
17use crate::settings;
18
19#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
20#[serde(rename_all = "snake_case")]
21pub struct License {
22    pub license: String,
23    pub text: String,
24}
25
26#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
27#[serde(rename_all = "snake_case")]
28pub struct ThirdPartyLibrary {
29    pub package_name: String,
30    pub package_version: String,
31    pub license: String,
32    pub licenses: Vec<License>,
33}
34
35#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
36#[serde(rename_all = "snake_case")]
37pub struct ThirdPartyLicenses {
38    pub root_name: String,
39    pub third_party_libraries: Vec<ThirdPartyLibrary>,
40}
41
42impl ThirdPartyLicenses {
43    pub fn load(file: &std::path::Path) -> Result<Self> {
44        let str = std::fs::read_to_string(file).with_context(|| {
45            format!(
46                "Cannot read third party licenses from file {}",
47                file.to_str().unwrap_or("empty")
48            )
49        })?;
50        serde_json::from_str::<Self>(str.as_str()).with_context(|| {
51            format!(
52                "Cannot parse third party licenses from file {}",
53                file.to_str().unwrap_or("empty")
54            )
55        })
56    }
57
58    pub fn save(&self, file_path: &Path) -> Result<()> {
59        // Create all parent directories if the don't exist:
60        let parent_dir = file_path.parent().unwrap_or_else(|| {
61            panic!(
62                "Cannot get parent directory of third party file {}",
63                file_path.to_string_lossy().into_owned()
64            )
65        });
66        std::fs::create_dir_all(parent_dir)?;
67        // Write json file:
68        let str = serde_json::to_string_pretty(self).with_context(|| "Cannot serialize third party licenses")?;
69        std::fs::write(file_path, str).with_context(|| {
70            format!(
71                "Cannot serialize third party licenses to file {}",
72                file_path.to_str().unwrap_or("empty")
73            )
74        })
75    }
76
77    pub fn apply_overrides(&mut self, overrides: &[settings::Override]) -> Result<()> {
78        for package in &mut self.third_party_libraries {
79            if let Some(license_override) = settings::Override::find_override(&package.package_name, overrides) {
80                if let Some(license_id) = &license_override.license_id {
81                    package.license = license_id.clone();
82
83                    if license_override.overwrite_all_license_ids {
84                        for package_license in &mut package.licenses {
85                            package_license.license = license_id.clone();
86                        }
87                    }
88                }
89            }
90        }
91        Ok(())
92    }
93
94    pub fn export(&self, result_dir: &Path) -> Result<()> {
95        let base_path = result_dir.join(self.root_name.clone());
96        for package in &self.third_party_libraries {
97            let pkg_dir = base_path.join(package.package_name.clone());
98
99            std::fs::create_dir_all(pkg_dir.as_path())?;
100
101            let pkg_desc_file = pkg_dir.join("README.txt");
102            let pkg_desc = format!(
103                "Package: {}\nVersion: {}\nLicense: {}\n",
104                package.package_name, package.package_version, package.license
105            );
106            std::fs::write(pkg_desc_file.as_path(), pkg_desc).with_context(|| {
107                format!(
108                    "Cannot write package description to file {}",
109                    pkg_desc_file.to_string_lossy()
110                )
111            })?;
112
113            for license_text in &package.licenses {
114                // Workaround to fix unusual license ID: "License specified in file ($CARGO_HOME/registry/src/.../LICENSE)"
115                let file_name = license_text.license.replace(std::path::is_separator, "-");
116                let file_name = file_name.replace('$', "-");
117                let license_text_file = pkg_dir.join(format!("{file_name}.txt"));
118
119                std::fs::write(license_text_file.as_path(), license_text.text.clone()).with_context(|| {
120                    format!(
121                        "Cannot write license text to file {}",
122                        license_text_file.to_string_lossy()
123                    )
124                })?;
125            }
126        }
127        Ok(())
128    }
129
130    pub fn new(root_name: &str, license_infos: &[LicenseInfo]) -> Self {
131        let mut third_party_libraries = vec![];
132
133        for info in license_infos {
134            let mut licenses = vec![];
135
136            for license_text in &info.license_texts {
137                licenses.push(License {
138                    license: license_text.id.clone(),
139                    text: license_text.text.clone(),
140                });
141            }
142
143            third_party_libraries.push(ThirdPartyLibrary {
144                package_name: info.package_name.clone(),
145                package_version: info.version.clone().unwrap_or_default(),
146                license: info.license.clone(),
147                licenses,
148            });
149        }
150
151        ThirdPartyLicenses {
152            root_name: root_name.to_owned(),
153            third_party_libraries,
154        }
155    }
156
157    pub fn print(&self) {
158        let mut root_tree = termtree::Tree::new(self.root_name.clone());
159
160        for lib in &self.third_party_libraries {
161            let mut pkg_tree = termtree::Tree::new(format!("Package: {}", lib.package_name.clone()));
162
163            pkg_tree.push(termtree::Tree::new(format!("Version: {}", lib.package_version)));
164            pkg_tree.push(termtree::Tree::new(format!("License ID: {}", lib.license)));
165
166            let mut license_tree = termtree::Tree::new("Licenses:".to_owned());
167            for license in &lib.licenses {
168                license_tree.push(termtree::Tree::new(format!("ID: {}", license.license)));
169            }
170            pkg_tree.push(license_tree);
171
172            root_tree.push(pkg_tree);
173        }
174
175        for line in root_tree.to_string().lines() {
176            debug!("{line}")
177        }
178    }
179}