Mercurial > hg > truffle
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.max.base/src/com/sun/max/tools/CheckCopyright.java Sat Dec 17 19:59:18 2011 +0100 @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2011, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.max.tools; + +import java.io.*; +import java.util.*; +import java.util.regex.*; + +import com.sun.max.ide.*; +import com.sun.max.program.*; +import com.sun.max.program.option.*; + +/** + * A program to check the existence and correctness of the copyright notice on a given set of Maxine sources. + * Sources are defined to be those under management by Mercurial and various options are available + * to limit the set of sources scanned. + */ + +public class CheckCopyright { + + static class YearInfo { + + final int firstYear; + final int lastYear; + + YearInfo(int firstYear, int lastYear) { + this.firstYear = firstYear; + this.lastYear = lastYear; + } + + @Override + public boolean equals(Object other) { + final YearInfo yearInfo = (YearInfo) other; + return yearInfo.firstYear == firstYear && yearInfo.lastYear == lastYear; + } + + @Override + public int hashCode() { + return firstYear ^ lastYear; + } + } + + static class Info extends YearInfo { + + final String fileName; + + Info(String fileName, int firstYear, int lastYear) { + super(firstYear, lastYear); + this.fileName = fileName; + } + + @Override + public String toString() { + return fileName + " " + firstYear + ", " + lastYear; + } + } + + enum CopyrightKind { + STAR("star"), + HASH("hash"); + + private static Map<String, CopyrightKind> copyrightMap; + private static final String COPYRIGHT_REGEX = "com.oracle.max.base/.copyright.regex"; + private static String copyrightFiles = "bin/max|.*/makefile|.*/Makefile|.*\\.sh|.*\\.bash|.*\\.mk|.*\\.java|.*\\.c|.*\\.h"; + private static Pattern copyrightFilePattern; + private final String suffix; + private String copyright; + Pattern copyrightPattern; + + CopyrightKind(String suffix) { + this.suffix = suffix; + } + + static void addCopyrightFilesPattern(String pattern) { + copyrightFiles += "|" + pattern; + } + + void readCopyright() throws IOException { + final File file = new File(workSpaceDirectory, COPYRIGHT_REGEX + "." + suffix); + assert file.exists(); + byte[] b = new byte[(int) file.length()]; + FileInputStream is = new FileInputStream(file); + is.read(b); + is.close(); + copyright = new String(b); + copyrightPattern = Pattern.compile(copyright, Pattern.DOTALL); + } + + /** + * Returns a matcher for the modification year from copyright. + * + * @param fileContent + * @return modification year matcher or null if copyright not expected + */ + static Matcher getCopyrightMatcher(String fileName, String fileContent) { + if (copyrightMap == null) { + copyrightFilePattern = Pattern.compile(copyrightFiles); + copyrightMap = new HashMap<String, CopyrightKind>(); + copyrightMap.put("java", CopyrightKind.STAR); + copyrightMap.put("c", CopyrightKind.STAR); + copyrightMap.put("h", CopyrightKind.STAR); + copyrightMap.put("mk", CopyrightKind.HASH); + copyrightMap.put("sh", CopyrightKind.HASH); + copyrightMap.put("bash", CopyrightKind.HASH); + copyrightMap.put("", CopyrightKind.HASH); + } + if (!copyrightFilePattern.matcher(fileName).matches()) { + return null; + } + final String extension = getExtension(fileName); + CopyrightKind ck = copyrightMap.get(extension); + assert ck != null : fileName; + return ck.copyrightPattern.matcher(fileContent); + } + + private static String getExtension(String fileName) { + int index = fileName.lastIndexOf(File.separatorChar); + if (index > 0) { + fileName = fileName.substring(index + 1); + } + index = fileName.lastIndexOf('.'); + if (index > 0) { + return fileName.substring(index + 1); + } + if (fileName.equals("makefile")) { + return "mk"; + } + return ""; + } + } + + private static List<YearInfo> infoList = new ArrayList<YearInfo>(); + private static int currentYear = Calendar.getInstance().get(Calendar.YEAR); + private static final OptionSet options = new OptionSet(true); + private static final Option<Boolean> help = options.newBooleanOption("help", false, "Show help message and exit."); + private static final Option<List<String>> FILES_TO_CHECK = options.newStringListOption("files", + null, ',', "list of files to check"); + private static final Option<String> FILE_LIST = options.newStringOption("filelist", + null, "file containing list of files to check"); + private static final Option<Boolean> HG_ALL = options.newBooleanOption("all", false, "check all hg managed files requiring a copyright (hg status --all)"); + private static final Option<Boolean> HG_MODIFIED = options.newBooleanOption("modified", false, "check all modified hg managed files requiring a copyright (hg status)"); + private static final Option<Boolean> HG_OUTGOING = options.newBooleanOption("outgoing", false, "check outgoing hg managed files requiring a copyright (hg outgoing)"); + 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)"); + private static final Option<List<String>> PROJECT = options.newStringListOption("projects", null, ',', "filter files to specific projects"); + private static final Option<String> OUTGOING_REPO = options.newStringOption("repo", null, "override outgoing repository"); + private static final Option<Boolean> EXHAUSTIVE = options.newBooleanOption("exhaustive", false, "check all hg managed files"); + private static final Option<Boolean> FIX = options.newBooleanOption("fix", false, "fix copyright errors"); + private static final Option<String> FILE_PATTERN = options.newStringOption("filepattern", null, "append additiional file patterns for copyright checks"); + private static final Option<Boolean> REPORT_ERRORS = options.newBooleanOption("reporterrors", true, "report non-fatal errors"); + private static final Option<Boolean> CONTINUE_ON_ERROR = options.newBooleanOption("continueonerror", false, "continue after normally fatal error"); + private static final Option<String> HG_PATH = options.newStringOption("hgpath", "hg", "path to hg executable"); + private static final String NON_EXISTENT_FILE = "abort: cannot follow nonexistent file:"; + private static String hgPath; + private static boolean error; + private static File workSpaceDirectory; + + + public static void main(String[] args) { + Trace.addTo(options); + // parse the arguments + options.parseArguments(args).getArguments(); + if (help.getValue()) { + options.printHelp(System.out, 100); + return; + } + + hgPath = HG_PATH.getValue(); + + workSpaceDirectory = JavaProject.findWorkspaceDirectory(); + + if (FILE_PATTERN.getValue() != null) { + CopyrightKind.addCopyrightFilesPattern(FILE_PATTERN.getValue()); + } + + try { + CopyrightKind.STAR.readCopyright(); + CopyrightKind.HASH.readCopyright(); + List<String> filesToCheck = null; + if (HG_ALL.getValue()) { + filesToCheck = getAllFiles(true); + } else if (HG_OUTGOING.getValue()) { + filesToCheck = getOutgoingFiles(); + } else if (HG_MODIFIED.getValue()) { + filesToCheck = getAllFiles(false); + } else if (HG_LOG.getValue() > 0) { + filesToCheck = getLastNFiles(HG_LOG.getValue()); + } else if (FILE_LIST.getValue() != null) { + filesToCheck = readFileList(FILE_LIST.getValue()); + } else { + filesToCheck = FILES_TO_CHECK.getValue(); + } + if (filesToCheck != null && filesToCheck.size() > 0) { + processFiles(filesToCheck); + } else { + System.out.println("nothing to check"); + } + System.exit(error ? 1 : 0); + } catch (Exception ex) { + throw ProgramError.unexpected("processing failed", ex); + } + } + + private static void processFiles(List<String> fileNames) throws Exception { + final List<String> projects = PROJECT.getValue(); + for (String fileName : fileNames) { + if (projects == null || isInProjects(fileName, projects)) { + Trace.line(1, "checking " + fileName); + try { + final List<String> logInfo = hglog(fileName); + final Info info = getInfo(fileName, true, logInfo); + checkFile(fileName, info); + } catch (ProgramError e) { + System.err.println("COPYRIGHT CHECK WARNING: error while processing " + fileName); + } + } + } + } + + private static boolean isInProjects(String fileName, List<String> projects) { + final int ix = fileName.indexOf(File.separatorChar); + if (ix < 0) { + return false; + } + final String fileProject = fileName.substring(0, ix); + for (String project : projects) { + if (fileProject.equals(project)) { + return true; + } + } + return false; + } + + private static List<String> readFileList(String fileListName) throws IOException { + final List<String> result = new ArrayList<String>(); + BufferedReader b = null; + try { + b = new BufferedReader(new FileReader(fileListName)); + while (true) { + final String fileName = b.readLine(); + if (fileName == null) { + break; + } + if (fileName.length() == 0) { + continue; + } + result.add(fileName); + } + } finally { + if (b != null) { + b.close(); + } + } + return result; + } + + private static Info getInfo(String fileName, boolean lastOnly, List<String> logInfo) { + // process sequence of changesets + int lastYear = 0; + int firstYear = 0; + String summary = null; + int ix = 0; + + while (ix < logInfo.size()) { + String s = logInfo.get(ix++); + assert s.startsWith("changeset"); + s = logInfo.get(ix++); + // process every entry in a given change set + if (s.startsWith("tag")) { + s = logInfo.get(ix++); + } + if (s.startsWith("branch")) { + s = logInfo.get(ix++); + } + while (s.startsWith("parent")) { + s = logInfo.get(ix++); + } + assert s.startsWith("user"); + s = logInfo.get(ix++); + assert s.startsWith("date"); + final int csYear = getYear(s); + summary = logInfo.get(ix++); + assert summary.startsWith("summary"); + s = logInfo.get(ix++); // blank + assert s.length() == 0; + if (lastYear == 0 && summary.contains("change all copyright notices from Sun to Oracle")) { + // special case of last change being the copyright change, which didn't + // count as a change of last modification date! + continue; + } + if (lastYear == 0) { + lastYear = csYear; + firstYear = lastYear; + } else { + firstYear = csYear; + } + // if we only want the last modified year, quit now + if (lastOnly) { + break; + } + + } + + // Special case + if (summary != null && summary.contains("Initial commit of VM sources")) { + firstYear = 2007; + } + if (HG_MODIFIED.getValue()) { + // We are only looking at modified and, therefore, uncommitted files. + // This means that the lastYear value will be the current year once the + // file is committed, so that is what we want to check against. + lastYear = currentYear; + } + return new Info(fileName, firstYear, lastYear); + } + + private static int getYear(String dateLine) { + final String[] parts = dateLine.split(" "); + assert parts[parts.length - 2].startsWith("20"); + return Integer.parseInt(parts[parts.length - 2]); + } + + private static void checkFile(String c, Info info) throws IOException { + String fileName = info.fileName; + File file = new File(fileName); + if (!file.exists()) { + System.err.println("COPYRIGHT CHECK WARNING: file " + file + " doesn't exist"); + return; + } + int fileLength = (int) file.length(); + byte[] b = new byte[fileLength]; + FileInputStream is = new FileInputStream(file); + is.read(b); + is.close(); + final String fileContent = new String(b); + Matcher copyrightMatcher = CopyrightKind.getCopyrightMatcher(fileName, fileContent); + if (copyrightMatcher != null) { + if (copyrightMatcher.matches()) { + int yearInCopyright; + int yearInCopyrightIndex; + yearInCopyright = Integer.parseInt(copyrightMatcher.group(2)); + yearInCopyrightIndex = copyrightMatcher.start(2); + if (yearInCopyright != info.lastYear) { + System.out.println(fileName + " copyright last modified year " + yearInCopyright + ", hg last modified year " + info.lastYear); + if (FIX.getValue()) { + // Use currentYear as that is what it will be when it's checked in! + System.out.println("updating last modified year of " + fileName + " to " + currentYear); + final int lx = yearInCopyrightIndex; + final String newContent = fileContent.substring(0, lx) + info.lastYear + fileContent.substring(lx + 4); + final FileOutputStream os = new FileOutputStream(file); + os.write(newContent.getBytes()); + os.close(); + } else { + error = true; + } + } + } else { + System.out.println("ERROR: file " + fileName + " has no copyright"); + error = true; + } + } else if (EXHAUSTIVE.getValue()) { + System.out.println("ERROR: file " + fileName + " has no copyright"); + error = true; + } + } + + + private static List<String> hglog(String fileName) throws Exception { + final String[] cmd = new String[] {hgPath, "log", "-f", fileName}; + return exec(null, cmd, true); + } + + private static List<String> getLastNFiles(int n) throws Exception { + final String[] cmd = new String[] {hgPath, "log", "-v", "-l", Integer.toString(n)}; + return getFilesFiles(exec(null, cmd, false)); + } + + private static List<String> getAllFiles(boolean all) throws Exception { + final String[] cmd; + if (HG_MODIFIED.getValue()) { + cmd = new String[] {hgPath, "status"}; + } else { + cmd = new String[] {hgPath, "status", "--all"}; + } + List<String> output = exec(null, cmd, true); + final List<String> result = new ArrayList<String>(output.size()); + for (String s : output) { + final char ch = s.charAt(0); + if (!(ch == 'R' || ch == 'I' || ch == '?' || ch == '!')) { + result.add(s.substring(2)); + } + } + return result; + } + + private static List<String> getOutgoingFiles() throws Exception { + final String[] cmd; + if (OUTGOING_REPO.getValue() == null) { + cmd = new String[] {hgPath, "-v", "outgoing"}; + } else { + cmd = new String[] {hgPath, "-v", "outgoing", OUTGOING_REPO.getValue()}; + } + + final List<String> output = exec(null, cmd, false); // no outgoing exits with result 1 + return getFilesFiles(output); + } + + private static List<String> getFilesFiles(List<String> output) { + // there may be multiple changesets so merge the "files:" + final Map<String, String> outSet = new TreeMap<String, String>(); + for (String s : output) { + if (s.startsWith("files:")) { + int ix = s.indexOf(' '); + while (ix < s.length() && s.charAt(ix) == ' ') { + ix++; + } + final String[] files = s.substring(ix).split(" "); + for (String file : files) { + outSet.put(file, file); + } + } + } + return new ArrayList<String>(outSet.values()); + } + + private static List<String> exec(File workingDir, String[] command, boolean failOnError) throws IOException, InterruptedException { + List<String> result = new ArrayList<String>(); + if (Trace.hasLevel(2)) { + Trace.line(2, "Executing process in directory: " + workingDir); + for (String c : command) { + Trace.line(2, " " + c); + } + } + final Process process = Runtime.getRuntime().exec(command, null, workingDir); + try { + result = readOutput(process.getInputStream()); + final int exitValue = process.waitFor(); + if (exitValue != 0) { + final List<String> errorResult = readOutput(process.getErrorStream()); + if (REPORT_ERRORS.getValue()) { + System.err.print("execution of command: "); + for (String c : command) { + System.err.print(c); + System.err.print(' '); + } + System.err.println("failed with result " + exitValue); + for (String e : errorResult) { + System.err.println(e); + } + } + if (failOnError && !(CONTINUE_ON_ERROR.getValue() || cannotFollowNonExistentFile(errorResult))) { + throw ProgramError.unexpected("terminating"); + } + } + } finally { + process.destroy(); + } + return result; + } + + private static boolean cannotFollowNonExistentFile(List<String> errorResult) { + return errorResult.size() == 1 && errorResult.get(0).startsWith(NON_EXISTENT_FILE); + } + + private static List<String> readOutput(InputStream is) throws IOException { + final List<String> result = new ArrayList<String>(); + BufferedReader bs = null; + try { + bs = new BufferedReader(new InputStreamReader(is)); + while (true) { + final String line = bs.readLine(); + if (line == null) { + break; + } + result.add(line); + } + } finally { + if (bs != null) { + bs.close(); + } + } + return result; + } + +}