/ build.rs
build.rs
1 // Copyright (c) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the alphavm library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 use std::{ 17 ffi::OsStr, 18 fs::{self, File}, 19 io::Read, 20 path::Path, 21 }; 22 23 use walkdir::WalkDir; 24 25 // The following license text that should be present at the beginning of every source file. 26 const EXPECTED_LICENSE_TEXT: &[u8] = include_bytes!(".license_header"); 27 28 // The following directories will be excluded from the license scan. 29 const DIRS_TO_SKIP: [&str; 5] = [".cargo", ".circleci", ".git", ".github", "target"]; 30 31 #[derive(Clone, Copy, PartialEq, Eq)] 32 enum ImportOfInterest { 33 Locktick, 34 ParkingLot, 35 Tokio, 36 } 37 38 fn check_locktick_imports<P: AsRef<Path>>(path: P) { 39 let mut iter = WalkDir::new(path).into_iter(); 40 while let Some(entry) = iter.next() { 41 let entry = entry.unwrap(); 42 let entry_type = entry.file_type(); 43 44 // Skip the specified directories. 45 if entry_type.is_dir() && DIRS_TO_SKIP.contains(&entry.file_name().to_str().unwrap_or("")) { 46 iter.skip_current_dir(); 47 48 continue; 49 } 50 51 let path = entry.path(); 52 53 // Ignore non-rs 54 if path.extension() != Some(OsStr::new("rs")) { 55 continue; 56 } 57 58 // Read the entire file. 59 let file = fs::read_to_string(path).unwrap(); 60 61 // Prepare a filtered line iterator. 62 let lines = file 63 .lines() 64 .filter(|l| !l.is_empty()) // Ignore empty lines. 65 .skip_while(|l| !l.starts_with("use")) // Skip the license etc. 66 .take_while(|l| { // Process the section containing import statements. 67 l.starts_with("use") 68 || l.starts_with("#[cfg") 69 || l.starts_with("//") 70 || *l == "};" 71 || l.starts_with(|c: char| c.is_ascii_whitespace()) 72 }); 73 74 // The currently processed import of interest. 75 let mut import_of_interest: Option<ImportOfInterest> = None; 76 // This value not being zero at the end of the imports suggests a missing locktick import. 77 let mut lock_balance: i8 = 0; 78 79 // Process the filtered lines. 80 for line in lines { 81 // Check if this is a lock-related import. 82 if import_of_interest.is_none() { 83 if line.starts_with("use locktick::") { 84 import_of_interest = Some(ImportOfInterest::Locktick); 85 } else if line.starts_with("use parking_lot::") { 86 import_of_interest = Some(ImportOfInterest::ParkingLot); 87 } else if line.starts_with("use tokio::") { 88 import_of_interest = Some(ImportOfInterest::Tokio); 89 } 90 } 91 92 // Skip irrelevant imports. 93 let Some(ioi) = import_of_interest else { 94 continue; 95 }; 96 97 // Modify the lock balance based on the type of the relevant import. 98 if [ImportOfInterest::ParkingLot, ImportOfInterest::Tokio].contains(&ioi) { 99 if line.contains("Mutex") { 100 lock_balance += 1; 101 } 102 if line.contains("RwLock") { 103 lock_balance += 1; 104 } 105 } else if ioi == ImportOfInterest::Locktick { 106 // Use `matches` instead of just `contains` here, as more than a single 107 // lock type entry is possible in a locktick import. 108 for _hit in line.matches("Mutex") { 109 lock_balance -= 1; 110 } 111 for _hit in line.matches("RwLock") { 112 lock_balance -= 1; 113 } 114 // A correction in case of the `use tokio::Mutex as TMutex` convention. 115 if line.contains("TMutex") { 116 lock_balance += 1; 117 } 118 } 119 120 // Register the end of an import statement. 121 if line.ends_with(";") { 122 import_of_interest = None; 123 } 124 } 125 126 // If the file has a lock import "imbalance", print it out and increment the counter. 127 assert!( 128 lock_balance == 0, 129 "The locks in \"{}\" don't seem to have `locktick` counterparts!", 130 entry.path().display() 131 ); 132 } 133 } 134 135 fn check_file_licenses<P: AsRef<Path>>(path: P) { 136 let path = path.as_ref(); 137 138 let mut iter = WalkDir::new(path).into_iter(); 139 while let Some(entry) = iter.next() { 140 let entry = entry.unwrap(); 141 let entry_type = entry.file_type(); 142 143 // Skip the specified directories. 144 if entry_type.is_dir() && DIRS_TO_SKIP.contains(&entry.file_name().to_str().unwrap_or("")) { 145 iter.skip_current_dir(); 146 147 continue; 148 } 149 150 // Check all files with the ".rs" extension. 151 if entry_type.is_file() && entry.file_name().to_str().unwrap_or("").ends_with(".rs") { 152 let file = File::open(entry.path()).unwrap(); 153 let mut contents = Vec::with_capacity(EXPECTED_LICENSE_TEXT.len()); 154 file.take(EXPECTED_LICENSE_TEXT.len() as u64).read_to_end(&mut contents).unwrap(); 155 156 assert!( 157 contents == EXPECTED_LICENSE_TEXT, 158 "The license in \"{}\" is either missing or it doesn't match the expected string!", 159 entry.path().display() 160 ); 161 } 162 } 163 } 164 165 // The build script; it currently only checks the licenses. 166 fn main() { 167 // Check licenses in the current folder. 168 check_file_licenses("."); 169 // Ensure that lock imports have locktick counterparts. 170 check_locktick_imports("."); 171 }