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