LUCENE-5442: ant check-lib-versions will fail the build if there are unexpected version conflicts between direct and transitive dependencies.

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1598538 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Steven Rowe 2014-05-30 10:33:55 +00:00
parent 47a1a8be4a
commit 4125c7682e
16 changed files with 1228 additions and 113 deletions

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="Ivy">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lucene/tools/lib/ivy-2.3.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -10,6 +10,7 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Ant" level="project" />
<orderEntry type="library" name="Ivy" level="project" />
<orderEntry type="module-library">
<library>
<CLASSES>

View File

@ -250,6 +250,15 @@ Test Framework
* LUCENE-5619: Added backwards compatibility tests to ensure we can update existing
indexes with doc-values updates. (Shai Erera, Robert Muir)
Build
* LUCENE-5442: The Ant check-lib-versions target now runs Ivy resolution
transitively, then fails the build when it finds a version conflict: when a
transitive dependency's version is more recent than the direct dependency's
version specified in lucene/ivy-versions.properties. Exceptions are
specifiable in lucene/ivy-ignore-conflicts.properties.
(Steve Rowe)
======================= Lucene 4.8.1 =======================
Bug fixes

View File

@ -171,7 +171,11 @@
<target name="check-lib-versions" depends="compile-tools,resolve,load-custom-tasks"
description="Verify that the '/org/name' keys in ivy-versions.properties are sorted lexically and are neither duplicates nor orphans, and that all dependencies in all ivy.xml files use rev=&quot;$${/org/name}&quot; format.">
<lib-versions-check-macro dir="${common.dir}/.." centralized.versions.file="${common.dir}/ivy-versions.properties"/>
<lib-versions-check-macro dir="${common.dir}/.."
centralized.versions.file="${common.dir}/ivy-versions.properties"
ivy.settings.file="${common.dir}/ivy-settings.xml"
common.build.dir="${common.build.dir}"
ignore.conflicts.file="${common.dir}/ivy-ignore-conflicts.properties"/>
</target>
<!-- -install-forbidden-apis is *not* a useless dependency. do not remove -->

View File

@ -0,0 +1,33 @@
# The /org/name keys in this file must be kept lexically sorted.
# Blank lines, comment lines, and keys that aren't in /org/name format are ignored
# when the lexical sort check is performed by the ant check-lib-versions target.
#
# The format is:
#
# /org/name = <version1> [, <version2> [ ... ] ]
#
# where each <versionX> is an indirect dependency version to ignore (i.e., not
# trigger a conflict) when the ant check-lib-versions target is run.
/asm/asm = 3.2
/com.google.guava/guava = 16.0.1
com.sun.jersey.ignore.versions = 1.9
/com.sun.jersey.contribs/jersey-guice = ${com.sun.jersey.ignore.versions}
/com.sun.jersey/jersey-core = ${com.sun.jersey.ignore.versions}
/com.sun.jersey/jersey-json = ${com.sun.jersey.ignore.versions}
/com.sun.jersey/jersey-server = ${com.sun.jersey.ignore.versions}
/com.sun.xml.bind/jaxb-impl = 2.2.3-1
/commons-beanutils/commons-beanutils = 1.8.0, 1.8.3
/commons-digester/commons-digester = 2.1
/commons-io/commons-io = 2.3
/commons-logging/commons-logging = 1.1.3
/io.netty/netty = 3.7.0.Final
/javax.activation/activation = 1.1.1
/javax.servlet/servlet-api = 2.5, 3.0-alpha-1
/log4j/log4j = 1.2.17
/org.apache.avro/avro = 1.7.5
/org.ow2.asm/asm = 5.0_BETA
/org.tukaani/xz = 1.4
/org.xerial.snappy/snappy-java = 1.0.5

View File

@ -113,6 +113,8 @@ org.apache.hadoop.version = 2.2.0
/org.apache.httpcomponents/httpcore = 4.3
/org.apache.httpcomponents/httpmime = 4.3.1
/org.apache.ivy/ivy = 2.3.0
org.apache.james.apache.mime4j.version = 0.7.2
/org.apache.james/apache-mime4j-core = ${org.apache.james.apache.mime4j.version}
/org.apache.james/apache-mime4j-dom = ${org.apache.james.apache.mime4j.version}

View File

@ -0,0 +1 @@
c5ebf1c253ad4959a29f4acfe696ee48cdd9f473

View File

@ -0,0 +1,258 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed 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.
------------------------------------------------------------------------------
License for JCraft JSch package
------------------------------------------------------------------------------
Copyright (c) 2002,2003,2004,2005,2006,2007 Atsuhiko Yamanaka, JCraft,Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------------------------------------------------------------------------
License for jQuery
------------------------------------------------------------------------------
Copyright (c) 2007 John Resig, http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,16 @@
Apache Ivy (TM)
Copyright 2007-2013 The Apache Software Foundation
This product includes software developed by
The Apache Software Foundation (http://www.apache.org/).
Portions of Ivy were originally developed by
Jayasoft SARL (http://www.jayasoft.fr/)
and are licensed to the Apache Software Foundation under the
"Software Grant License Agreement"
SSH and SFTP support is provided by the JCraft JSch package,
which is open source software, available under
the terms of a BSD style license.
The original software and related information is available
at http://www.jcraft.com/jsch/.

View File

@ -88,6 +88,9 @@
<macrodef name="lib-versions-check-macro">
<attribute name="dir"/>
<attribute name="centralized.versions.file"/>
<attribute name="ivy.settings.file"/>
<attribute name="common.build.dir"/>
<attribute name="ignore.conflicts.file"/>
<sequential>
<!--
Verify that the '/org/name' keys in ivy-versions.properties are sorted
@ -95,7 +98,10 @@
dependencies in all ivy.xml files use rev="${/org/name}" format.
-->
<echo>Lib versions check under: @{dir}</echo>
<libversions centralizedVersionsFile="@{centralized.versions.file}">
<libversions centralizedVersionsFile="@{centralized.versions.file}"
ivySettingsFile="@{ivy.settings.file}"
commonBuildDir="@{common.build.dir}"
ignoreConflictsFile="@{ignore.conflicts.file}">
<fileset dir="@{dir}">
<include name="**/ivy.xml" />
<!-- Speed up scanning a bit. -->

View File

@ -20,6 +20,7 @@
<info organisation="org.apache.lucene" module="core-tools"/>
<dependencies>
<dependency org="org.apache.ant" name="ant" rev="${/org.apache.ant/ant}" transitive="false" />
<dependency org="org.apache.ivy" name="ivy" rev="${/org.apache.ivy/ivy}" transitive="false" />
<exclude org="*" ext="*" matcher="regexp" type="${ivy.exclude.types}"/>
</dependencies>
</ivy-module>

View File

@ -39,7 +39,6 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@ -83,7 +82,6 @@ public class GetMavenDependenciesTask extends Task {
// lucene/build/core/classes/java
private static final Pattern COMPILATION_OUTPUT_DIRECTORY_PATTERN
= Pattern.compile("(lucene|solr)/build/(?:contrib/)?(.*)/classes/(?:java|test)");
private static final Pattern PROPERTY_REFERENCE_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
private static final String UNWANTED_INTERNAL_DEPENDENCIES
= "/(?:test-)?lib/|test-framework/classes/java|/test-files|/resources";
private static final Pattern SHARED_EXTERNAL_DEPENDENCIES_PATTERN
@ -437,7 +435,13 @@ public class GetMavenDependenciesTask extends Task {
private void appendAllExternalDependencies(StringBuilder dependenciesBuilder, Map<String,String> versionsMap) {
log("Loading centralized ivy versions from: " + centralizedVersionsFile, verboseLevel);
ivyCacheDir = getIvyCacheDir();
Properties versions = loadPropertiesFile(centralizedVersionsFile);
Properties versions = new InterpolatedProperties();
try (InputStream inputStream = new FileInputStream(centralizedVersionsFile);
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
versions.load(reader);
} catch (IOException e) {
throw new BuildException("Exception reading centralized versions file " + centralizedVersionsFile.getPath(), e);
}
SortedSet<Map.Entry> sortedEntries = new TreeSet<>(new Comparator<Map.Entry>() {
@Override public int compare(Map.Entry o1, Map.Entry o2) {
return ((String)o1.getKey()).compareTo((String)o2.getKey());
@ -510,7 +514,15 @@ public class GetMavenDependenciesTask extends Task {
*/
private void setInternalDependencyProperties() {
log("Loading module dependencies from: " + moduleDependenciesPropertiesFile, verboseLevel);
Properties moduleDependencies = loadPropertiesFile(moduleDependenciesPropertiesFile);
Properties moduleDependencies = new Properties();
try (InputStream inputStream = new FileInputStream(moduleDependenciesPropertiesFile);
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
moduleDependencies.load(reader);
} catch (FileNotFoundException e) {
throw new BuildException("Properties file does not exist: " + moduleDependenciesPropertiesFile.getPath());
} catch (IOException e) {
throw new BuildException("Exception reading properties file " + moduleDependenciesPropertiesFile.getPath(), e);
}
Map<String,SortedSet<String>> testScopeDependencies = new HashMap<>();
Map<String, String> testScopePropertyKeys = new HashMap<>();
for (Map.Entry entry : moduleDependencies.entrySet()) {
@ -809,48 +821,6 @@ public class GetMavenDependenciesTask extends Task {
return builder.toString().replace("solr-solr-", "solr-");
}
/**
* Parse the given properties file, performing non-recursive Ant-like
* property value interpolation, and return the resulting Properties.
*/
private Properties loadPropertiesFile(File file) {
final InputStream stream;
try {
stream = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new BuildException("Properties file does not exist: " + file.getPath());
}
// Properties files are encoded as Latin-1
final Reader reader = new InputStreamReader(stream, StandardCharsets.ISO_8859_1);
final Properties properties = new Properties();
try {
properties.load(reader);
} catch (IOException e) {
throw new BuildException("Exception reading properties file " + file, e);
} finally {
try {
reader.close();
} catch (IOException e) {
// do nothing
}
}
// Perform non-recursive Ant-like property value interpolation
StringBuffer buffer = new StringBuffer();
for (Map.Entry entry : properties.entrySet()) {
buffer.setLength(0);
Matcher matcher = PROPERTY_REFERENCE_PATTERN.matcher((String)entry.getValue());
while (matcher.find()) {
String interpolatedValue = properties.getProperty(matcher.group(1));
if (null != interpolatedValue) {
matcher.appendReplacement(buffer, interpolatedValue);
}
}
matcher.appendTail(buffer);
properties.setProperty((String)entry.getKey(), buffer.toString());
}
return properties;
}
/**
* Appends a &lt;dependency&gt; snippet to the given builder.
*/

View File

@ -0,0 +1,72 @@
package org.apache.lucene.dependencies;
/*
* 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 java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parse a properties file, performing non-recursive Ant-like
* property value interpolation, and return the resulting Properties.
*/
public class InterpolatedProperties extends Properties {
private static final Pattern PROPERTY_REFERENCE_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
/**
* Loads the properties file via {@link Properties#load(InputStream)},
* then performs non-recursive Ant-like property value interpolation.
*/
@Override
public void load(InputStream inStream) throws IOException {
throw new UnsupportedOperationException("InterpolatedProperties.load(InputStream) is not supported.");
}
/**
* Loads the properties file via {@link Properties#load(Reader)},
* then performs non-recursive Ant-like property value interpolation.
*/
@Override
public void load(Reader reader) throws IOException {
super.load(reader);
interpolate();
}
/**
* Perform non-recursive Ant-like property value interpolation
*/
private void interpolate() {
StringBuffer buffer = new StringBuffer();
for (Map.Entry entry : entrySet()) {
buffer.setLength(0);
Matcher matcher = PROPERTY_REFERENCE_PATTERN.matcher(entry.getValue().toString());
while (matcher.find()) {
String interpolatedValue = getProperty(matcher.group(1));
if (null != interpolatedValue) {
matcher.appendReplacement(buffer, interpolatedValue);
}
}
matcher.appendTail(buffer);
setProperty((String) entry.getKey(), buffer.toString());
}
}
}

View File

@ -17,6 +17,15 @@ package org.apache.lucene.validation;
* limitations under the License.
*/
import org.apache.ivy.Ivy;
import org.apache.ivy.core.LogOptions;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.conflict.NoConflictManager;
import org.apache.lucene.dependencies.InterpolatedProperties;
import org.apache.lucene.validation.ivyde.IvyNodeElement;
import org.apache.lucene.validation.ivyde.IvyNodeElementAdapter;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
@ -24,7 +33,6 @@ 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;
@ -33,23 +41,35 @@ import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -62,7 +82,7 @@ import java.util.regex.Pattern;
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 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]+(.*)");
@ -70,6 +90,10 @@ public class LibVersionsCheckTask extends Task {
= Pattern.compile("[ \t\f]*(.*?)(?:(?<!\\\\)[ \t\f]*)?\\\\");
private static final Pattern TRAILING_WHITESPACE_BACKSLASH_PATTERN
= Pattern.compile("(.*?)(?:(?<!\\\\)[ \t\f]*)?\\\\");
private static final Pattern MODULE_NAME_PATTERN = Pattern.compile("\\smodule\\s*=\\s*[\"']([^\"']+)[\"']");
private static final Pattern MODULE_DIRECTORY_PATTERN
= Pattern.compile(".*[/\\\\]((?:lucene|solr)[/\\\\].*)[/\\\\].*");
private Ivy ivy;
/**
* All ivy.xml files to check.
@ -77,14 +101,24 @@ public class LibVersionsCheckTask extends Task {
private Resources ivyXmlResources = new Resources();
/**
* Centralized Ivy versions properties file
* Centralized Ivy versions properties file: ivy-versions.properties
*/
private File centralizedVersionsFile;
/**
* License file mapper.
* Centralized Ivy ignore conflicts file: ivy-ignore-conflicts.properties
*/
private FileNameMapper licenseMapper;
private File ignoreConflictsFile;
/**
* Ivy settings file: ivy-settings.xml
*/
private File ivySettingsFile;
/**
* Location of common build dir: lucene/build/
*/
private File commonBuildDir;
/**
* A logging level associated with verbose logging.
@ -92,15 +126,28 @@ public class LibVersionsCheckTask extends Task {
private int verboseLevel = Project.MSG_VERBOSE;
/**
* Failure flag.
* All /org/name keys found in ivy-versions.properties,
* mapped to info about direct dependence and what would
* be conflicting indirect dependencies if Lucene/Solr
* were to use transitive dependencies.
*/
private boolean failures;
private Map<String,Versions> directDependencies = new LinkedHashMap<>();
/**
* All /org/name version keys found in ivy-versions.properties, and whether they
* are referenced in any ivy.xml file.
* All /org/name keys found in ivy-ignore-conflicts.properties,
* mapped to the set of indirect dependency versions that will
* be ignored, i.e. not trigger a conflict.
*/
private Map<String,Boolean> referencedCoordinateKeys = new LinkedHashMap<>();
private Map<String,HashSet<String>> ignoreConflictVersions = new HashMap<>();
private class Versions {
String direct;
LinkedHashMap<IvyNodeElement,Set<String>> conflictLocations = new LinkedHashMap<>(); // dependency path -> moduleNames
boolean directlyReferenced = false;
Versions(String direct) {
this.direct = direct;
}
}
/**
* Adds a set of ivy.xml resources to check.
@ -117,6 +164,18 @@ public class LibVersionsCheckTask extends Task {
centralizedVersionsFile = file;
}
public void setIvySettingsFile(File file) {
ivySettingsFile = file;
}
public void setCommonBuildDir(File file) {
commonBuildDir = file;
}
public void setIgnoreConflictsFile(File file) {
ignoreConflictsFile = file;
}
/**
* Execute the task.
*/
@ -125,8 +184,21 @@ public class LibVersionsCheckTask extends Task {
log("Starting scan.", verboseLevel);
long start = System.currentTimeMillis();
int errors = verifySortedCentralizedVersionsFile() ? 0 : 1;
int checked = 0;
setupIvy();
int numErrors = 0;
if ( ! verifySortedCoordinatesPropertiesFile(centralizedVersionsFile)) {
++numErrors;
}
if ( ! verifySortedCoordinatesPropertiesFile(ignoreConflictsFile)) {
++numErrors;
}
collectDirectDependencies();
if ( ! collectVersionConflictsToIgnore()) {
++numErrors;
}
int numChecked = 0;
@SuppressWarnings("unchecked")
Iterator<Resource> iter = (Iterator<Resource>)ivyXmlResources.iterator();
@ -142,89 +214,434 @@ public class LibVersionsCheckTask extends Task {
File ivyXmlFile = ((FileResource)resource).getFile();
try {
if ( ! checkIvyXmlFile(ivyXmlFile) ) {
failures = true;
errors++;
if ( ! checkIvyXmlFile(ivyXmlFile)) {
++numErrors;
}
if ( ! resolveTransitively(ivyXmlFile)) {
++numErrors;
}
} catch (Exception e) {
throw new BuildException("Exception reading file " + ivyXmlFile.getPath(), e);
throw new BuildException("Exception reading file " + ivyXmlFile.getPath() + " - " + e.toString(), e);
}
checked++;
++numChecked;
}
log("Checking for orphans in " + centralizedVersionsFile.getName(), verboseLevel);
for (Map.Entry<String,Boolean> entry : referencedCoordinateKeys.entrySet()) {
for (Map.Entry<String,Versions> entry : directDependencies.entrySet()) {
String coordinateKey = entry.getKey();
boolean isReferenced = entry.getValue();
if ( ! isReferenced) {
if ( ! entry.getValue().directlyReferenced) {
log("ORPHAN coordinate key '" + coordinateKey + "' in " + centralizedVersionsFile.getName()
+ " is not found in any " + IVY_XML_FILENAME + " file.",
Project.MSG_ERR);
failures = true;
errors++;
++numErrors;
}
}
log(String.format(Locale.ROOT, "Checked that %s has lexically sorted "
+ "'/org/name' keys and no duplicates or orphans, and scanned %d %s "
+ "file(s) for rev=\"${/org/name}\" format (in %.2fs.), %d error(s).",
centralizedVersionsFile.getName(), checked, IVY_XML_FILENAME,
(System.currentTimeMillis() - start) / 1000.0, errors),
errors > 0 ? Project.MSG_ERR : Project.MSG_INFO);
int numConflicts = emitConflicts();
if (failures) {
int messageLevel = numErrors > 0 ? Project.MSG_ERR : Project.MSG_INFO;
log("Checked that " + centralizedVersionsFile.getName() + " and " + ignoreConflictsFile.getName()
+ " have lexically sorted '/org/name' keys and no duplicates or orphans.",
messageLevel);
log("Scanned " + numChecked + " " + IVY_XML_FILENAME + " files for rev=\"${/org/name}\" format.",
messageLevel);
log("Found " + numConflicts + " indirect dependency version conflicts.");
log(String.format(Locale.ROOT, "Completed in %.2fs., %d error(s).",
(System.currentTimeMillis() - start) / 1000.0, numErrors),
messageLevel);
if (numConflicts > 0 || numErrors > 0) {
throw new BuildException("Lib versions check failed. Check the logs.");
}
}
/**
* Returns true if the "/org/name" coordinate keys in ivy-versions.properties
* are lexically sorted and are not duplicates.
* Collects indirect dependency version conflicts to ignore
* in ivy-ignore-conflicts.properties, and also checks for orphans
* (coordinates not included in ivy-versions.properties).
*
* Returns true if no orphans are found.
*/
private boolean verifySortedCentralizedVersionsFile() {
log("Checking for lexically sorted non-duplicated '/org/name' keys in: " + centralizedVersionsFile, verboseLevel);
final InputStream stream;
try {
stream = new FileInputStream(centralizedVersionsFile);
} catch (FileNotFoundException e) {
throw new BuildException("Centralized versions file does not exist: "
+ centralizedVersionsFile.getPath());
private boolean collectVersionConflictsToIgnore() {
log("Checking for orphans in " + ignoreConflictsFile.getName(), verboseLevel);
boolean orphansFound = false;
InterpolatedProperties properties = new InterpolatedProperties();
try (InputStream inputStream = new FileInputStream(ignoreConflictsFile);
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
properties.load(reader);
} catch (IOException e) {
throw new BuildException("Exception reading " + ignoreConflictsFile + ": " + e.toString(), e);
}
for (Object obj : properties.keySet()) {
String coordinate = (String)obj;
if (COORDINATE_KEY_PATTERN.matcher(coordinate).matches()) {
if ( ! directDependencies.containsKey(coordinate)) {
orphansFound = true;
log("ORPHAN coordinate key '" + coordinate + "' in " + ignoreConflictsFile.getName()
+ " is not found in " + centralizedVersionsFile.getName(),
Project.MSG_ERR);
} else {
String versionsToIgnore = properties.getProperty(coordinate);
List<String> ignore = Arrays.asList(versionsToIgnore.trim().split("\\s*,\\s*|\\s+"));
ignoreConflictVersions.put(coordinate, new HashSet<>(ignore));
}
}
}
return ! orphansFound;
}
// Properties files are encoded as Latin-1
final Reader reader = new InputStreamReader(stream, StandardCharsets.ISO_8859_1);
final BufferedReader bufferedReader = new BufferedReader(reader);
private void collectDirectDependencies() {
InterpolatedProperties properties = new InterpolatedProperties();
try (InputStream inputStream = new FileInputStream(centralizedVersionsFile);
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
properties.load(reader);
} catch (IOException e) {
throw new BuildException("Exception reading " + centralizedVersionsFile + ": " + e.toString(), e);
}
for (Object obj : properties.keySet()) {
String coordinate = (String)obj;
if (COORDINATE_KEY_PATTERN.matcher(coordinate).matches()) {
String direct = properties.getProperty(coordinate);
Versions versions = new Versions(direct);
directDependencies.put(coordinate, versions);
}
}
}
/**
* Transitively resolves all dependencies in the given ivy.xml file,
* looking for indirect dependencies with versions that conflict
* with those of direct dependencies. Versions conflict when a
* direct dependency's version is older than that of an indirect
* dependency with the same /org/name.
*
* Returns true if no version conflicts are found and no resolution
* errors occurred, false otherwise.
*/
private boolean resolveTransitively(File ivyXmlFile) {
boolean success = true;
ResolveOptions options = new ResolveOptions();
options.setDownload(false); // Download only module descriptors, not artifacts
options.setTransitive(true); // Resolve transitively, if not already specified in the ivy.xml file
options.setUseCacheOnly(false); // Download the internet!
options.setOutputReport(false); // Don't print to the console
options.setLog(LogOptions.LOG_QUIET); // Don't log to the console
options.setConfs(new String[] {"*"}); // Resolve all configurations
// Rewrite the ivy.xml, replacing all 'transitive="false"' with 'transitive="true"'
// The Ivy API is file-based, so we have to write the result to the filesystem.
String moduleName = "unknown";
String ivyXmlContent = xmlToString(ivyXmlFile);
Matcher matcher = MODULE_NAME_PATTERN.matcher(ivyXmlContent);
if (matcher.find()) {
moduleName = matcher.group(1);
}
ivyXmlContent = ivyXmlContent.replaceAll("\\btransitive\\s*=\\s*[\"']false[\"']", "transitive=\"true\"");
File transitiveIvyXmlFile = null;
try {
File buildDir = new File(commonBuildDir, "ivy-transitive-resolve");
if ( ! buildDir.exists() && ! buildDir.mkdirs()) {
throw new BuildException("Could not create temp directory " + buildDir.getPath());
}
matcher = MODULE_DIRECTORY_PATTERN.matcher(ivyXmlFile.getCanonicalPath());
if ( ! matcher.matches()) {
throw new BuildException("Unknown ivy.xml module directory: " + ivyXmlFile.getCanonicalPath());
}
String moduleDirPrefix = matcher.group(1).replaceAll("[/\\\\]", ".");
transitiveIvyXmlFile = new File(buildDir, "transitive." + moduleDirPrefix + ".ivy.xml");
try (Writer writer = new OutputStreamWriter(new FileOutputStream(transitiveIvyXmlFile), StandardCharsets.UTF_8)) {
writer.write(ivyXmlContent);
}
ResolveReport resolveReport = ivy.resolve(transitiveIvyXmlFile.toURI().toURL(), options);
IvyNodeElement root = IvyNodeElementAdapter.adapt(resolveReport);
for (IvyNodeElement directDependency : root.getDependencies()) {
String coordinate = "/" + directDependency.getOrganization() + "/" + directDependency.getName();
Versions versions = directDependencies.get(coordinate);
if (null == versions) {
log("ERROR: the following coordinate key does not appear in "
+ centralizedVersionsFile.getName() + ": " + coordinate);
success = false;
} else {
versions.directlyReferenced = true;
if (collectConflicts(directDependency, directDependency, moduleName)) {
success = false;
}
}
}
} catch (ParseException | IOException e) {
if (null != transitiveIvyXmlFile) {
log("Exception reading " + transitiveIvyXmlFile.getPath() + ": " + e.toString());
}
success = false;
}
return success;
}
/**
* Recursively finds indirect dependencies that have a version conflict with a direct dependency.
* Returns true if one or more conflicts are found, false otherwise
*/
private boolean collectConflicts(IvyNodeElement root, IvyNodeElement parent, String moduleName) {
boolean conflicts = false;
for (IvyNodeElement child : parent.getDependencies()) {
String coordinate = "/" + child.getOrganization() + "/" + child.getName();
Versions versions = directDependencies.get(coordinate);
if (null != versions) { // Ignore this indirect dependency if it's not also a direct dependency
String indirectVersion = child.getRevision();
if (isConflict(coordinate, versions.direct, indirectVersion)) {
conflicts = true;
Set<String> moduleNames = versions.conflictLocations.get(root);
if (null == moduleNames) {
moduleNames = new HashSet<>();
versions.conflictLocations.put(root, moduleNames);
}
moduleNames.add(moduleName);
}
conflicts |= collectConflicts(root, child, moduleName);
}
}
return conflicts;
}
/**
* Copy-pasted from Ivy's
* org.apache.ivy.plugins.latest.LatestRevisionStrategy
* with minor modifications
*/
private static final Map<String,Integer> SPECIAL_MEANINGS;
static {
SPECIAL_MEANINGS = new HashMap<>();
SPECIAL_MEANINGS.put("dev", -1);
SPECIAL_MEANINGS.put("rc", 1);
SPECIAL_MEANINGS.put("final", 2);
}
/**
* Copy-pasted from Ivy's
* org.apache.ivy.plugins.latest.LatestRevisionStrategy.MridComparator
* with minor modifications
*/
private static class LatestVersionComparator implements Comparator<String> {
@Override
public int compare(String rev1, String rev2) {
rev1 = rev1.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
rev1 = rev1.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
rev2 = rev2.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
rev2 = rev2.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
String[] parts1 = rev1.split("[-._+]");
String[] parts2 = rev2.split("[-._+]");
int i = 0;
for (; i < parts1.length && i < parts2.length; i++) {
if (parts1[i].equals(parts2[i])) {
continue;
}
boolean is1Number = isNumber(parts1[i]);
boolean is2Number = isNumber(parts2[i]);
if (is1Number && !is2Number) {
return 1;
}
if (is2Number && !is1Number) {
return -1;
}
if (is1Number && is2Number) {
return Long.valueOf(parts1[i]).compareTo(Long.valueOf(parts2[i]));
}
// both are strings, we compare them taking into account special meaning
Integer sm1 = SPECIAL_MEANINGS.get(parts1[i].toLowerCase(Locale.ROOT));
Integer sm2 = SPECIAL_MEANINGS.get(parts2[i].toLowerCase(Locale.ROOT));
if (sm1 != null) {
sm2 = sm2 == null ? new Integer(0) : sm2;
return sm1.compareTo(sm2);
}
if (sm2 != null) {
return new Integer(0).compareTo(sm2);
}
return parts1[i].compareTo(parts2[i]);
}
if (i < parts1.length) {
return isNumber(parts1[i]) ? 1 : -1;
}
if (i < parts2.length) {
return isNumber(parts2[i]) ? -1 : 1;
}
return 0;
}
private static final Pattern IS_NUMBER = Pattern.compile("\\d+");
private static boolean isNumber(String str) {
return IS_NUMBER.matcher(str).matches();
}
}
private static LatestVersionComparator LATEST_VERSION_COMPARATOR = new LatestVersionComparator();
/**
* Returns true if directVersion is less than indirectVersion, and
* coordinate=indirectVersion is not present in ivy-ignore-conflicts.properties.
*/
private boolean isConflict(String coordinate, String directVersion, String indirectVersion) {
boolean isConflict = LATEST_VERSION_COMPARATOR.compare(directVersion, indirectVersion) < 0;
if (isConflict) {
Set<String> ignoredVersions = ignoreConflictVersions.get(coordinate);
if (null != ignoredVersions && ignoredVersions.contains(indirectVersion)) {
isConflict = false;
}
}
return isConflict;
}
/**
* Returns the number of direct dependencies in conflict with indirect
* dependencies.
*/
private int emitConflicts() {
int conflicts = 0;
StringBuilder builder = new StringBuilder();
for (Map.Entry<String,Versions> directDependency : directDependencies.entrySet()) {
String coordinate = directDependency.getKey();
Set<Map.Entry<IvyNodeElement,Set<String>>> entrySet
= directDependency.getValue().conflictLocations.entrySet();
if (entrySet.isEmpty()) {
continue;
}
++conflicts;
Map.Entry<IvyNodeElement,Set<String>> first = entrySet.iterator().next();
int notPrinted = entrySet.size() - 1;
builder.append("VERSION CONFLICT: transitive dependency in module(s) ");
boolean isFirst = true;
for (String moduleName : first.getValue()) {
if (isFirst) {
isFirst = false;
} else {
builder.append(", ");
}
builder.append(moduleName);
}
builder.append(":\n");
IvyNodeElement element = first.getKey();
builder.append('/').append(element.getOrganization()).append('/').append(element.getName())
.append('=').append(element.getRevision()).append('\n');
emitConflict(builder, coordinate, first.getKey(), 1);
if (notPrinted > 0) {
builder.append("... and ").append(notPrinted).append(" more\n");
}
builder.append("\n");
}
if (builder.length() > 0) {
log(builder.toString());
}
return conflicts;
}
private boolean emitConflict(StringBuilder builder, String conflictCoordinate, IvyNodeElement parent, int depth) {
for (IvyNodeElement child : parent.getDependencies()) {
String indirectCoordinate = "/" + child.getOrganization() + "/" + child.getName();
if (conflictCoordinate.equals(indirectCoordinate)) {
String directVersion = directDependencies.get(conflictCoordinate).direct;
if (isConflict(conflictCoordinate, directVersion, child.getRevision())) {
for (int i = 0 ; i < depth - 1 ; ++i) {
builder.append(" ");
}
builder.append("+-- ");
builder.append(indirectCoordinate).append("=").append(child.getRevision());
builder.append(" <<< Conflict (direct=").append(directVersion).append(")\n");
return true;
}
} else if (hasConflicts(conflictCoordinate, child)) {
for (int i = 0 ; i < depth -1 ; ++i) {
builder.append(" ");
}
builder.append("+-- ");
builder.append(indirectCoordinate).append("=").append(child.getRevision()).append("\n");
if (emitConflict(builder, conflictCoordinate, child, depth + 1)) {
return true;
}
}
}
return false;
}
private boolean hasConflicts(String conflictCoordinate, IvyNodeElement parent) {
// the element itself will never be in conflict, since its coordinate is different
for (IvyNodeElement child : parent.getDependencies()) {
String indirectCoordinate = "/" + child.getOrganization() + "/" + child.getName();
if (conflictCoordinate.equals(indirectCoordinate)) {
if (isConflict(conflictCoordinate, directDependencies.get(conflictCoordinate).direct, child.getRevision())) {
return true;
}
} else if (hasConflicts(conflictCoordinate, child)) {
return true;
}
}
return false;
}
private String xmlToString(File ivyXmlFile) {
StringWriter writer = new StringWriter();
try {
StreamSource inputSource = new StreamSource(new FileInputStream(ivyXmlFile.getPath()));
Transformer serializer = TransformerFactory.newInstance().newTransformer();
serializer.transform(inputSource, new StreamResult(writer));
} catch (TransformerException | IOException e) {
throw new BuildException("Exception reading " + ivyXmlFile.getPath() + ": " + e.toString(), e);
}
return writer.toString();
}
private void setupIvy() {
IvySettings ivySettings = new IvySettings();
try {
ivySettings.setVariable("common.build.dir", commonBuildDir.getAbsolutePath());
ivySettings.setVariable("ivy.exclude.types", "source|javadoc");
ivySettings.setBaseDir(commonBuildDir);
ivySettings.setDefaultConflictManager(new NoConflictManager());
ivy = Ivy.newInstance(ivySettings);
ivy.configure(ivySettingsFile);
} catch (Exception e) {
throw new BuildException("Exception reading " + ivySettingsFile.getPath() + ": " + e.toString(), e);
}
}
/**
* Returns true if the "/org/name" coordinate keys in the given
* properties file are lexically sorted and are not duplicates.
*/
private boolean verifySortedCoordinatesPropertiesFile(File coordinatePropertiesFile) {
log("Checking for lexically sorted non-duplicated '/org/name' keys in: " + coordinatePropertiesFile, verboseLevel);
boolean success = true;
String line = null;
String currentKey = null;
String previousKey = null;
try {
try (InputStream stream = new FileInputStream(coordinatePropertiesFile);
Reader reader = new InputStreamReader(stream, StandardCharsets.ISO_8859_1);
BufferedReader bufferedReader = new BufferedReader(reader)) {
while (null != (line = readLogicalPropertiesLine(bufferedReader))) {
final Matcher keyMatcher = COORDINATE_KEY_PATTERN.matcher(line);
if ( ! keyMatcher.matches()) {
if ( ! keyMatcher.lookingAt()) {
continue; // Ignore keys that don't look like "/org/name"
}
currentKey = keyMatcher.group(1);
if (null != previousKey) {
int comparison = currentKey.compareTo(previousKey);
if (0 == comparison) {
log("DUPLICATE coordinate key '" + currentKey + "' in " + centralizedVersionsFile.getName(),
log("DUPLICATE coordinate key '" + currentKey + "' in " + coordinatePropertiesFile.getName(),
Project.MSG_ERR);
failures = true;
success = false;
} else if (comparison < 0) {
log("OUT-OF-ORDER coordinate key '" + currentKey + "' in " + centralizedVersionsFile.getName(),
log("OUT-OF-ORDER coordinate key '" + currentKey + "' in " + coordinatePropertiesFile.getName(),
Project.MSG_ERR);
failures = true;
success = false;
}
}
referencedCoordinateKeys.put(currentKey, false);
previousKey = currentKey;
}
} catch (IOException e) {
throw new BuildException("Exception reading centralized versions file: "
+ centralizedVersionsFile.getPath(), e);
} finally {
try { reader.close(); } catch (IOException e) { }
throw new BuildException("Exception reading " + coordinatePropertiesFile.getPath() + ": " + e.toString(), e);
}
return ! failures;
return success;
}
/**
@ -360,11 +777,10 @@ public class LibVersionsCheckTask extends Task {
+ " in " + ivyXmlFile.getPath(), Project.MSG_ERR);
fail = true;
}
if ( ! referencedCoordinateKeys.containsKey(coordinateKey)) {
if ( ! directDependencies.containsKey(coordinateKey)) {
log("MISSING key '" + coordinateKey + "' in " + centralizedVersionsFile.getPath(), Project.MSG_ERR);
fail = true;
}
referencedCoordinateKeys.put(coordinateKey, true);
}
}
tags.push(localName);

View File

@ -0,0 +1,180 @@
package org.apache.lucene.validation.ivyde;
/*
* 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.ivy.core.module.id.ModuleRevisionId;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* Assists in the further separation of concerns between the view and the Ivy resolve report. The view looks at the
* IvyNode in a unique way that can lead to expensive operations if we do not achieve this separation.
*
* This class is copied from org/apache/ivyde/eclipse/resolvevisualizer/model/IvyNodeElement.java at
* https://svn.apache.org/repos/asf/ant/ivy/ivyde/trunk/org.apache.ivyde.eclipse.resolvevisualizer/src/
*
* Changes include: uncommenting generics and converting to diamond operators where appropriate;
* removing unnecessary casts; removing javadoc tags with no description; and adding a hashCode() implementation.
*/
public class IvyNodeElement {
private ModuleRevisionId moduleRevisionId;
private boolean evicted = false;
private int depth = Integer.MAX_VALUE / 10;
private Collection<IvyNodeElement> dependencies = new HashSet<>();
private Collection<IvyNodeElement> callers = new HashSet<>();
private Collection<IvyNodeElement> conflicts = new HashSet<>();
/**
* The caller configurations that caused this node to be reached in the resolution, grouped by caller.
*/
private Map<IvyNodeElement,String[]>callerConfigurationMap = new HashMap<>();
/**
* We try to avoid building the list of this nodes deep dependencies by storing them in this cache by depth level.
*/
private IvyNodeElement[] deepDependencyCache;
@Override
public boolean equals(Object obj) {
if (obj instanceof IvyNodeElement) {
IvyNodeElement elem = (IvyNodeElement) obj;
if (elem.getOrganization().equals(getOrganization()) && elem.getName().equals(getName())
&& elem.getRevision().equals(getRevision()))
return true;
}
return false;
}
@Override
public int hashCode() {
int result = 1;
result = result * 31 + (null == getOrganization() ? 0 : getOrganization().hashCode());
result = result * 31 + (null == getName() ? 0 : getName().hashCode());
result = result * 31 + (null == getRevision() ? 0 : getRevision().hashCode());
return result;
}
public IvyNodeElement[] getDependencies() {
return dependencies.toArray(new IvyNodeElement[dependencies.size()]);
}
/**
* Recursive dependency retrieval
*
* @return The array of nodes that represents a node's immediate and transitive dependencies down to an arbitrary
* depth.
*/
public IvyNodeElement[] getDeepDependencies() {
if (deepDependencyCache == null) {
Collection deepDependencies = getDeepDependencies(this);
deepDependencyCache = (IvyNodeElement[])deepDependencies.toArray(new IvyNodeElement[deepDependencies.size()]);
}
return deepDependencyCache;
}
/**
* Recursive dependency retrieval
*/
private Collection<IvyNodeElement> getDeepDependencies(IvyNodeElement node) {
Collection<IvyNodeElement> deepDependencies = new HashSet<>();
deepDependencies.add(node);
IvyNodeElement[] directDependencies = node.getDependencies();
for (int i = 0; i < directDependencies.length; i++) {
deepDependencies.addAll(getDeepDependencies(directDependencies[i]));
}
return deepDependencies;
}
/**
* @return An array of configurations by which this module was resolved
*/
public String[] getCallerConfigurations(IvyNodeElement caller) {
return callerConfigurationMap.get(caller);
}
public void setCallerConfigurations(IvyNodeElement caller, String[] configurations) {
callerConfigurationMap.put(caller, configurations);
}
public String getOrganization() {
return moduleRevisionId.getOrganisation();
}
public String getName() {
return moduleRevisionId.getName();
}
public String getRevision() {
return moduleRevisionId.getRevision();
}
public boolean isEvicted() {
return evicted;
}
public void setEvicted(boolean evicted) {
this.evicted = evicted;
}
public int getDepth() {
return depth;
}
/**
* Set this node's depth and recursively update the node's children to relative to the new value.
*/
public void setDepth(int depth) {
this.depth = depth;
for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
IvyNodeElement dependency = (IvyNodeElement) iter.next();
dependency.setDepth(depth + 1);
}
}
public IvyNodeElement[] getConflicts() {
return conflicts.toArray(new IvyNodeElement[conflicts.size()]);
}
public void setConflicts(Collection conflicts) {
this.conflicts = conflicts;
}
public ModuleRevisionId getModuleRevisionId() {
return moduleRevisionId;
}
public void setModuleRevisionId(ModuleRevisionId moduleRevisionId) {
this.moduleRevisionId = moduleRevisionId;
}
public void addCaller(IvyNodeElement caller) {
callers.add(caller);
caller.dependencies.add(this);
}
public IvyNodeElement[] getCallers() {
return callers.toArray(new IvyNodeElement[callers.size()]);
}
}

View File

@ -0,0 +1,137 @@
package org.apache.lucene.validation.ivyde;
/*
* 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.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.IvyNode;
import org.apache.ivy.core.resolve.IvyNodeCallers;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This class is copied from org/apache/ivyde/eclipse/resolvevisualizer/model/IvyNodeElementAdapter.java at
* https://svn.apache.org/repos/asf/ant/ivy/ivyde/trunk/org.apache.ivyde.eclipse.resolvevisualizer/src/
*
* Changes include: uncommenting generics and converting to diamond operators where appropriate;
* removing unnecessary casts; and removing javadoc tags with no description.
*/
public class IvyNodeElementAdapter {
/**
* Adapt all dependencies and evictions from the ResolveReport.
* @return the root node adapted from the ResolveReport
*/
public static IvyNodeElement adapt(ResolveReport report) {
Map<ModuleRevisionId,IvyNodeElement> resolvedNodes = new HashMap<>();
IvyNodeElement root = new IvyNodeElement();
root.setModuleRevisionId(report.getModuleDescriptor().getModuleRevisionId());
resolvedNodes.put(report.getModuleDescriptor().getModuleRevisionId(), root);
@SuppressWarnings("unchecked") List<IvyNode> dependencies = report.getDependencies();
// First pass - build the map of resolved nodes by revision id
for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
IvyNode node = (IvyNode) iter.next();
if (node.getAllEvictingNodes() != null) {
// Nodes that are evicted as a result of conf inheritance still appear
// as dependencies, but with eviction data. They also appear as evictions.
// We map them as evictions rather than dependencies.
continue;
}
IvyNodeElement nodeElement = new IvyNodeElement();
nodeElement.setModuleRevisionId(node.getResolvedId());
resolvedNodes.put(node.getResolvedId(), nodeElement);
}
// Second pass - establish relationships between the resolved nodes
for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
IvyNode node = (IvyNode) iter.next();
if (node.getAllEvictingNodes() != null) {
continue; // see note above
}
IvyNodeElement nodeElement = resolvedNodes.get(node.getResolvedId());
IvyNodeCallers.Caller[] callers = node.getAllRealCallers();
for (int i = 0; i < callers.length; i++) {
IvyNodeElement caller = resolvedNodes.get(callers[i].getModuleRevisionId());
if (caller != null) {
nodeElement.addCaller(caller);
nodeElement.setCallerConfigurations(caller, callers[i].getCallerConfigurations());
}
}
}
IvyNode[] evictions = report.getEvictedNodes();
for (int i = 0; i < evictions.length; i++) {
IvyNode eviction = evictions[i];
IvyNodeElement evictionElement = new IvyNodeElement();
evictionElement.setModuleRevisionId(eviction.getResolvedId());
evictionElement.setEvicted(true);
IvyNodeCallers.Caller[] callers = eviction.getAllCallers();
for (int j = 0; j < callers.length; j++) {
IvyNodeElement caller = resolvedNodes.get(callers[j].getModuleRevisionId());
if (caller != null) {
evictionElement.addCaller(caller);
evictionElement.setCallerConfigurations(caller, callers[j].getCallerConfigurations());
}
}
}
// Recursively set depth starting at root
root.setDepth(0);
findConflictsBeneathNode(root);
return root;
}
/**
* Derives configuration conflicts that exist between node and all of its descendant dependencies.
*/
private static void findConflictsBeneathNode(IvyNodeElement node) {
// Derive conflicts
Map<ModuleId,Collection<IvyNodeElement>> moduleRevisionMap = new HashMap<>();
IvyNodeElement[] deepDependencies = node.getDeepDependencies();
for (int i = 0; i < deepDependencies.length; i++) {
if (deepDependencies[i].isEvicted())
continue;
ModuleId moduleId = deepDependencies[i].getModuleRevisionId().getModuleId();
if (moduleRevisionMap.containsKey(moduleId)) {
Collection<IvyNodeElement> conflicts = moduleRevisionMap.get(moduleId);
conflicts.add(deepDependencies[i]);
for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
IvyNodeElement conflict = (IvyNodeElement) iter.next();
conflict.setConflicts(conflicts);
}
} else {
List<IvyNodeElement> immutableMatchingSet = Arrays.asList(deepDependencies[i]);
moduleRevisionMap.put(moduleId, new HashSet<>(immutableMatchingSet));
}
}
}
}