Index: lucene/build.xml =================================================================== --- lucene/build.xml (revision 1528672) +++ lucene/build.xml (working copy) @@ -151,7 +151,8 @@ - + Index: lucene/common-build.xml =================================================================== --- lucene/common-build.xml (revision 1528672) +++ lucene/common-build.xml (working copy) @@ -2262,4 +2262,8 @@ + + + Index: lucene/ivy-versions.properties =================================================================== --- lucene/ivy-versions.properties (revision 1528672) +++ lucene/ivy-versions.properties (working copy) @@ -48,9 +48,7 @@ /org.apache.hadoop/hadoop-auth = ${hadoop.version} /org.apache.hadoop/hadoop-common = ${hadoop.version} /org.apache.hadoop/hadoop-hdfs = ${hadoop.version} -/org.apache.httpcomponents/httpclient = 4.2.3 /org.apache.httpcomponents/httpclient = 4.2.6 -/org.apache.httpcomponents/httpcore = 4.2.2 /org.apache.httpcomponents/httpcore = 4.2.5 /org.apache.httpcomponents/httpmime = 4.2.6 /org.apache.james/apache-mime4j-core = 0.7.2 @@ -119,4 +117,3 @@ /org.tukaani/xz = 1.0 /rome/rome = 0.9 /xerces/xercesImpl = 2.9.1 - Index: lucene/tools/custom-tasks.xml =================================================================== --- lucene/tools/custom-tasks.xml (revision 1528672) +++ lucene/tools/custom-tasks.xml (working copy) @@ -84,4 +84,29 @@ + + + + + + + Lib versions check under: @{dir} + + + + + + + + + + + + + + Index: lucene/tools/src/java/lucene-solr.antlib.xml =================================================================== --- lucene/tools/src/java/lucene-solr.antlib.xml (revision 1528672) +++ lucene/tools/src/java/lucene-solr.antlib.xml (working copy) @@ -18,4 +18,7 @@ + Index: lucene/tools/src/java/org/apache/lucene/validation/LibVersionsCheckTask.java =================================================================== --- lucene/tools/src/java/org/apache/lucene/validation/LibVersionsCheckTask.java (revision 0) +++ lucene/tools/src/java/org/apache/lucene/validation/LibVersionsCheckTask.java (working copy) @@ -0,0 +1,365 @@ +package org.apache.lucene.validation; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.Resources; +import org.apache.tools.ant.util.FileNameMapper; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.Iterator; +import java.util.Locale; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An Ant task that verifies if Ivy dependencies across all Lucene/Solr + * modules refer to the centralized version file: lucene/ivy-versions.properties; + * this task also verifies that the centralized version file is sorted + * alphabetically. + */ +public class LibVersionsCheckTask extends Task { + + private static final String IVY_XML_FILENAME = "ivy.xml"; + private static final Pattern COORDINATE_KEY_PATTERN = Pattern.compile("(/[^/ \t\f]+/[^=:/ \t\f]+).*"); + private static final Pattern BLANK_OR_COMMENT_LINE_PATTERN = Pattern.compile("[ \t\f]*(?:[#!].*)?"); + private static final Pattern TRAILING_BACKSLASH_PATTERN = Pattern.compile("[^\\\\]*(\\\\+)$"); + private static final Pattern LEADING_WHITESPACE_PATTERN = Pattern.compile("[ \t\f]+(.*)"); + private static final Pattern WHITESPACE_GOODSTUFF_WHITESPACE_BACKSLASH_PATTERN + = Pattern.compile("[ \t\f]*(.*?)(?:(? iter = (Iterator)ivyXmlResources.iterator(); + while (iter.hasNext()) { + final Resource resource = iter.next(); + if ( ! resource.isExists()) { + throw new BuildException("Resource does not exist: " + resource.getName()); + } + if ( ! (resource instanceof FileResource)) { + throw new BuildException("Only filesystem resources are supported: " + + resource.getName() + ", was: " + resource.getClass().getName()); + } + + File ivyXmlFile = ((FileResource)resource).getFile(); + try { + if ( ! checkIvyXmlFile(ivyXmlFile) ) { + failures = true; + errors++; + } + } catch (Exception e) { + throw new BuildException("Exception reading file " + ivyXmlFile.getPath(), e); + } + checked++; + } + + log(String.format(Locale.ROOT, "Checked %s for alpha sort, and scanned %d %s" + + " file(s) for rev=\"${/org/name}\" format (in %.2fs.), %d error(s).", + centralizedVersionsFile.getPath(), checked, IVY_XML_FILENAME, + (System.currentTimeMillis() - start) / 1000.0, errors), + errors > 0 ? Project.MSG_ERR : Project.MSG_INFO); + + if (failures) { + throw new BuildException("Lib versions check failed. Check the logs."); + } + } + + /** + * Returns true if the "/org/name = rev" properties lines in the + * centralized versions properties file are alphabetically sorted, ignoring + * any leading whitespace. + * @return + */ + private boolean verifySortedCentralizedVersionsFile() { + log("Scanning: " + centralizedVersionsFile, verboseLevel); + final InputStream stream; + try { + stream = new FileInputStream(centralizedVersionsFile); + } catch (FileNotFoundException e) { + throw new BuildException("Centralized versions file does not exist: " + + centralizedVersionsFile.getPath()); + } + // Properties files are encoded as Latin-1 + final Reader reader = new InputStreamReader(stream, Charset.forName("ISO-8859-1")); + final BufferedReader bufferedReader = new BufferedReader(reader); + + String line = null; + String currentKey = null; + String previousKey = null; + try { + while (null != (line = readLogicalPropertiesLine(bufferedReader))) { + final Matcher keyMatcher = COORDINATE_KEY_PATTERN.matcher(line); + if ( ! keyMatcher.matches()) { + continue; + } + currentKey = keyMatcher.group(1); + if (null == previousKey) { + previousKey = currentKey; + continue; + } + int comparison = currentKey.compareTo(previousKey); + if (0 == comparison) { + log("DUPLICATE property key found: '" + currentKey + "'", Project.MSG_ERR); + failures = true; + break; + } else if (comparison < 0) { + log("OUT-OF-ORDER property key found: '" + currentKey + "'", Project.MSG_ERR); + failures = true; + break; + } + previousKey = currentKey; + } + } catch (IOException e) { + throw new BuildException("Exception reading centralized versions file: " + + centralizedVersionsFile.getPath(), e); + } finally { + try { reader.close(); } catch (IOException e) { } + } + return ! failures; + } + + /** + * Builds up logical {@link java.util.Properties} lines, composed of one non-blank, + * non-comment initial line, either: + * + * 1. without a non-escaped trailing slash; or + * 2. with a non-escaped trailing slash, followed by + * zero or more lines with a non-escaped trailing slash, followed by + * one or more lines without a non-escaped trailing slash + * + * All leading whitespace and trailing whitespace + non-escaped slash are + * trimmed from each line before concatenating. + * + * After composing the logical line, leading whitespace is trimmed, then + * escaped characters are un-escaped. + * + * null is returned if there are no lines left to read. + */ + private String readLogicalPropertiesLine(BufferedReader reader) throws IOException { + final StringBuilder logicalLine = new StringBuilder(); + String line; + do { + line = reader.readLine(); + if (null == line) { + return null; + } + } while (BLANK_OR_COMMENT_LINE_PATTERN.matcher(line).matches()); + + Matcher backslashMatcher = TRAILING_BACKSLASH_PATTERN.matcher(line); + // Check for a non-escaped backslash + if (backslashMatcher.find() && 1 == (backslashMatcher.group(1).length() % 2)) { + final Matcher firstLineMatcher = TRAILING_WHITESPACE_BACKSLASH_PATTERN.matcher(line); + if (firstLineMatcher.matches()) { + logicalLine.append(firstLineMatcher.group(1)); // trim trailing backslash and any preceding whitespace + } + line = reader.readLine(); + while (null != line + && (backslashMatcher = TRAILING_BACKSLASH_PATTERN.matcher(line)).find() + && 1 == (backslashMatcher.group(1).length() % 2)) { + // Trim leading whitespace, the trailing backslash and any preceding whitespace + final Matcher goodStuffMatcher = WHITESPACE_GOODSTUFF_WHITESPACE_BACKSLASH_PATTERN.matcher(line); + if (goodStuffMatcher.matches()) { + logicalLine.append(goodStuffMatcher.group(1)); + } + line = reader.readLine(); + } + if (null != line) { + // line can't have a non-escaped trailing backslash + final Matcher leadingWhitespaceMatcher = LEADING_WHITESPACE_PATTERN.matcher(line); + if (leadingWhitespaceMatcher.matches()) { + line = leadingWhitespaceMatcher.group(1); // trim leading whitespace + } + logicalLine.append(line); + } + } else { + logicalLine.append(line); + } + // trim non-escaped leading whitespace + final Matcher leadingWhitespaceMatcher = LEADING_WHITESPACE_PATTERN.matcher(logicalLine); + final CharSequence leadingWhitespaceStripped = leadingWhitespaceMatcher.matches() + ? leadingWhitespaceMatcher.group(1) + : logicalLine; + + // unescape all chars in the logical line + StringBuilder output = new StringBuilder(); + final int numChars = leadingWhitespaceStripped.length(); + for (int pos = 0 ; pos < numChars - 1 ; ++pos) { + char ch = leadingWhitespaceStripped.charAt(pos); + if (ch == '\\') { + ch = leadingWhitespaceStripped.charAt(++pos); + } + output.append(ch); + } + if (numChars > 0) { + output.append(leadingWhitespaceStripped.charAt(numChars - 1)); + } + + return output.toString(); + } + + /** + * Check a single ivy.xml file. Returns true if it has no problems, false otherwise. + */ + private boolean checkIvyXmlFile(File ivyXmlFile) + throws ParserConfigurationException, SAXException, IOException { + log("Scanning: " + ivyXmlFile.getPath(), verboseLevel); + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + DependencyRevChecker revChecker = new DependencyRevChecker(ivyXmlFile); + xmlReader.setContentHandler(revChecker); + xmlReader.setErrorHandler(revChecker); + // To work around a bug in XERCES (XERCESJ-1257), we assume the XML is always UTF8, so we simply provide reader. + CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + InputStream stream = new FileInputStream(ivyXmlFile); + xmlReader.parse(new InputSource(new BufferedReader(new InputStreamReader(stream, decoder)))); + return ! revChecker.fail; + } + + private class DependencyRevChecker extends DefaultHandler { + private final File ivyXmlFile; + private final Stack tags = new Stack(); + + public boolean fail = false; + + public DependencyRevChecker(File ivyXmlFile) { + this.ivyXmlFile = ivyXmlFile; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (localName.equals("dependency") && insideDependenciesTag()) { + String org = attributes.getValue("org"); + boolean foundAllAttributes = true; + if (null == org) { + log("MISSING 'org' attribute on in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + foundAllAttributes = false; + } + String name = attributes.getValue("name"); + if (null == name) { + log("MISSING 'name' attribute on in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + foundAllAttributes = false; + } + String rev = attributes.getValue("rev"); + if (null == rev) { + log("MISSING 'rev' attribute on in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + foundAllAttributes = false; + } + if (foundAllAttributes) { + String expectedRev = "${/" + org + '/' + name + '}'; + if ( ! rev.equals(expectedRev)) { + log("BAD 'rev' attribute value '" + rev + "' - expected '" + expectedRev + "'" + + " in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + } + } + } + tags.push(localName); + } + + @Override + public void endElement (String uri, String localName, String qName) throws SAXException { + tags.pop(); + } + + private boolean insideDependenciesTag() { + return tags.size() == 2 && tags.get(0).equals("ivy-module") && tags.get(1).equals("dependencies"); + } + } +} Property changes on: lucene/tools/src/java/org/apache/lucene/validation/LibVersionsCheckTask.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: solr/build.xml =================================================================== --- solr/build.xml (revision 1528672) +++ solr/build.xml (working copy) @@ -234,7 +234,8 @@ - +