xtask\tasks\fmt\house_rules/
copyright.rs1use anyhow::anyhow;
5use fs_err::File;
6use std::io::BufRead;
7use std::io::BufReader;
8use std::io::Read;
9use std::io::Write;
10use std::path::Path;
11
12fn commit(source: File, target: &Path) -> std::io::Result<()> {
13 source.set_permissions(target.metadata()?.permissions())?;
14 let (file, path) = source.into_parts();
15 drop(file); fs_err::rename(path, target)
17}
18
19pub fn check_copyright(path: &Path, fix: bool) -> anyhow::Result<()> {
20 const HEADER_MIT_FIRST: &str = "Copyright (c) Microsoft Corporation.";
21 const HEADER_MIT_SECOND: &str = "Licensed under the MIT License.";
22
23 let ext = path
24 .extension()
25 .and_then(|e| e.to_str())
26 .unwrap_or_default();
27
28 if !matches!(
29 ext,
30 "rs" | "c" | "proto" | "toml" | "ts" | "js" | "py" | "ps1"
31 ) {
32 return Ok(());
33 }
34
35 let f = BufReader::new(File::open(path)?);
36 let mut lines = f.lines();
37 let (script_interpreter_line, blank_after_script_interpreter_line, first_content_line) = {
38 let line = lines.next().unwrap_or(Ok(String::new()))?;
39 if line.starts_with("#!") && ext != "rs" {
46 let script_interpreter_line = line;
47 let after_script_interpreter_line = lines.next().unwrap_or(Ok(String::new()))?;
48 (
49 Some(script_interpreter_line),
50 Some(after_script_interpreter_line.is_empty()),
51 lines.next().unwrap_or(Ok(String::new()))?,
52 )
53 } else {
54 (None, None, line)
55 }
56 };
57 let second_content_line = lines.next().unwrap_or(Ok(String::new()))?;
58 let third_content_line = lines.next().unwrap_or(Ok(String::new()))?;
59
60 if first_content_line.contains("Copyright") && !first_content_line.contains("Microsoft") {
62 return Ok(());
63 }
64
65 let mut missing_banner = !first_content_line.contains(HEADER_MIT_FIRST)
66 || !second_content_line.contains(HEADER_MIT_SECOND);
67 let mut missing_blank_line = !third_content_line.is_empty();
68 let mut header_lines = 2;
69
70 let is_msft_internal = std::env::var("XTASK_FMT_COPYRIGHT_ALLOW_MISSING_MIT").is_ok();
76 if is_msft_internal && missing_banner {
77 missing_banner =
79 !(first_content_line.contains("Copyright") && first_content_line.contains("Microsoft"));
80 missing_blank_line = !second_content_line.is_empty();
81 header_lines = 1;
82 }
83
84 if fix {
85 drop(lines);
88
89 if missing_banner || missing_blank_line {
90 let path_fix = &{
91 let mut p = path.to_path_buf();
92 let ok = p.set_extension(format!("{}.fix", ext));
93 assert!(ok);
94 p
95 };
96
97 let mut f = BufReader::new(File::open(path)?);
98 let mut f_fixed = File::create(path_fix)?;
99
100 if let Some(script_interpreter_line) = &script_interpreter_line {
101 writeln!(f_fixed, "{script_interpreter_line}")?;
102 f.read_line(&mut String::new())?;
103 }
104 if let Some(blank_after_script_interpreter_line) = blank_after_script_interpreter_line {
105 if !blank_after_script_interpreter_line {
106 writeln!(f_fixed)?;
107 }
108 }
109
110 if missing_banner {
111 let prefix = match ext {
112 "rs" | "c" | "proto" | "ts" | "js" => "//",
113 "toml" | "py" | "ps1" | "config" => "#",
114 _ => unreachable!(),
115 };
116
117 if script_interpreter_line.is_none() && first_content_line.starts_with('\u{feff}') {
119 write!(f_fixed, "\u{feff}")?;
120 f.read_exact(&mut [0; 3])?;
122 }
123
124 writeln!(f_fixed, "{} {}", prefix, HEADER_MIT_FIRST)?;
125 if !is_msft_internal {
126 writeln!(f_fixed, "{} {}", prefix, HEADER_MIT_SECOND)?;
127 }
128
129 writeln!(f_fixed)?; } else if missing_blank_line {
131 for _ in 0..header_lines {
133 let mut s = String::new();
134 f.read_line(&mut s)?;
135 write!(f_fixed, "{}", s)?;
136 }
137
138 writeln!(f_fixed)?;
140 }
141
142 std::io::copy(&mut f, &mut f_fixed)?;
144
145 drop(f);
148 commit(f_fixed, path)?;
149 }
150 }
151
152 let mut missing = vec![];
155 if missing_banner {
156 missing.push("the copyright & license header");
157 }
158 if missing_blank_line {
159 missing.push("a blank line after the copyright & license header");
160 }
161 if let Some(blank_after_script_interpreter_line) = blank_after_script_interpreter_line {
162 if !blank_after_script_interpreter_line {
163 missing.push("a blank line after the script interpreter line");
164 }
165 }
166
167 if missing.is_empty() {
168 return Ok(());
169 }
170
171 if fix {
172 log::info!(
173 "applied fixes for missing {:?} in {}",
174 missing,
175 path.display()
176 );
177 Ok(())
178 } else {
179 Err(anyhow!("missing {:?} in {}", missing, path.display()))
180 }
181}