1 // Copyright 2023 Google LLC
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 chrono::Datelike;
16 use file_header::{check_headers_recursively, license::spdx::*};
17 use std::path;
18 
19 #[derive(clap::Subcommand, Debug, Clone)]
20 pub enum LicenseSubcommand {
21     /// Checks the workspace 3rd party crates and makes sure they have a valid license
22     CheckLicenseHeaders,
23     /// Generate new headers for any files that are missing them
24     AddLicenseHeaders,
25 }
26 
27 impl LicenseSubcommand {
run(&self, checker: &LicenseChecker, root: &path::Path) -> anyhow::Result<()>28     pub fn run(&self, checker: &LicenseChecker, root: &path::Path) -> anyhow::Result<()> {
29         match self {
30             LicenseSubcommand::CheckLicenseHeaders => checker.check(root)?,
31             LicenseSubcommand::AddLicenseHeaders => checker.add_missing(root)?,
32         }
33         Ok(())
34     }
35 }
36 
37 pub struct LicenseChecker {
38     pub ignore: &'static [&'static str],
39 }
40 
41 impl LicenseChecker {
check(&self, root: &path::Path) -> anyhow::Result<()>42     pub fn check(&self, root: &path::Path) -> anyhow::Result<()> {
43         log::info!("Checking license headers");
44         let ignore = self.ignore_globset()?;
45         let results = check_headers_recursively(
46             root,
47             |p| !ignore.is_match(p),
48             APACHE_2_0.build_header(YearCopyrightOwnerValue::new(
49                 u32::try_from(chrono::Utc::now().year())?,
50                 "Google LLC".to_string(),
51             )),
52             4,
53         )?;
54 
55         for path in results.no_header_files.iter() {
56             eprintln!("Header not present: {path:?}");
57         }
58 
59         for path in results.binary_files.iter() {
60             eprintln!("Binary file: {path:?}");
61         }
62         if !results.binary_files.is_empty() {
63             eprintln!("Consider adding binary files to the ignore list in src/licence.rs.");
64         }
65 
66         if results.has_failure() {
67             Err(anyhow::anyhow!("License header check failed"))
68         } else {
69             Ok(())
70         }
71     }
72 
add_missing(&self, root: &path::Path) -> anyhow::Result<()>73     pub fn add_missing(&self, root: &path::Path) -> anyhow::Result<()> {
74         let ignore = self.ignore_globset()?;
75         for p in file_header::add_headers_recursively(
76             root,
77             |p| !ignore.is_match(p),
78             APACHE_2_0.build_header(YearCopyrightOwnerValue::new(
79                 u32::try_from(chrono::Utc::now().year())?,
80                 "Google LLC".to_string(),
81             )),
82         )? {
83             println!("Added header: {:?}", p);
84         }
85 
86         Ok(())
87     }
88 
ignore_globset(&self) -> Result<globset::GlobSet, globset::Error>89     fn ignore_globset(&self) -> Result<globset::GlobSet, globset::Error> {
90         let mut builder = globset::GlobSet::builder();
91         for lic in self.ignore {
92             builder.add(globset::Glob::new(lic)?);
93         }
94         builder.build()
95     }
96 
check_new_ignore_is_likely_buggy(&self)97     pub fn check_new_ignore_is_likely_buggy(&self) {
98         for dir in self.ignore {
99             assert!(
100                 dir.starts_with("**/"),
101                 "Matching on the root filesystem is likely unintended"
102             );
103         }
104     }
105 }
106