1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use std::{
16 collections::BTreeMap,
17 fs::{remove_file, write},
18 path::Path,
19 sync::LazyLock,
20 };
21
22 use anyhow::{anyhow, Result};
23 use glob::glob;
24 use google_metadata::metadata::LicenseType;
25 use license_checker::LicenseState;
26 use spdx::{LicenseReq, Licensee};
27
28 /// Update MODULE_LICENSE_* files in a directory based on the applicable licenses.
29 /// These files are typically empty, and their name indicates the type of license that
30 /// applies to the code, for example MODULE_LICENSE_APACHE2.
update_module_license_files(path: &impl AsRef<Path>, licenses: &LicenseState) -> Result<()>31 pub fn update_module_license_files(path: &impl AsRef<Path>, licenses: &LicenseState) -> Result<()> {
32 let path = path.as_ref();
33 for old_module_license_file in glob(
34 path.join("MODULE_LICENSE*").to_str().ok_or(anyhow!("Failed to convert path to string"))?,
35 )? {
36 remove_file(old_module_license_file?)?;
37 }
38 for license in licenses.satisfied.keys().chain(&licenses.unsatisfied) {
39 if let Some(mod_lic) = MODULE_LICENSE_FILES.get(license) {
40 write(path.join(mod_lic), "")?; // Write an empty file. Essentially "touch".
41 }
42 }
43 Ok(())
44 }
45
discriminant(lt: LicenseType) -> u846 fn discriminant(lt: LicenseType) -> u8 {
47 // Smaller --> more restricted
48 // Larger --> less restricted
49 match lt {
50 LicenseType::UNKNOWN => 0,
51 LicenseType::BY_EXCEPTION_ONLY => 1,
52 LicenseType::RESTRICTED => 2,
53 LicenseType::RESTRICTED_IF_STATICALLY_LINKED => 3,
54 LicenseType::RECIPROCAL => 4,
55 LicenseType::NOTICE => 5,
56 LicenseType::PERMISSIVE => 6,
57 LicenseType::UNENCUMBERED => 7,
58 }
59 }
60
most_restrictive_type(licenses: &LicenseState) -> LicenseType61 pub fn most_restrictive_type(licenses: &LicenseState) -> LicenseType {
62 licenses
63 .satisfied
64 .keys()
65 .chain(&licenses.unsatisfied)
66 .map(|req| LICENSE_TYPES.get(req).cloned().unwrap_or(LicenseType::UNKNOWN))
67 .min_by(|a, b| discriminant(*a).cmp(&discriminant(*b)))
68 .unwrap_or(LicenseType::UNKNOWN)
69 }
70
71 static MODULE_LICENSE_FILES: LazyLock<BTreeMap<LicenseReq, &'static str>> = LazyLock::new(|| {
72 vec![
73 ("Apache-2.0", "MODULE_LICENSE_APACHE2"),
74 ("MIT", "MODULE_LICENSE_MIT"),
75 ("BSD-3-Clause", "MODULE_LICENSE_BSD"),
76 ("BSD-2-Clause", "MODULE_LICENSE_BSD"),
77 ("ISC", "MODULE_LICENSE_ISC"),
78 ("MPL-2.0", "MODULE_LICENSE_MPL"),
79 ("0BSD", "MODULE_LICENSE_PERMISSIVE"),
80 ("Unlicense", "MODULE_LICENSE_PERMISSIVE"),
81 ("Zlib", "MODULE_LICENSE_ZLIB"),
82 ("Unicode-DFS-2016", "MODULE_LICENSE_UNICODE"),
83 ("NCSA", "MODULE_LICENSE_NCSA"),
84 ("OpenSSL", "MODULE_LICENSE_OPENSSL"),
85 ]
86 .into_iter()
87 .map(|l| (Licensee::parse(l.0).unwrap().into_req(), l.1))
88 .collect()
89 });
90 static LICENSE_TYPES: LazyLock<BTreeMap<LicenseReq, LicenseType>> = LazyLock::new(|| {
91 vec![
92 ("Apache-2.0", LicenseType::NOTICE),
93 ("MIT", LicenseType::NOTICE),
94 ("BSD-3-Clause", LicenseType::NOTICE),
95 ("BSD-2-Clause", LicenseType::NOTICE),
96 ("ISC", LicenseType::NOTICE),
97 ("MPL-2.0", LicenseType::RECIPROCAL),
98 ("0BSD", LicenseType::PERMISSIVE),
99 ("Unlicense", LicenseType::PERMISSIVE),
100 ("Zlib", LicenseType::NOTICE),
101 ("Unicode-DFS-2016", LicenseType::NOTICE),
102 ("NCSA", LicenseType::NOTICE),
103 ("OpenSSL", LicenseType::NOTICE),
104 ]
105 .into_iter()
106 .map(|l| (Licensee::parse(l.0).unwrap().into_req(), l.1))
107 .collect()
108 });
109