Mercurial > hg > truffle
comparison graal/com.oracle.max.base/src/com/sun/max/tools/CheckCopyright.java @ 3733:e233f5660da4
Added Java files from Maxine project.
author | Thomas Wuerthinger <thomas.wuerthinger@oracle.com> |
---|---|
date | Sat, 17 Dec 2011 19:59:18 +0100 |
parents | |
children | bc8527f3071c |
comparison
equal
deleted
inserted
replaced
3732:3e2e8b8abdaf | 3733:e233f5660da4 |
---|---|
1 /* | |
2 * Copyright (c) 2011, 2011, Oracle and/or its affiliates. All rights reserved. | |
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
4 * | |
5 * This code is free software; you can redistribute it and/or modify it | |
6 * under the terms of the GNU General Public License version 2 only, as | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * This code is distributed in the hope that it will be useful, but WITHOUT | |
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
12 * version 2 for more details (a copy is included in the LICENSE file that | |
13 * accompanied this code). | |
14 * | |
15 * You should have received a copy of the GNU General Public License version | |
16 * 2 along with this work; if not, write to the Free Software Foundation, | |
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
18 * | |
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
20 * or visit www.oracle.com if you need additional information or have any | |
21 * questions. | |
22 */ | |
23 package com.sun.max.tools; | |
24 | |
25 import java.io.*; | |
26 import java.util.*; | |
27 import java.util.regex.*; | |
28 | |
29 import com.sun.max.ide.*; | |
30 import com.sun.max.program.*; | |
31 import com.sun.max.program.option.*; | |
32 | |
33 /** | |
34 * A program to check the existence and correctness of the copyright notice on a given set of Maxine sources. | |
35 * Sources are defined to be those under management by Mercurial and various options are available | |
36 * to limit the set of sources scanned. | |
37 */ | |
38 | |
39 public class CheckCopyright { | |
40 | |
41 static class YearInfo { | |
42 | |
43 final int firstYear; | |
44 final int lastYear; | |
45 | |
46 YearInfo(int firstYear, int lastYear) { | |
47 this.firstYear = firstYear; | |
48 this.lastYear = lastYear; | |
49 } | |
50 | |
51 @Override | |
52 public boolean equals(Object other) { | |
53 final YearInfo yearInfo = (YearInfo) other; | |
54 return yearInfo.firstYear == firstYear && yearInfo.lastYear == lastYear; | |
55 } | |
56 | |
57 @Override | |
58 public int hashCode() { | |
59 return firstYear ^ lastYear; | |
60 } | |
61 } | |
62 | |
63 static class Info extends YearInfo { | |
64 | |
65 final String fileName; | |
66 | |
67 Info(String fileName, int firstYear, int lastYear) { | |
68 super(firstYear, lastYear); | |
69 this.fileName = fileName; | |
70 } | |
71 | |
72 @Override | |
73 public String toString() { | |
74 return fileName + " " + firstYear + ", " + lastYear; | |
75 } | |
76 } | |
77 | |
78 enum CopyrightKind { | |
79 STAR("star"), | |
80 HASH("hash"); | |
81 | |
82 private static Map<String, CopyrightKind> copyrightMap; | |
83 private static final String COPYRIGHT_REGEX = "com.oracle.max.base/.copyright.regex"; | |
84 private static String copyrightFiles = "bin/max|.*/makefile|.*/Makefile|.*\\.sh|.*\\.bash|.*\\.mk|.*\\.java|.*\\.c|.*\\.h"; | |
85 private static Pattern copyrightFilePattern; | |
86 private final String suffix; | |
87 private String copyright; | |
88 Pattern copyrightPattern; | |
89 | |
90 CopyrightKind(String suffix) { | |
91 this.suffix = suffix; | |
92 } | |
93 | |
94 static void addCopyrightFilesPattern(String pattern) { | |
95 copyrightFiles += "|" + pattern; | |
96 } | |
97 | |
98 void readCopyright() throws IOException { | |
99 final File file = new File(workSpaceDirectory, COPYRIGHT_REGEX + "." + suffix); | |
100 assert file.exists(); | |
101 byte[] b = new byte[(int) file.length()]; | |
102 FileInputStream is = new FileInputStream(file); | |
103 is.read(b); | |
104 is.close(); | |
105 copyright = new String(b); | |
106 copyrightPattern = Pattern.compile(copyright, Pattern.DOTALL); | |
107 } | |
108 | |
109 /** | |
110 * Returns a matcher for the modification year from copyright. | |
111 * | |
112 * @param fileContent | |
113 * @return modification year matcher or null if copyright not expected | |
114 */ | |
115 static Matcher getCopyrightMatcher(String fileName, String fileContent) { | |
116 if (copyrightMap == null) { | |
117 copyrightFilePattern = Pattern.compile(copyrightFiles); | |
118 copyrightMap = new HashMap<String, CopyrightKind>(); | |
119 copyrightMap.put("java", CopyrightKind.STAR); | |
120 copyrightMap.put("c", CopyrightKind.STAR); | |
121 copyrightMap.put("h", CopyrightKind.STAR); | |
122 copyrightMap.put("mk", CopyrightKind.HASH); | |
123 copyrightMap.put("sh", CopyrightKind.HASH); | |
124 copyrightMap.put("bash", CopyrightKind.HASH); | |
125 copyrightMap.put("", CopyrightKind.HASH); | |
126 } | |
127 if (!copyrightFilePattern.matcher(fileName).matches()) { | |
128 return null; | |
129 } | |
130 final String extension = getExtension(fileName); | |
131 CopyrightKind ck = copyrightMap.get(extension); | |
132 assert ck != null : fileName; | |
133 return ck.copyrightPattern.matcher(fileContent); | |
134 } | |
135 | |
136 private static String getExtension(String fileName) { | |
137 int index = fileName.lastIndexOf(File.separatorChar); | |
138 if (index > 0) { | |
139 fileName = fileName.substring(index + 1); | |
140 } | |
141 index = fileName.lastIndexOf('.'); | |
142 if (index > 0) { | |
143 return fileName.substring(index + 1); | |
144 } | |
145 if (fileName.equals("makefile")) { | |
146 return "mk"; | |
147 } | |
148 return ""; | |
149 } | |
150 } | |
151 | |
152 private static List<YearInfo> infoList = new ArrayList<YearInfo>(); | |
153 private static int currentYear = Calendar.getInstance().get(Calendar.YEAR); | |
154 private static final OptionSet options = new OptionSet(true); | |
155 private static final Option<Boolean> help = options.newBooleanOption("help", false, "Show help message and exit."); | |
156 private static final Option<List<String>> FILES_TO_CHECK = options.newStringListOption("files", | |
157 null, ',', "list of files to check"); | |
158 private static final Option<String> FILE_LIST = options.newStringOption("filelist", | |
159 null, "file containing list of files to check"); | |
160 private static final Option<Boolean> HG_ALL = options.newBooleanOption("all", false, "check all hg managed files requiring a copyright (hg status --all)"); | |
161 private static final Option<Boolean> HG_MODIFIED = options.newBooleanOption("modified", false, "check all modified hg managed files requiring a copyright (hg status)"); | |
162 private static final Option<Boolean> HG_OUTGOING = options.newBooleanOption("outgoing", false, "check outgoing hg managed files requiring a copyright (hg outgoing)"); | |
163 private static final Option<Integer> HG_LOG = options.newIntegerOption("last", 0, "check hg managed files requiring a copyright in last N changesets (hg log -l N)"); | |
164 private static final Option<List<String>> PROJECT = options.newStringListOption("projects", null, ',', "filter files to specific projects"); | |
165 private static final Option<String> OUTGOING_REPO = options.newStringOption("repo", null, "override outgoing repository"); | |
166 private static final Option<Boolean> EXHAUSTIVE = options.newBooleanOption("exhaustive", false, "check all hg managed files"); | |
167 private static final Option<Boolean> FIX = options.newBooleanOption("fix", false, "fix copyright errors"); | |
168 private static final Option<String> FILE_PATTERN = options.newStringOption("filepattern", null, "append additiional file patterns for copyright checks"); | |
169 private static final Option<Boolean> REPORT_ERRORS = options.newBooleanOption("reporterrors", true, "report non-fatal errors"); | |
170 private static final Option<Boolean> CONTINUE_ON_ERROR = options.newBooleanOption("continueonerror", false, "continue after normally fatal error"); | |
171 private static final Option<String> HG_PATH = options.newStringOption("hgpath", "hg", "path to hg executable"); | |
172 private static final String NON_EXISTENT_FILE = "abort: cannot follow nonexistent file:"; | |
173 private static String hgPath; | |
174 private static boolean error; | |
175 private static File workSpaceDirectory; | |
176 | |
177 | |
178 public static void main(String[] args) { | |
179 Trace.addTo(options); | |
180 // parse the arguments | |
181 options.parseArguments(args).getArguments(); | |
182 if (help.getValue()) { | |
183 options.printHelp(System.out, 100); | |
184 return; | |
185 } | |
186 | |
187 hgPath = HG_PATH.getValue(); | |
188 | |
189 workSpaceDirectory = JavaProject.findWorkspaceDirectory(); | |
190 | |
191 if (FILE_PATTERN.getValue() != null) { | |
192 CopyrightKind.addCopyrightFilesPattern(FILE_PATTERN.getValue()); | |
193 } | |
194 | |
195 try { | |
196 CopyrightKind.STAR.readCopyright(); | |
197 CopyrightKind.HASH.readCopyright(); | |
198 List<String> filesToCheck = null; | |
199 if (HG_ALL.getValue()) { | |
200 filesToCheck = getAllFiles(true); | |
201 } else if (HG_OUTGOING.getValue()) { | |
202 filesToCheck = getOutgoingFiles(); | |
203 } else if (HG_MODIFIED.getValue()) { | |
204 filesToCheck = getAllFiles(false); | |
205 } else if (HG_LOG.getValue() > 0) { | |
206 filesToCheck = getLastNFiles(HG_LOG.getValue()); | |
207 } else if (FILE_LIST.getValue() != null) { | |
208 filesToCheck = readFileList(FILE_LIST.getValue()); | |
209 } else { | |
210 filesToCheck = FILES_TO_CHECK.getValue(); | |
211 } | |
212 if (filesToCheck != null && filesToCheck.size() > 0) { | |
213 processFiles(filesToCheck); | |
214 } else { | |
215 System.out.println("nothing to check"); | |
216 } | |
217 System.exit(error ? 1 : 0); | |
218 } catch (Exception ex) { | |
219 throw ProgramError.unexpected("processing failed", ex); | |
220 } | |
221 } | |
222 | |
223 private static void processFiles(List<String> fileNames) throws Exception { | |
224 final List<String> projects = PROJECT.getValue(); | |
225 for (String fileName : fileNames) { | |
226 if (projects == null || isInProjects(fileName, projects)) { | |
227 Trace.line(1, "checking " + fileName); | |
228 try { | |
229 final List<String> logInfo = hglog(fileName); | |
230 final Info info = getInfo(fileName, true, logInfo); | |
231 checkFile(fileName, info); | |
232 } catch (ProgramError e) { | |
233 System.err.println("COPYRIGHT CHECK WARNING: error while processing " + fileName); | |
234 } | |
235 } | |
236 } | |
237 } | |
238 | |
239 private static boolean isInProjects(String fileName, List<String> projects) { | |
240 final int ix = fileName.indexOf(File.separatorChar); | |
241 if (ix < 0) { | |
242 return false; | |
243 } | |
244 final String fileProject = fileName.substring(0, ix); | |
245 for (String project : projects) { | |
246 if (fileProject.equals(project)) { | |
247 return true; | |
248 } | |
249 } | |
250 return false; | |
251 } | |
252 | |
253 private static List<String> readFileList(String fileListName) throws IOException { | |
254 final List<String> result = new ArrayList<String>(); | |
255 BufferedReader b = null; | |
256 try { | |
257 b = new BufferedReader(new FileReader(fileListName)); | |
258 while (true) { | |
259 final String fileName = b.readLine(); | |
260 if (fileName == null) { | |
261 break; | |
262 } | |
263 if (fileName.length() == 0) { | |
264 continue; | |
265 } | |
266 result.add(fileName); | |
267 } | |
268 } finally { | |
269 if (b != null) { | |
270 b.close(); | |
271 } | |
272 } | |
273 return result; | |
274 } | |
275 | |
276 private static Info getInfo(String fileName, boolean lastOnly, List<String> logInfo) { | |
277 // process sequence of changesets | |
278 int lastYear = 0; | |
279 int firstYear = 0; | |
280 String summary = null; | |
281 int ix = 0; | |
282 | |
283 while (ix < logInfo.size()) { | |
284 String s = logInfo.get(ix++); | |
285 assert s.startsWith("changeset"); | |
286 s = logInfo.get(ix++); | |
287 // process every entry in a given change set | |
288 if (s.startsWith("tag")) { | |
289 s = logInfo.get(ix++); | |
290 } | |
291 if (s.startsWith("branch")) { | |
292 s = logInfo.get(ix++); | |
293 } | |
294 while (s.startsWith("parent")) { | |
295 s = logInfo.get(ix++); | |
296 } | |
297 assert s.startsWith("user"); | |
298 s = logInfo.get(ix++); | |
299 assert s.startsWith("date"); | |
300 final int csYear = getYear(s); | |
301 summary = logInfo.get(ix++); | |
302 assert summary.startsWith("summary"); | |
303 s = logInfo.get(ix++); // blank | |
304 assert s.length() == 0; | |
305 if (lastYear == 0 && summary.contains("change all copyright notices from Sun to Oracle")) { | |
306 // special case of last change being the copyright change, which didn't | |
307 // count as a change of last modification date! | |
308 continue; | |
309 } | |
310 if (lastYear == 0) { | |
311 lastYear = csYear; | |
312 firstYear = lastYear; | |
313 } else { | |
314 firstYear = csYear; | |
315 } | |
316 // if we only want the last modified year, quit now | |
317 if (lastOnly) { | |
318 break; | |
319 } | |
320 | |
321 } | |
322 | |
323 // Special case | |
324 if (summary != null && summary.contains("Initial commit of VM sources")) { | |
325 firstYear = 2007; | |
326 } | |
327 if (HG_MODIFIED.getValue()) { | |
328 // We are only looking at modified and, therefore, uncommitted files. | |
329 // This means that the lastYear value will be the current year once the | |
330 // file is committed, so that is what we want to check against. | |
331 lastYear = currentYear; | |
332 } | |
333 return new Info(fileName, firstYear, lastYear); | |
334 } | |
335 | |
336 private static int getYear(String dateLine) { | |
337 final String[] parts = dateLine.split(" "); | |
338 assert parts[parts.length - 2].startsWith("20"); | |
339 return Integer.parseInt(parts[parts.length - 2]); | |
340 } | |
341 | |
342 private static void checkFile(String c, Info info) throws IOException { | |
343 String fileName = info.fileName; | |
344 File file = new File(fileName); | |
345 if (!file.exists()) { | |
346 System.err.println("COPYRIGHT CHECK WARNING: file " + file + " doesn't exist"); | |
347 return; | |
348 } | |
349 int fileLength = (int) file.length(); | |
350 byte[] b = new byte[fileLength]; | |
351 FileInputStream is = new FileInputStream(file); | |
352 is.read(b); | |
353 is.close(); | |
354 final String fileContent = new String(b); | |
355 Matcher copyrightMatcher = CopyrightKind.getCopyrightMatcher(fileName, fileContent); | |
356 if (copyrightMatcher != null) { | |
357 if (copyrightMatcher.matches()) { | |
358 int yearInCopyright; | |
359 int yearInCopyrightIndex; | |
360 yearInCopyright = Integer.parseInt(copyrightMatcher.group(2)); | |
361 yearInCopyrightIndex = copyrightMatcher.start(2); | |
362 if (yearInCopyright != info.lastYear) { | |
363 System.out.println(fileName + " copyright last modified year " + yearInCopyright + ", hg last modified year " + info.lastYear); | |
364 if (FIX.getValue()) { | |
365 // Use currentYear as that is what it will be when it's checked in! | |
366 System.out.println("updating last modified year of " + fileName + " to " + currentYear); | |
367 final int lx = yearInCopyrightIndex; | |
368 final String newContent = fileContent.substring(0, lx) + info.lastYear + fileContent.substring(lx + 4); | |
369 final FileOutputStream os = new FileOutputStream(file); | |
370 os.write(newContent.getBytes()); | |
371 os.close(); | |
372 } else { | |
373 error = true; | |
374 } | |
375 } | |
376 } else { | |
377 System.out.println("ERROR: file " + fileName + " has no copyright"); | |
378 error = true; | |
379 } | |
380 } else if (EXHAUSTIVE.getValue()) { | |
381 System.out.println("ERROR: file " + fileName + " has no copyright"); | |
382 error = true; | |
383 } | |
384 } | |
385 | |
386 | |
387 private static List<String> hglog(String fileName) throws Exception { | |
388 final String[] cmd = new String[] {hgPath, "log", "-f", fileName}; | |
389 return exec(null, cmd, true); | |
390 } | |
391 | |
392 private static List<String> getLastNFiles(int n) throws Exception { | |
393 final String[] cmd = new String[] {hgPath, "log", "-v", "-l", Integer.toString(n)}; | |
394 return getFilesFiles(exec(null, cmd, false)); | |
395 } | |
396 | |
397 private static List<String> getAllFiles(boolean all) throws Exception { | |
398 final String[] cmd; | |
399 if (HG_MODIFIED.getValue()) { | |
400 cmd = new String[] {hgPath, "status"}; | |
401 } else { | |
402 cmd = new String[] {hgPath, "status", "--all"}; | |
403 } | |
404 List<String> output = exec(null, cmd, true); | |
405 final List<String> result = new ArrayList<String>(output.size()); | |
406 for (String s : output) { | |
407 final char ch = s.charAt(0); | |
408 if (!(ch == 'R' || ch == 'I' || ch == '?' || ch == '!')) { | |
409 result.add(s.substring(2)); | |
410 } | |
411 } | |
412 return result; | |
413 } | |
414 | |
415 private static List<String> getOutgoingFiles() throws Exception { | |
416 final String[] cmd; | |
417 if (OUTGOING_REPO.getValue() == null) { | |
418 cmd = new String[] {hgPath, "-v", "outgoing"}; | |
419 } else { | |
420 cmd = new String[] {hgPath, "-v", "outgoing", OUTGOING_REPO.getValue()}; | |
421 } | |
422 | |
423 final List<String> output = exec(null, cmd, false); // no outgoing exits with result 1 | |
424 return getFilesFiles(output); | |
425 } | |
426 | |
427 private static List<String> getFilesFiles(List<String> output) { | |
428 // there may be multiple changesets so merge the "files:" | |
429 final Map<String, String> outSet = new TreeMap<String, String>(); | |
430 for (String s : output) { | |
431 if (s.startsWith("files:")) { | |
432 int ix = s.indexOf(' '); | |
433 while (ix < s.length() && s.charAt(ix) == ' ') { | |
434 ix++; | |
435 } | |
436 final String[] files = s.substring(ix).split(" "); | |
437 for (String file : files) { | |
438 outSet.put(file, file); | |
439 } | |
440 } | |
441 } | |
442 return new ArrayList<String>(outSet.values()); | |
443 } | |
444 | |
445 private static List<String> exec(File workingDir, String[] command, boolean failOnError) throws IOException, InterruptedException { | |
446 List<String> result = new ArrayList<String>(); | |
447 if (Trace.hasLevel(2)) { | |
448 Trace.line(2, "Executing process in directory: " + workingDir); | |
449 for (String c : command) { | |
450 Trace.line(2, " " + c); | |
451 } | |
452 } | |
453 final Process process = Runtime.getRuntime().exec(command, null, workingDir); | |
454 try { | |
455 result = readOutput(process.getInputStream()); | |
456 final int exitValue = process.waitFor(); | |
457 if (exitValue != 0) { | |
458 final List<String> errorResult = readOutput(process.getErrorStream()); | |
459 if (REPORT_ERRORS.getValue()) { | |
460 System.err.print("execution of command: "); | |
461 for (String c : command) { | |
462 System.err.print(c); | |
463 System.err.print(' '); | |
464 } | |
465 System.err.println("failed with result " + exitValue); | |
466 for (String e : errorResult) { | |
467 System.err.println(e); | |
468 } | |
469 } | |
470 if (failOnError && !(CONTINUE_ON_ERROR.getValue() || cannotFollowNonExistentFile(errorResult))) { | |
471 throw ProgramError.unexpected("terminating"); | |
472 } | |
473 } | |
474 } finally { | |
475 process.destroy(); | |
476 } | |
477 return result; | |
478 } | |
479 | |
480 private static boolean cannotFollowNonExistentFile(List<String> errorResult) { | |
481 return errorResult.size() == 1 && errorResult.get(0).startsWith(NON_EXISTENT_FILE); | |
482 } | |
483 | |
484 private static List<String> readOutput(InputStream is) throws IOException { | |
485 final List<String> result = new ArrayList<String>(); | |
486 BufferedReader bs = null; | |
487 try { | |
488 bs = new BufferedReader(new InputStreamReader(is)); | |
489 while (true) { | |
490 final String line = bs.readLine(); | |
491 if (line == null) { | |
492 break; | |
493 } | |
494 result.add(line); | |
495 } | |
496 } finally { | |
497 if (bs != null) { | |
498 bs.close(); | |
499 } | |
500 } | |
501 return result; | |
502 } | |
503 | |
504 } |