# HG changeset patch # User Doug Simon # Date 1432329077 -7200 # Node ID c190ed6b84bff0616fae5a9cd28a15e203216900 # Parent a64d09dc45908921478de30acc386f30b373396c added checkcopyrights command (from mxtool2) diff -r a64d09dc4590 -r c190ed6b84bf mxtool/CheckCopyright.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mxtool/CheckCopyright.java Fri May 22 23:11:17 2015 +0200 @@ -0,0 +1,918 @@ +/* + * Copyright (c) 2011, 2015, 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. + */ +import java.io.*; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.*; +import java.util.regex.*; + + +/** + * A program to check the existence and correctness of the copyright notice on a given set of 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; + } + } + + private static abstract class CopyrightHandler { + enum CommentType{ + STAR, HASH + } + + private static Map copyrightMap; + private static String copyrightFiles = ".*/makefile|.*/Makefile|.*\\.sh|.*\\.bash|.*\\.mk|.*\\.java|.*\\.c|.*\\.h|.*\\.py|.*\\.g|.*\\.r"; + private static Pattern copyrightFilePattern; + + protected final String suffix; + private CopyrightHandler customHandler; + + CopyrightHandler(CommentType commentType) { + this.suffix = commentType.name().toLowerCase(); + initCopyrightMap(); + } + + void addCustomhandler(CopyrightHandler copyrightHandler) { + this.customHandler = copyrightHandler; + } + + /** + * Add @code extension to files handled by this {@code CopyrightKind} + */ + protected void updateMap(String extension) { + copyrightMap.put(extension, this); + } + + static void addCopyrightFilesPattern(String pattern) { + copyrightFiles += "|" + pattern; + } + + protected abstract void readCopyrights() throws IOException; + + protected abstract Matcher getMatcher(String fileName, String fileContent) throws IOException; + + protected abstract String getText(String fileName) throws IOException ; + + protected abstract boolean handlesFile(String fileName); + + /** + * Checks that the Oracle copyright year info was correct. + * @return {@code false} if the year info was incorrect and was not fixed otherwise return {@code true} + * @throws IOException + */ + protected abstract boolean checkYearInfo(String fileName, String fileContent, Matcher matcher, Info info) throws IOException; + + static String getCopyrightText(String fileName) throws IOException { + return getCopyrightHandler(fileName).getText(fileName); + } + + private static CopyrightHandler getCopyrightHandler(String fileName) { + initCopyrightMap(); + if (!copyrightFilePattern.matcher(fileName).matches()) { + return null; + } + CopyrightHandler ck = getDefaultHandler(fileName); + if (ck.customHandler != null && ck.customHandler.handlesFile(fileName)) { + return ck.customHandler; + } else { + return ck; + } + } + + private static void initCopyrightMap() { + if (copyrightMap == null) { + copyrightMap = new HashMap(); + copyrightFilePattern = Pattern.compile(copyrightFiles); + } + } + + static CopyrightHandler getDefaultHandler(String fileName) { + int index = fileName.lastIndexOf(File.separatorChar); + if (index > 0) { + fileName = fileName.substring(index + 1); + } + String ext = ""; + index = fileName.lastIndexOf('.'); + if (index > 0) { + ext = fileName.substring(index + 1); + } + if (fileName.equals("makefile")) { + ext = "mk"; + } + CopyrightHandler ck = copyrightMap.get(ext); + assert ck != null : fileName; + return ck; + } + + protected String readCopyright(InputStream is) throws IOException { + byte[] b = new byte[16384]; + int n = is.read(b); + is.close(); + return new String(b, 0, n); + } + + } + + private static class DefaultCopyrightHandler extends CopyrightHandler { + private static String ORACLE_COPYRIGHT = "oracle.copyright"; + private static String ORACLE_COPYRIGHT_REGEX = "oracle.copyright.regex"; + + private String copyrightRegex; + private String copyright; + Pattern copyrightPattern; + + DefaultCopyrightHandler(CopyrightHandler.CommentType commentType) throws IOException { + super(commentType); + if (commentType == CopyrightHandler.CommentType.STAR) { + updateMap("java"); + updateMap("c"); + updateMap("h"); + updateMap("g"); + } else { + updateMap("r"); + updateMap("R"); + updateMap("py"); + updateMap("sh"); + updateMap("mk"); + updateMap("bash"); + updateMap(""); + } + readCopyrights(); + } + + private String readCopyright(String name) throws IOException { + String copyRightDir = COPYRIGHT_DIR.getValue(); + String fileName = "copyrights/" + name + "." + suffix; + String copyrightPath; + if (copyRightDir != null) { + copyrightPath = new File(new File(copyRightDir), fileName).getAbsolutePath(); + } else { + URL url = CheckCopyright.class.getResource(fileName); + try { + copyrightPath = url.toURI().getPath(); + } catch (URISyntaxException ex) { + throw new IOException(ex); + } + } + InputStream is = new FileInputStream(copyrightPath); + return readCopyright(is); + } + + @Override + protected void readCopyrights() throws IOException { + copyright = readCopyright(ORACLE_COPYRIGHT); + copyrightRegex = readCopyright(ORACLE_COPYRIGHT_REGEX); + copyrightPattern = Pattern.compile(copyrightRegex, Pattern.DOTALL); + } + + @Override + protected Matcher getMatcher(String fileName, String fileContent) { + return copyrightPattern.matcher(fileContent); + } + + @Override + protected String getText(String fileName) { + return copyright; + } + + @Override + protected boolean handlesFile(String fileName) { + return true; + } + + /** + * Check the year info against the copyright header. + * N.B. In the case of multiple matching groups, only the last group is checked. + * I.e., only the last lines containing year info is checked/updated. + */ + @Override + protected boolean checkYearInfo(String fileName, String fileContent, Matcher matcher, Info info) throws IOException { + int yearInCopyright; + int yearInCopyrightIndex; + int groupCount = matcher.groupCount(); + String yearInCopyrightString = matcher.group(groupCount); + yearInCopyright = Integer.parseInt(yearInCopyrightString); + yearInCopyrightIndex = matcher.start(groupCount); + 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 " + info.lastYear); + // If the previous copyright only specified a single (initial) year, we convert it to the pair form + String newContent = fileContent.substring(0, yearInCopyrightIndex); + if (matcher.group(groupCount - 1) == null) { + // single year form + newContent += yearInCopyrightString + ", "; + } + newContent += info.lastYear + fileContent.substring(yearInCopyrightIndex + 4); + final FileOutputStream os = new FileOutputStream(fileName); + os.write(newContent.getBytes()); + os.close(); + return true; + } else { + return false; + } + } + return true; + } + + } + + private static class CustomCopyrightHandler extends CopyrightHandler { + private Map overrides = new HashMap(); + private CopyrightHandler defaultHandler; + + CustomCopyrightHandler(CopyrightHandler.CommentType commentType, CopyrightHandler defaultHandler) { + super(commentType); + this.defaultHandler = defaultHandler; + } + + void addFile(String fileName, String copyright) { + overrides.put(fileName, copyright); + } + + @Override + protected void readCopyrights() throws IOException { + } + + @Override + protected Matcher getMatcher(String fileName, String fileContent) throws IOException { + String copyright = overrides.get(fileName); + assert copyright != null : fileName; + try (InputStream fs = new FileInputStream(copyright + "." + suffix + ".regex")) { + return Pattern.compile(readCopyright(fs), Pattern.DOTALL).matcher(fileContent); + } + } + + @Override + protected String getText(String fileName) throws IOException { + String copyright = overrides.get(fileName); + assert copyright != null : fileName; + try (InputStream fs = new FileInputStream(copyright + "." + suffix)) { + return readCopyright(fs); + } + } + + @Override + protected boolean handlesFile(String fileName) { + return overrides.get(fileName) != null; + } + + @Override + protected boolean checkYearInfo(String fileName, String fileContent, Matcher matcher, Info info) throws IOException { + // This is a bit tacky + String copyright = overrides.get(fileName); + if (copyright.endsWith("no.copyright")) { + return true; + } + return defaultHandler.checkYearInfo(fileName, fileContent, matcher, info); + } + } + + private static void initCopyrightKinds() throws IOException { + CopyrightHandler starHandler = new DefaultCopyrightHandler(CopyrightHandler.CommentType.STAR); + CopyrightHandler hashHandler = new DefaultCopyrightHandler(CopyrightHandler.CommentType.HASH); + + String customCopyrightDir = CUSTOM_COPYRIGHT_DIR.getValue(); + if (customCopyrightDir != null) { + CustomCopyrightHandler customStarHandler = new CustomCopyrightHandler(CopyrightHandler.CommentType.STAR, starHandler); + CustomCopyrightHandler customHashHandler = new CustomCopyrightHandler(CopyrightHandler.CommentType.HASH, hashHandler); + starHandler.addCustomhandler(customStarHandler); + hashHandler.addCustomhandler(customHashHandler); + + File overrides = new File(new File(customCopyrightDir), "overrides"); + if (overrides.exists()) { + ArrayList lines = new ArrayList<>(); + boolean changed = false; + try (BufferedReader br = new BufferedReader(new FileReader( + overrides))) { + while (true) { + String line = br.readLine(); + if (line == null) { + break; + } + if (line.length() == 0 || line.startsWith("#")) { + lines.add(line); + continue; + } + String[] parts = line.split(","); + // filename,copyright-file + CopyrightHandler defaultHandler = CopyrightHandler.getDefaultHandler(parts[0]); + if (defaultHandler == null) { + System.err.println("no default copyright handler for: " + parts[0]); + System.exit(1); + } + if (!new File(parts[0]).exists()) { + System.err.printf("file %s in overrides file does not exist", parts[0]); + if (FIX.getValue()) { + System.err.print(" - removing"); + line = null; + changed = true; + } + System.err.println(); + } + if (line != null) { + lines.add(line); + } + CustomCopyrightHandler customhandler = (CustomCopyrightHandler) defaultHandler.customHandler; + customhandler.addFile(parts[0], new File(new File(customCopyrightDir), parts[1]).getAbsolutePath()); + } + } + if (changed) { + try (BufferedWriter bw = new BufferedWriter(new FileWriter( + overrides))) { + for (String line : lines) { + bw.write(line); + bw.write('\n'); + } + } + } + } + } + } + + private static int currentYear = Calendar.getInstance().get(Calendar.YEAR); + private static Options options = new Options(); + private static Option help = options.newBooleanOption("help", false, "Show help message and exit."); + private static Option COPYRIGHT_DIR = options.newStringOption("copyright-dir", null, "override default location of copyright files"); + private static Option> FILES_TO_CHECK = options.newStringListOption("files", null, "list of files to check"); + private static Option FILE_LIST = options.newStringOption("file-list", null, "file containing list of files to check"); + private static Option DIR_WALK = options.newBooleanOption("list-dir", false, "check all files in directory tree requiring a copyright (ls -R)"); + private static Option HG_ALL = options.newBooleanOption("hg-all", false, "check all hg managed files requiring a copyright (hg status --all)"); + private static Option HG_MODIFIED = options.newBooleanOption("hg-modified", false, "check all modified hg managed files requiring a copyright (hg status)"); + private static Option HG_OUTGOING = options.newBooleanOption("hg-outgoing", false, "check outgoing hg managed files requiring a copyright (hg outgoing)"); + private static Option HG_LOG = options.newStringOption("hg-last", "0", "check hg managed files requiring a copyright in last N changesets (hg log -l N)"); + private static Option> PROJECT = options.newStringListOption("projects", null, "filter files to specific projects"); + private static Option OUTGOING_REPO = options.newStringOption("hg-repo", null, "override outgoing repository"); + private static Option EXHAUSTIVE = options.newBooleanOption("hg-exhaustive", false, "check all hg managed files"); + private static Option FIX = options.newBooleanOption("fix", false, "fix all copyright errors"); + private static Option FILE_PATTERN = options.newStringOption("file-pattern", null, "append additional file patterns for copyright checks"); + private static Option REPORT_ERRORS = options.newBooleanOption("report-errors", false, "report non-fatal errors"); + private static Option HALT_ON_ERROR = options.newBooleanOption("halt-on-error", false, "continue after normally fatal error"); + private static Option HG_PATH = options.newStringOption("hg-path", "hg", "path to hg executable"); + private static Option VERBOSE = options.newBooleanOption("verbose", false, "verbose output"); + private static Option VERY_VERBOSE = options.newBooleanOption("very-verbose", false, "very verbose output"); + private static Option CUSTOM_COPYRIGHT_DIR = options.newStringOption("custom-copyright-dir", null, "file containing filenames with custom copyrights"); + + private static String CANNOT_FOLLOW_FILE = "abort: cannot follow"; + private static String hgPath; + private static boolean error; +// private static File workSpaceDirectory; + private static boolean verbose; + private static boolean veryVerbose; + + public static void main(String[] args) { + // parse the arguments + options.parseArguments(args); + if (help.getValue()) { + options.printHelp(); + return; + } + + verbose = VERBOSE.getValue(); + veryVerbose = VERY_VERBOSE.getValue(); + + hgPath = HG_PATH.getValue(); + + if (FILE_PATTERN.getValue() != null) { + CopyrightHandler.addCopyrightFilesPattern(FILE_PATTERN.getValue()); + } + + try { + initCopyrightKinds(); + List 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 (Integer.parseInt(HG_LOG.getValue()) > 0) { + filesToCheck = getLastNFiles(Integer.parseInt(HG_LOG.getValue())); + } else if (FILE_LIST.getValue() != null) { + filesToCheck = readFileList(FILE_LIST.getValue()); + } else if (DIR_WALK.getValue()) { + filesToCheck = getDirWalkFiles(); + } else if (FILES_TO_CHECK.getValue() != null) { + filesToCheck = FILES_TO_CHECK.getValue(); + } else { + // no option set, default to HG_ALL + filesToCheck = getAllFiles(true); + } + if (filesToCheck != null && filesToCheck.size() > 0) { + processFiles(filesToCheck); + } else { + System.out.println("nothing to check"); + } + System.exit(error ? 1 : 0); + } catch (Exception ex) { + System.err.println("processing failed: " + ex); + ex.printStackTrace(); + } + } + + private static void processFiles(List fileNames) throws Exception { + final List projects = PROJECT.getValue(); + Calendar cal = Calendar.getInstance(); + for (String fileName : fileNames) { + if (projects == null || isInProjects(fileName, projects)) { + File file = new File(fileName); + if (file.isDirectory()) { + continue; + } + if (verbose) { + System.out.println("checking " + fileName); + } + try { + Info info = null; + if (DIR_WALK.getValue()) { + info = getFromLastModified(cal, fileName); + } else { + final List logInfo = hglog(fileName); + if (logInfo.size() == 0) { + // an added file, so go with last modified + info = getFromLastModified(cal, fileName); + } else { + info = getInfo(fileName, true, logInfo); + } + } + checkFile(fileName, info); + } catch (Exception e) { + System.err.format("COPYRIGHT CHECK WARNING: error while processing %s: %s%n", fileName, e.getMessage()); + } + } + } + } + + private static Info getFromLastModified(Calendar cal, String fileName) { + File file = new File(fileName); + cal.setTimeInMillis(file.lastModified()); + int year = cal.get(Calendar.YEAR); + return new Info(fileName, year, year); + } + + private static boolean isInProjects(String fileName, List 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 readFileList(String fileListName) throws IOException { + final List result = new ArrayList(); + 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 logInfo) { + // process sequence of changesets + int lastYear = 0; + int firstYear = 0; + int ix = 0; + + while (ix < logInfo.size()) { + Map tagMap = new HashMap<>(); + ix = getChangeset(logInfo, ix, tagMap); + String date = tagMap.get("date"); + assert date != null; + final int csYear = getYear(date); + if (lastYear == 0) { + lastYear = csYear; + firstYear = lastYear; + } else { + firstYear = csYear; + } + // if we only want the last modified year, quit now + if (lastOnly) { + break; + } + + } + + 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); + } + + /** + * Process all the changeset data, storing in {@outMap}. + * Return updated value of {@code ix}. + */ + private static int getChangeset(List logInfo, int ixx, Map outMap) { + int ix = ixx; + String s = logInfo.get(ix++); + while (s.length() > 0) { + int cx = s.indexOf(':'); + String tag = s.substring(0, cx); + String value = s.substring(cx + 1); + outMap.put(tag, value); + s = logInfo.get(ix++); + } + return ix; + } + + 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[] fileContentBytes = new byte[fileLength]; + FileInputStream is = new FileInputStream(file); + is.read(fileContentBytes); + is.close(); + final String fileContent = new String(fileContentBytes); + CopyrightHandler copyrightHandler = CopyrightHandler.getCopyrightHandler(fileName); + if (file.getName().equals("Makefile")) { + System.console(); + } + if (copyrightHandler != null) { + Matcher copyrightMatcher = copyrightHandler.getMatcher(fileName, fileContent); + if (copyrightMatcher.matches()) { + error = error | !copyrightHandler.checkYearInfo(fileName, fileContent, copyrightMatcher, info); + } else { + // If copyright is missing, insert it, otherwise user has to manually fix existing copyright. + if (!fileContent.contains("Copyright")) { + System.out.print("file " + fileName + " has missing copyright"); + if (FIX.getValue()) { + final FileOutputStream os = new FileOutputStream(file); + os.write(CopyrightHandler.getCopyrightText(fileName) + .getBytes()); + os.write(fileContentBytes); + os.close(); + System.out.println("...fixed"); + } else { + System.out.println(); + error = true; + } + } else { + System.out.println("file " + fileName + " has malformed copyright" + (FIX.getValue() ? " not fixing" : "")); + error = true; + } + } + } else if (EXHAUSTIVE.getValue()) { + System.out.println("ERROR: file " + fileName + " has no copyright"); + error = true; + } + } + + + private static List hglog(String fileName) throws Exception { + final String[] cmd = new String[] {hgPath, "log", "-f", fileName}; + return exec(null, cmd, true); + } + + private static List 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 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 output = exec(null, cmd, true); + final List result = new ArrayList(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 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 output = exec(null, cmd, false); // no outgoing exits with result 1 + return getFilesFiles(output); + } + + private static List getFilesFiles(List output) { + // there may be multiple changesets so merge the "files:" + final Map outSet = new TreeMap(); + 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(outSet.values()); + } + + private static List getDirWalkFiles() { + File cwd = new File(System.getProperty("user.dir")); + ArrayList result = new ArrayList(); + getDirWalkFiles(cwd, result); + // remove "user.dir" prefix to make files relative as per hg + String cwdPath = cwd.getAbsolutePath() + '/'; + for (int i = 0; i < result.size(); i++) { + String path = result.get(i); + result.set(i, path.replace(cwdPath, "")); + } + return result; + } + + private static void getDirWalkFiles(File dir, ArrayList list) { + File[] files = dir.listFiles(); + for (File file : files) { + if (ignoreFile(file.getName())) { + continue; + } + if (file.isDirectory()) { + getDirWalkFiles(file, list); + } else { + list.add(file.getAbsolutePath()); + } + } + } + + private static final String IGNORE_LIST = "\\.hg|.*\\.class|bin|src_gen"; + private static final Pattern ignorePattern = Pattern.compile(IGNORE_LIST); + + private static boolean ignoreFile(String name) { + return ignorePattern.matcher(name).matches(); + } + + private static List exec(File workingDir, String[] command, boolean failOnError) throws IOException, InterruptedException { + List result = new ArrayList(); + if (veryVerbose) { + System.out.println("Executing process in directory: " + workingDir); + for (String c : command) { + System.out.println(" " + 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 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 && HALT_ON_ERROR.getValue()) { + if (!cannotFollowNonExistentFile(errorResult)) { + throw new Error("terminating"); + } + } + } + } finally { + process.destroy(); + } + return result; + } + + private static boolean cannotFollowNonExistentFile(List errorResult) { + return errorResult.size() == 1 && errorResult.get(0).startsWith(CANNOT_FOLLOW_FILE); + } + + private static List readOutput(InputStream is) throws IOException { + final List result = new ArrayList(); + 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; + } + + private static class Options { + private static Map> optionMap = new TreeMap<>(); + + private Option newBooleanOption(String name, boolean defaultValue, String help) { + Option option = new Option(name, help, defaultValue, false, false); + optionMap.put(key(name), option); + return option; + } + + private Option newStringOption(String name, String defaultValue, String help) { + Option option = new Option(name, help, defaultValue); + optionMap.put(key(name), option); + return option; + } + + private Option> newStringListOption(String name, List defaultValue, String help) { + Option> option = new Option>(name, help, defaultValue, true, true); + optionMap.put(key(name), option); + return option; + } + + private static String key(String name) { + return "--" + name; + } + + void parseArguments(String[] args) { + for (int i = 0; i < args.length; i++) { + final String arg = args[i]; + if (arg.startsWith("--")) { + Option option = optionMap.get(arg); + if (option == null || (option.consumesNext() && i == args.length - 1)) { + System.out.println("usage:"); + printHelp(); + System.exit(1); + } + if (option.consumesNext()) { + i++; + option.setValue(args[i]); + } else { + option.setValue(true); + } + } + } + } + + void printHelp() { + int maxKeyLen = 0; + for (Map.Entry> entrySet : optionMap.entrySet()) { + int l = entrySet.getKey().length(); + if (l > maxKeyLen) { + maxKeyLen = l; + } + } + for (Map.Entry> entrySet : optionMap.entrySet()) { + String key = entrySet.getKey(); + System.out.printf(" %s", key); + for (int i = 0; i < maxKeyLen - key.length(); i++) { + System.out.print(' '); + } + System.out.printf(" %s%n", entrySet.getValue().help); + } + } +} + + private static class Option { + private final String name; + private final String help; + private final boolean consumesNext; + private final boolean isList; + private T value; + + Option(String name, String help, T defaultValue, boolean consumesNext, boolean isList) { + this.name = name; + this.help = help; + this.value = defaultValue; + this.consumesNext = consumesNext; + this.isList = isList; + + } + + Option(String name, String help, T defaultValue) { + this(name, help, defaultValue, true, false); + } + + T getValue() { + return value; + } + + boolean consumesNext() { + return consumesNext; + } + + @SuppressWarnings("unchecked") + void setValue(boolean value) { + this.value = (T) new Boolean(value); + } + + @SuppressWarnings("unchecked") + void setValue(String value) { + if (isList) { + String[] parts = value.split(","); + this.value = (T) Arrays.asList(parts); + } else { + this.value = (T) value; + } + } + + @SuppressWarnings("unused") + String getName() { + return name; + } + } + +} diff -r a64d09dc4590 -r c190ed6b84bf mxtool/copyrights/oracle.copyright.hash --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mxtool/copyrights/oracle.copyright.hash Fri May 22 23:11:17 2015 +0200 @@ -0,0 +1,22 @@ +# +# Copyright (c) 2014, 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. +# diff -r a64d09dc4590 -r c190ed6b84bf mxtool/copyrights/oracle.copyright.regex.hash --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mxtool/copyrights/oracle.copyright.regex.hash Fri May 22 23:11:17 2015 +0200 @@ -0,0 +1,1 @@ +(?:#!.*\n#\n#\ -*\n)?#\n# Copyright \(c\) (?:(20[0-9][0-9]), )?(20[0-9][0-9]), Oracle and/or its affiliates. All rights reserved.\n# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n#\n# This code is free software; you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 2 only, as\n# published by the Free Software Foundation.\n#\n# This code is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\n# version 2 for more details \(a copy is included in the LICENSE file that\n# accompanied this code\).\n#\n# You should have received a copy of the GNU General Public License version\n# 2 along with this work; if not, write to the Free Software Foundation,\n# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n# or visit www.oracle.com if you need additional information or have any\n# questions.\n#\n.* diff -r a64d09dc4590 -r c190ed6b84bf mxtool/copyrights/oracle.copyright.regex.star --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mxtool/copyrights/oracle.copyright.regex.star Fri May 22 23:11:17 2015 +0200 @@ -0,0 +1,1 @@ +/\*\n \* Copyright \(c\) (?:(20[0-9][0-9]), )?(20[0-9][0-9]), Oracle and/or its affiliates. All rights reserved.\n \* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n \*\n \* This code is free software; you can redistribute it and/or modify it\n \* under the terms of the GNU General Public License version 2 only, as\n \* published by the Free Software Foundation.\n \*\n \* This code is distributed in the hope that it will be useful, but WITHOUT\n \* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n \* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\n \* version 2 for more details \(a copy is included in the LICENSE file that\n \* accompanied this code\).\n \*\n \* You should have received a copy of the GNU General Public License version\n \* 2 along with this work; if not, write to the Free Software Foundation,\n \* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n \*\n \* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n \* or visit www.oracle.com if you need additional information or have any\n \* questions.\n \*/\n.* diff -r a64d09dc4590 -r c190ed6b84bf mxtool/copyrights/oracle.copyright.star --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mxtool/copyrights/oracle.copyright.star Fri May 22 23:11:17 2015 +0200 @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014, 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. + */ diff -r a64d09dc4590 -r c190ed6b84bf mxtool/mx.py --- a/mxtool/mx.py Fri May 22 23:10:51 2015 +0200 +++ b/mxtool/mx.py Fri May 22 23:11:17 2015 +0200 @@ -2318,15 +2318,8 @@ def _init_classpaths(self): if not self._classpaths_initialized: - myDir = dirname(__file__) - outDir = join(dirname(__file__), '.jdk' + str(self.version)) - if not exists(outDir): - os.makedirs(outDir) - javaSource = join(myDir, 'ClasspathDump.java') - javaClass = join(outDir, 'ClasspathDump.class') - if not exists(javaClass) or getmtime(javaClass) < getmtime(javaSource): - subprocess.check_call([self.javac, '-d', _cygpathU2W(outDir), _cygpathU2W(javaSource)], stderr=subprocess.PIPE, stdout=subprocess.PIPE) - self._bootclasspath, self._extdirs, self._endorseddirs = [x if x != 'null' else None for x in subprocess.check_output([self.java, '-cp', _cygpathU2W(outDir), 'ClasspathDump'], stderr=subprocess.PIPE).split('|')] + _, binDir = _compile_mx_class('ClasspathDump', jdk=self) + self._bootclasspath, self._extdirs, self._endorseddirs = [x if x != 'null' else None for x in subprocess.check_output([self.java, '-cp', _cygpathU2W(binDir), 'ClasspathDump'], stderr=subprocess.PIPE).split('|')] if self.javaCompliance <= JavaCompliance('1.8'): # All 3 system properties accessed by ClasspathDump are expected to exist if not self._bootclasspath or not self._extdirs or not self._endorseddirs: @@ -2537,15 +2530,12 @@ assert not path.endswith(os.sep) - myDir = dirname(__file__) - javaSource = join(myDir, 'URLConnectionDownload.java') - javaClass = join(myDir, 'URLConnectionDownload.class') - if not exists(javaClass) or getmtime(javaClass) < getmtime(javaSource): - subprocess.check_call([java().javac, '-d', _cygpathU2W(myDir), _cygpathU2W(javaSource)]) + _, binDir = _compile_mx_class('URLConnectionDownload') + verbose = [] if sys.stderr.isatty(): verbose.append("-v") - if run([java().java, '-cp', _cygpathU2W(myDir), 'URLConnectionDownload', _cygpathU2W(path)] + verbose + urls, nonZeroIsFatal=False) == 0: + if run([java().java, '-cp', _cygpathU2W(binDir), 'URLConnectionDownload', _cygpathU2W(path)] + verbose + urls, nonZeroIsFatal=False) == 0: return abort('Could not download to ' + path + ' from any of the following URLs:\n\n ' + @@ -5442,6 +5432,65 @@ _show_section('projects', s.projects) _show_section('distributions', s.dists) +def _compile_mx_class(javaClassName, classpath=None, jdk=None): + myDir = dirname(__file__) + binDir = join(myDir, 'bin' if not jdk else '.jdk' + str(jdk.version)) + javaSource = join(myDir, javaClassName + '.java') + javaClass = join(binDir, javaClassName + '.class') + if not exists(javaClass) or getmtime(javaClass) < getmtime(javaSource): + if not exists(binDir): + os.mkdir(binDir) + javac = jdk.javac if jdk else java().javac + cmd = [javac, '-d', _cygpathU2W(binDir)] + if classpath: + cmd += ['-cp', _separatedCygpathU2W(binDir + os.pathsep + classpath)] + cmd += [_cygpathU2W(javaSource)] + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError: + abort('failed to compile:' + javaSource) + + return (myDir, binDir) + +def checkcopyrights(args): + '''run copyright check on the sources''' + class CP(ArgumentParser): + def format_help(self): + return ArgumentParser.format_help(self) + self._get_program_help() + + def _get_program_help(self): + help_output = subprocess.check_output([java().java, '-cp', _cygpathU2W(binDir), 'CheckCopyright', '--help']) + return '\nother argumemnts preceded with --\n' + help_output + + myDir, binDir = _compile_mx_class('CheckCopyright') + + parser = CP(prog='mx checkcopyrights') + + parser.add_argument('--primary', action='store_true', help='limit checks to primary suite') + parser.add_argument('remainder', nargs=REMAINDER, metavar='...') + args = parser.parse_args(args) + remove_doubledash(args.remainder) + + + # ensure compiled form of code is up to date + + result = 0 + # copyright checking is suite specific as each suite may have different overrides + for s in suites(True): + if args.primary and not s.primary: + continue + custom_copyrights = _cygpathU2W(join(s.mxDir, 'copyrights')) + custom_args = [] + if exists(custom_copyrights): + custom_args = ['--custom-copyright-dir', custom_copyrights] + rc = run([java().java, '-cp', _cygpathU2W(binDir), 'CheckCopyright', '--copyright-dir', _cygpathU2W(myDir)] + custom_args + args.remainder, cwd=s.dir, nonZeroIsFatal=False) + result = result if rc == 0 else rc + return result + +def remove_doubledash(args): + if '--' in args: + args.remove('--') + def ask_yes_no(question, default=None): """""" assert not default or default == 'y' or default == 'n' @@ -5485,6 +5534,7 @@ 'build': [build, '[options]'], 'checkstyle': [checkstyle, ''], 'canonicalizeprojects': [canonicalizeprojects, ''], + 'checkcopyrights': [checkcopyrights, '[options]'], 'clean': [clean, ''], 'eclipseinit': [eclipseinit, ''], 'eclipseformat': [eclipseformat, ''],