diff --git a/.gitignore b/.gitignore index f79c9285cd4c..cd0121172e71 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ .classpath .settings/ .svn/ +.cache/ bin/ # Intellij *.ipr diff --git a/Documentation/CACHE-HOWTO.md b/Documentation/CACHE-HOWTO.md new file mode 100644 index 000000000000..aa15f626af65 --- /dev/null +++ b/Documentation/CACHE-HOWTO.md @@ -0,0 +1,204 @@ + + +### Overview + +Cache configuration provides you additional control over incremental maven behavior. Follow it step by step to +understand how it works and figure out your optimal config + +### Minimal config + +Absolutely minimal config which enables incremental maven with local cache + +```xml + + + + + true + XX + + + + + {*.java,*.xml,*.properties} + + + +``` + +### Enabling remote cache + +Just add `` section under `` + +```xml + + + true + XX + + https://yourserver:port + + +``` + +### Adding more file types to input + +Add all the project specific source code files in ``. Scala in this case: + +```xml + + + + {*.java,*.xml,*.properties,*.scala} + + +``` + +### Adding source directory for bespoke project layouts + +In most of the cases incremental maven will recognize directories automatically by build introspection. If not, you can +add additional directories with ``. Also you can filter out undesirable dirs and files by using exclude tag + +```xml + + + + {*.java,*.xml,*.properties,*.scala} + importantdir/ + tempfile.out + + +``` + +### Plugin property is env specific (breaks checksum and caching) + +Consider to exclude env specific properties: + +```xml + + + + ... + + + + argLine + + + +``` + +Implications - builds with different `argLine` will have identical checksum. Validate that is semantically valid. + +### Plugin property points to directory where only subset of files is relevant + +If plugin configuration property points to `somedir` it will be scanned with default glob. You can tweak it with custom +processing rule + +```xml + + + + ... + + + + + + + + +``` + +### Local repository is not updated because `install` is cached + +Add `executionControl/runAlways` section + +```xml + + + + ... + + + ... + + + + + + unpack-autoupdate + + + install + + + + +``` + +### I occasionally cached build with `-DskipTests=true` and tests do not run now + +If you add command line flags to your build, they do not participate in effective pom - maven defers final value +resolution to plugin runtime. To invalidate build if filed value is different in runtime, add reconciliation section +to `executionControl`: + +```xml + + + + ... + + + + + + + + + + +``` + +Please notice `skipValue` attribute. It denotes value which forces skipped execution. +Read `propertyName="skipTests" skipValue="true"` as if property skipTests has value true, plugin will skip execution If +you declare such value incremental maven will reuse appropriate full-build though technically they are different, but +because full-build is better it is safe to reuse + +### How to renormalize line endings in working copy after committing .gitattributes (git 2.16+) + +Ensure you've committed (and ideally pushed everything) - no changes in working copy. After that: + +```shell +# Rewrite objects and update index +git add --renormalize . +# Commit changes +git commit -m "Normalizing line endings" +# Remove working copy paths from git cache +git rm --cached -r . +# Refresh with new line endings +git reset --hard +``` + +### I want to cache interim build and override it later with final version + +Solution: set `-Dremote.cache.save.final=true` to nodes which produce final builds. Such builds will not be overridden +and eventually will replace all interim builds \ No newline at end of file diff --git a/Documentation/CACHE-PARAMETERS.md b/Documentation/CACHE-PARAMETERS.md new file mode 100644 index 000000000000..7633ddbea158 --- /dev/null +++ b/Documentation/CACHE-PARAMETERS.md @@ -0,0 +1,52 @@ + + +# Overview + +This documents contains various configuration parameters supported by cache engine + +## Command line flags + +| Parameter | Description | Usage Scenario | +| ----------- | ----------- | ----------- | +| `-Dremote.cache.configPath=true/false` | Location of cache configuration file | Cache config is not in default location | +| `-Dremote.cache.enabled=true/false` | Remote cache and associated features disabled/enabled | To remove noise from logs then remote cache is not available | +| `-Dremote.cache.save.enabled=true/false` | Remote cache save allowed or not | To designate nodes which allowed to push in remote shared cache | +| `-Dremote.cache.save.final=true/false` | Is it allowed or not to override produced cache | To ensure that reference build is not overriden by interim build | +| `-Dremote.cache.failFast=true/false` | Fail on the first module whcih cannot be restored from cache | Remote cache setup/tuning/troubleshooting | +| `-Dremote.cache.baselineUrl=` | Location of baseline build for comparison | Remote cache setup/tuning/troubleshooting | + +## Project level properties + +Project level parameters allow overriding global parameters on project level Must be specified as project properties: + +```xml + + + ... + + {*.css} + + +``` + +| Parameter | Description | +| ----------------------------- | ----------- | +| `remote.cache.input.glob` | Project specific glob to select sources. Overrides global glob. | +| `remote.cache.input` | Property prefix to mark paths which must be additionally scanned for source code. Value of property starting with this prefix will be treated as path relatively to current project/module | +| `remote.cache.exclude` | Property prefix to mark paths which must be excluded from source code search. Value of property starting with this prefix will be treated as path to current project/module | +| `remote.cache.processPlugins` | Introspect plugins to find inputs or not. Default is true. | diff --git a/Documentation/CACHE-REMOTE.md b/Documentation/CACHE-REMOTE.md new file mode 100644 index 000000000000..f5fe38721d70 --- /dev/null +++ b/Documentation/CACHE-REMOTE.md @@ -0,0 +1,249 @@ + + +# Overview + +This document describes generic approach to cache setup. The process require expertise in maven equivalent to expertise +required to author and Maven your project build, it implies good knowledge of both Maven and the project. Due to Maven +model limitation the process is manual, but allows you to achieve sufficient control and transparency over caching +logic. + +# Step-By-Step + +## Fork branch for cache setup purposes + +It is recommended to fork branch for cache setup purposes as you might need to do changes to project build as long as +you go. + +## Setup http server to store artifacts + +In order to share build results you need shared storage. Basically any http server which supports http PUT/GET/HEAD +operations will work. In our case it is a Nginx OSS with file system module. Add the url to config and +change `remote@enabled` to true: + +```xml + + + http://your-buildcache-url + +``` + +Known limitations: auth is not supported yet + +## Build selection + +In order to share build results, you need a golden source of builds. Build stored in cache ideally should be a build +assembled in the most correct, comprehensive and complete way. In such a case you can make sure that whoever reuses such +build doesn't compromise quality of own build. Often per pull requests builds are the best candidates to populate cache +because they seed cache fast and provide sufficient quality safeguards. + +## CI Build setup to seed shared cache + +In order to share build results, you need to store builds in the shared storage. Default scheme is to configure +designated CI nodes only to push in cache and prohibit elsewhere. In order to allow writes in remote cache add jvm +property to designated CI builds. + +``` +-Dremote.cache.save.enabled=true +``` + +Run your branch, review log and ensure that artifacts are uploaded to remote cache. Now, rerun build and ensure that it +completes almost instantly because it is fully cached. Hint: consider limiting first attempts to single agent/node to +simplify troubleshooting. + +## Normalize local builds to reuse CI build cache + +As practice shows, developers often don't realize that builds they run in local and CI environments are different. So +straightforward attempt to reuse remote cache in local build usually results in cache misses because of difference in +plugins, parameters, profiles, environment, etc. In order to reuse results you might need to change poms, cache config, +CI jobs and the project itself. This part is usually most challenging and time-consuming. Follow steps below to +iteratively achieve working configuration. + +### Before you start + +Before you start, please keep in mind basic principles: + +* Cache is checksum based, it is a complex hash function essentially. In order to to produce the same hash the source + code, effective poms and dependencies should match. +* There is no built-in normalization of line endings in this implementation, file checksum calculation is raw bytes + based. The most obvious implication could be illustrated by a simple Git checkout. By default git will check out + source code with CRLF line endings on win and LF on Linux. Because of that builds over same commit on a Linux agent + and local build on Windows workstation will yield different checksums. +* Parameters of plugins are manually tracked ony. For example to avoid of accidentally reusing builds which never run + tests ensure that critical surfire parameters are tracked (`skipTests` and similar) in config. The same applies for + all over plugins. + +### Configure local build in debug mode + +To minimize distractions and simplify understanding of discrepancies following is recommended: + +* Run build with single threaded builder to make sure logs from different modules do not interfere +* Enable cache fail fast mode to focus on the blocking failure +* Provide reference to the CI build as a baseline for comparison between your local and remote builds. Go to the + reference CI build and one of the final lines of the build should be + +``` +[INFO] [CACHE][artifactId] Saved to remote cache https://your-cache-url/<...>/915296a3-4596-4eb5-bf37-f6e13ebe087e/cache-report.xml. +``` + +followed by a link to a `cache-report.xml` file. The `cache-report.xml` contains aggregated information about the +produced cache and could be used as a baseline for comparison. + +* Run local build. Command line should look similar to this: + +```bash +mvnw verify -Dremote.cache.failFast=true -Dremote.cache.baselineUrl=https://url-from-ci-build-to-cache-report.xml +``` + +Once discrepancy between remote and local builds detected cache will fail with diagnostic info +in `target/incremental-maven` directory: + +``` +* buildinfo-baseline-3c64673e23259e6f.xml - build specficiation from baseline build +* buildinfo-db43936e0666ce7.xml - build specification of locall build +* buildsdiff.xml - comparison report with list of discrepancies +``` + +Review `buildsdiff.xml` file and eliminate detected discrepancies.You can also diff build-info files directly to get low +level insights. See techniques to configure cache in [How-To](CACHE-HOWTO.md) and troubleshooting of typical issues in +the section below. + +# Common issues + +## Issue 1: Local checkout is with different line endings + +Solution: normalise line endings. Current implementation doesn't have built-in line endings normalization, it has to be +done externally. In git it is recommended to use `.gitattributes` file to establish consistent line endings across all +envs for file types specific to this project + +## Issue 2: Effective poms mismatch because of plugins filtering by profiles + +Different profiles between remote and local builds results in different text of effective poms and break checksums. +Solution: instead of adding/removing specific plugins from build altogether with profiles use profile specific `skip` +or `disabled` flags instead. Instead of: + + ``` + + + run-plugin-in-ci-only + + + + surefire-report-maven-plugin + + + + + + + + + ``` + +Use: + + ```xml + + + + true + + + + + maven-surefire-plugin + + + ${skip.plugin.property} + + + + + + + run-plugin-in-ci-only + + + false + + + + ``` + +Hint: effective poms could be found in `buildinfo` files under `/build/projectsInputInfo/item[@type='pom']` +xpath (`item type="pom"`). + +## Issue 3: Effective pom mismatch because of environment specific properties + +Potential reason: Sometimes it is not possible to avoid discrepancies in different environments - for example if you +need to invoke command line command, it will be likely different on win and linux. Such commands will appear in +effective pom as a different literal values and will result in checksum mismatch Solution: filter out such properties +from cache effective pom: + +```xml + + + + ... + + + + argLine + + + +``` + +## Issue 4: Unexpected or transient files in checksum calculation + +Potential reasons: plugins or tests emit temporary files (logs and similar) in non-standard locations Solution: adjust +global exclusions list to filter out unexpected files: + +``` + + tempfile.out + +``` + +see sample config for exact syntax + +## Issue 5: Difference in tracked plugin properties + +Tracked property in config means it is critical for determining is build up to date or not. Discrepancies could happen +for any plugin for a number of reasons. Example: local build is using java target 1.6, remote: 1.8. `buildsdiff.xml` +will produce something like + +``` + +``` + +Solution is at your discretion. If the property is tracked, out-of-date status is fair and expected. If you want to +relax consistency rules in favor of compatibility, remove property from tracked list + +## Issue 5: Version changes invalidate effective pom checksum + +Current implementation doesn't support version changes between cache entries. It will result in cache invalidation for +each new version. +To mitigate the issue please consider migrating off traditional maven release approach - try to use single version id in +project (eg `MY-PROJECT-LOCAL`). Such approach simplifies git branching workflow significantly. + +Deployment of artifacts with specific version from builds with cache is not supported yet. + diff --git a/Documentation/CACHE.md b/Documentation/CACHE.md new file mode 100644 index 000000000000..391becc7b5f7 --- /dev/null +++ b/Documentation/CACHE.md @@ -0,0 +1,170 @@ + + +## Overview + +Idea of Incremental Maven is to specify module inputs and outputs and make them known to standard maven core. This +allows accurate analysis and determination of out-of-date build artifacts in the build dependencies graph. Making the +dependency graph analysis deterministic leads to improvements in build times by avoiding re-building unnecessary +modules. +Cache does not make any low-level interventions to build process and delegates actual build work to maven core. This +guarantees that build results are identical to results produced by standard maven and are fully reproducible. +To achieve accurate input and outputs calculation incremental maven combines automatic introspection +of [project object model](https://maven.apache.org/pom.html#What_is_the_POM) in conjunction with configuration-driven +rules for fine-grained content and execution control. For content analysis it digests based approach which is more +reliable over widely used file timestamps in tools like Make or Apache Ant. Deterministic build state allows reliably +cache even intermediate outputs of build and share them between teams using remote cache. Deterministic inputs +calculation allows distributed and parallel builds running in heterogeneous environments (like cloud of build agents) +could efficiently reuse cached build artifacts. Therefore incremental maven is particularly well-suited for large maven +projects that have significant number of small modules. Remote cache in conjunction with precise input identification +effectively enables "change once - build once" approach. + +### Maven insights + +The challenge of implementing build cache in Maven is that domain model is overly generic and doesn't support well +reproducible builds. You might have never thought of that, but it is a reality that 2 different Maven builds from the +same source code normally produce 2 different results. The question here is tolerance level - can you accept particular +discrepancies or not. For most of teams artifacts produced in the same build environment from the same source code will +be considered equivalent and technical differences between them (like different timestamps in jar manifests) could be +ignored. Now consider scenario when artifact is first produced with compiler X and cached but later without touching a +update compiler changes to Y and yields significantly different outcomes of compilation. Ask yourself a question \- am I +consider artifacts of such builds equivalent? Both Yes and No outcomes are pretty possible and could be even desirable +in different scenarios. When productivity and performance are the primary concerns it could be desirable to tolerate +insignificant discrepancies and maximise reuse of cached builds. As long as correctness in focus there could be demand +to comply with the exact release process. In the same way as with classic Maven, decision stays with you - what is +acceptable difference between builds. In the same way as with classic Maven the previous build is just an approximation +of today build with some tolerance (implementation, configuration and environment driven). + +### Implementation insights + +At very simple form, the incremental maven is essentially a hash function which takes maven project and produces hash +code (checksum). Then hash value is used to fetch and restore build result. +As with any hash, there could be collisions and instabilities. Collision could happen if the same hash produced from the +different build states and will result in unintended reuse. Instability means that same input yields different hash sums +in different runs - resulting in cache miss. The ultimate target is to achieve desired balance between collisions ( +sufficient correctness) and stability (sufficient reuse). In current implementation this is achieved by configuring +project specific processing rules in static configuration file. To avoid unintentional collisions and achieve better +correctness need to ensure that every critical file and plugin parameter accounted in build inputs. In order to achieve +better reuse need to ensure that non-critical files (test logs, readme and similar) and non-critical plugin parameters ( +like number of threads in build) are filtered out from build inputs. Essentially cache configuration is a process of +inspecting build, taking these decision and reflect them in the cache configuration. + +Please notice though idea of perfectly matching builds might be tempting, but it is not practical with regard to +caching. Perfect correctness means that not a single build could be reused and renders whole idea of builds caching +useless. Whatever build tool you use, there will be always a tradeoff which might be acceptable or not in particular +situation. Incremental Maven provides flexible and transparent control over caching policy and allows achieving desired +outcomes - maximize reusability or maximize equivalence between pre-cached candidates and requested builds. + +## Usage + +Cornerstone principle of using this tool is that it is delivered as is. Though the tool went through thorough +verification it's still consumer's responsibility to verify final product quality. + +### Recommended Scenarios + +Given all the information above, the Incremental Maven is recommended to use in scenarios when productivity and +performance are in priority. Typical cases are: + +* Speedup CI. In conjunction with remote cache incremental maven could drastically reduce build times, validate pull + requests faster and reduce load on CI nodes +* Speedup developer builds. By reusing cached builds developers could verify changes much faster and be more productive. + No more `-DskipTests` and similar. +* Assemble artifacts faster. In some development models it might be critical to have as fast build/deploy cycle as + possible. Caching helps to cut down time drastically in such scenarios because it doesn't require to build cached + dependencies. + +For cases there correctness must be ensured (eg prod builds), it is recommended to disable cache and do clean builds. +This also allows you to validate cache correctness and reconcile cache outcomes on CI process. + +## Getting Started + +To on-board incremental maven you need to complete several steps: + +* Get incremental maven distribution +* Add cache config in .mvn +* Validate build results and iteratively adjust config to project specifics +* Migrate CI to incremental maven with remote cache (to get full benefit) - optional + +### Get incremental maven distribution + +The recommended way is to add [Takari Maven Wrapper](https://github.com/takari/maven-wrapper) to your project. In that +case `maven-wrapper.properties` should reference the latest incremental maven distribution: + +```properties +distributionUrl=https://your-server/maven-incremental.zip +wrapperUrl=https://your-server/maven-wrapper-0.5.5.jar +``` + +Benefits of using maven wrapper are following: + +* simple distribution across workstations and CI envs +* maven stays compatible to your branch +* further upgrades are simplified significantly + If you refuse wrapper - then download, unzip and install it just as usual maven. Further it will be assumed you use + maven wrapper (`mvnw`) + +### Adding cache config + +Copy [default config](maven-cache-config.xml) and [xml schema](../maven-core/src/main/resources/cache-config.xsd) +to [.mvn](https://maven.apache.org/configure.html) dir of yor project. +To get overall understanding of cache machinery it is recommended to review the config and read comments. In typical +scenario you need to adjust: + +* remote cache location +* source code files glob +* plugins reconciliation rules - add critical plugin parameters to reconciliation +* add non-standard source code locations (most of locations discovered automatically from project and plugins config, + but still there might be edge cases) + +See also: + +* [Remote cache setup](CACHE-REMOTE.md) - instruction how to setup shared cache +* [Cache How-To](CACHE-HOWTO.md) - cookbook for frequently encountered questions +* [Cache Parameters](CACHE-PARAMETERS.md) - description of supported parameters +* Attached [sample config file](maven-cache-config.xml) and elements annotations in xsd schema. (Ctrl+Q in idea should + show annotations in popup) + +### Adjusting cache config + +Having incremental maven and the config in place you're all set. To run first cacheable build just +execute: `mvnw clean install` + +* Ensure that the config is picked up and incremental maven is picked up. Just check log output - you will notice cache + related output or initialization error message. +* Navigate to your local repo directory - there should be sibling next to your local repo named `cache` +* Find `buildinfo.xml` for typical module and review it. Ensure that + * expected source code files are present in build info + * all critical plugins and their critical parameters are covered by config + +Notice - in configuration you should find best working trade-off between fairness and cache efficiency. Adding +unnecessary rules and checks could reduce both performance and cache efficiency (hit rate). + +### Adding caching CI and remote cache + +To leverage remote cache feature you need web server which supports get/put operations +like [Nginx OSS](http://nginx.org/en/) (with fs module) or binary repo in Artifactory. It is recommended to populate +remote cache from CI build. Benefits are: + +* such scheme provides predictable and consistent artifacts in remote cache +* usually CI builds project fast enough to populate cache for team members See [Remote cache setup](CACHE-REMOTE.md) for + detailed description of cache setup + +## Credits +CacheConfigImpl +* Maximilian Novikov - Project lead. Idea, design, coordination and verification. +* Alexander Ashitkin - Co-design and implementation of the majority of functionality +* Alexander Novoselov - Hashing module implementation \ No newline at end of file diff --git a/Documentation/maven-cache-config.xml b/Documentation/maven-cache-config.xml new file mode 100644 index 000000000000..a2a7ad8ee1c3 --- /dev/null +++ b/Documentation/maven-cache-config.xml @@ -0,0 +1,102 @@ + + + + + + true + SHA-256 + true + + PROJECT-LOCAL + + + http://host:port + + + 3 + + + + + + + {*.java,*.groovy,*.yaml,*.svcd,*.proto,*assembly.xml,assembly*.xml,*logback.xml,*.vm,*.ini,*.jks,*.properties,*.sh,*.bat} + + src/ + pom.xml + + + + 111 + + + + + + + + 1 + 2 + + + + + + + + + + + my-execution-id + + + install + + + deploy + + + deploy-local + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 0c77dabf55ae..88b0a333f257 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ build, reporting and documentation from a central piece of information. If you think you have found a bug, please file an issue in the [Maven Issue Tracker](https://issues.apache.org/jira/browse/MNG). +Incremental Build and Cache +------------- +This maven version supports Incremental Build feature which calculates out-of-date modules in the build dependencies graph and improves build times by avoiding re-building unnecessary modules. +Read [cache guide](Documentation/CACHE.md) for more details + Documentation ------------- diff --git a/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm b/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm index 25ac46f3770f..6ac9f31124b7 100644 --- a/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm +++ b/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm @@ -18,13 +18,14 @@ ## -Apache Maven includes a number of components and libraries with separate -copyright notices and license terms. Your use of those components are -subject to the terms and conditions of the following licenses: +Apache Maven includes a number of components and libraries with separate +copyright notices and license terms. Your use of those components are +subject to the terms and conditions of the following licenses: ## #set ( $apacheMavenGroupIds = [ "org.apache.maven", "org.apache.maven.wagon", "org.apache.maven.resolver", "org.apache.maven.shared" ] ) #set ( $MITLicenseNames = [ "MIT License", "MIT license", "The MIT License" ] ) +#set ( $CDDLLicenseNames = [ "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", "CDDL/GPLv2+CE", "CDDL 1.1", "GPL2 w/ CPE" ] ) #foreach ( $project in $projects ) #**##foreach ( $license in $project.licenses) #* *##set ( $groupId = $project.artifact.groupId ) @@ -32,8 +33,8 @@ subject to the terms and conditions of the following licenses: #* *##if ( !$apacheMavenGroupIds.contains( $groupId ) ) #* *### advertise about each non-Maven dependency #* *### -#* *### infer SPDX license code -#* *##if ( $license.name == "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0" ) +#* *### infer SPDX license id +#* *##if ( $CDDLLicenseNames.contains( $license.name ) ) #* *##set ( $spdx = 'CDDL-1.0' ) #* *##elseif ( $MITLicenseNames.contains( $license.name ) ) #* *##set ( $spdx = 'MIT' ) @@ -41,9 +42,17 @@ subject to the terms and conditions of the following licenses: #* *##set ( $spdx = 'EPL-1.0' ) #* *##elseif ( $license.url.contains( "www.apache.org/licenses/LICENSE-2.0" ) ) #* *##set ( $spdx = 'ASL-2.0' ) +#* *##elseif ( $license.url.contains( "www.eclipse.org/org/documents/edl-v10.php" ) ) +#* *##set ( $spdx = 'EDL-1.0' ) +#* *##elseif ( $license.url.contains( "www.mozilla.org/en-US/MPL/1.1" ) ) +#* *##set ( $spdx = 'MPL-1.1' ) +#* *##elseif ( $license.url.contains( "www.gnu.org/licenses/gpl" ) ) +#* *##set ( $spdx = 'GPL-3.0' ) +#* *##elseif ( $license.url.contains( "www.gnu.org/licenses/lgpl" ) ) +#* *##set ( $spdx = 'LGPL-3.0' ) #* *##else #* *### unrecognized license will require analysis to know obligations -#* *##set ( $spdx = 'unrecognized' ) +#* *##set ( $spdx = "unrecognized: $license.name $license.url" ) #* *##end #* *### #* *### fix project urls that are wrong in pom diff --git a/apache-maven/src/main/appended-resources/licenses/EDL-1.0.txt b/apache-maven/src/main/appended-resources/licenses/EDL-1.0.txt new file mode 100644 index 000000000000..259e0377f30f --- /dev/null +++ b/apache-maven/src/main/appended-resources/licenses/EDL-1.0.txt @@ -0,0 +1,23 @@ +Eclipse Distribution License - v 1.0 +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided +that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- 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. +- Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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. \ No newline at end of file diff --git a/apache-maven/src/main/appended-resources/licenses/GPL-3.0.txt b/apache-maven/src/main/appended-resources/licenses/GPL-3.0.txt new file mode 100644 index 000000000000..e72bfddabc15 --- /dev/null +++ b/apache-maven/src/main/appended-resources/licenses/GPL-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/apache-maven/src/main/appended-resources/licenses/LGPL-3.0.txt b/apache-maven/src/main/appended-resources/licenses/LGPL-3.0.txt new file mode 100644 index 000000000000..153d416dc8d2 --- /dev/null +++ b/apache-maven/src/main/appended-resources/licenses/LGPL-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/apache-maven/src/main/appended-resources/licenses/MPL-1.1.txt b/apache-maven/src/main/appended-resources/licenses/MPL-1.1.txt new file mode 100644 index 000000000000..7bcf5c0e2885 --- /dev/null +++ b/apache-maven/src/main/appended-resources/licenses/MPL-1.1.txt @@ -0,0 +1,472 @@ +http://www.mozilla.org/MPL/MPL-1.1.html + + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the MPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + https://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/maven-core/pom.xml b/maven-core/pom.xml index 1f52e1b63bad..f9d19d4eb865 100644 --- a/maven-core/pom.xml +++ b/maven-core/pom.xml @@ -141,6 +141,32 @@ under the License. hamcrest-library test + + org.apache.httpcomponents + httpclient + 4.5.6 + + + net.openhft + zero-allocation-hashing + 0.9 + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.3 + + + com.sun.xml.bind + jaxb-impl + 2.3.3 + runtime + + + com.github.albfernandez + juniversalchardet + 2.4.0 + @@ -161,6 +187,7 @@ under the License. plugin-manager.txt project-builder.txt src/site/resources/design/** + src/main/resources/cache-* @@ -252,6 +279,26 @@ under the License. + + org.codehaus.mojo + jaxb2-maven-plugin + 2.5.0 + + + xjc + + xjc + + + + + org.apache.maven.caching.jaxb + + src/main/resources/cache-config.xsd + src/main/resources/cache-domain.xsd + + + diff --git a/maven-core/src/main/java/org/apache/maven/caching/ArtifactsRepository.java b/maven-core/src/main/java/org/apache/maven/caching/ArtifactsRepository.java new file mode 100644 index 000000000000..65315498c7ff --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/ArtifactsRepository.java @@ -0,0 +1,42 @@ +package org.apache.maven.caching; + +/* + * 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.maven.artifact.Artifact; +import org.apache.maven.caching.jaxb.CacheReportType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.execution.MavenSession; + +import java.io.IOException; + +/** + * ArtifactsRepository + */ +public interface ArtifactsRepository +{ + + BuildInfo findBuild( CacheContext context ) throws IOException; + + void saveBuildInfo( CacheResult cacheResult, BuildInfo buildInfo ) throws IOException; + + void saveArtifactFile( CacheResult cacheResult, Artifact artifact ) throws IOException; + + void saveCacheReport( String buildId, MavenSession session, CacheReportType cacheReport ) throws IOException; +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheContext.java b/maven-core/src/main/java/org/apache/maven/caching/CacheContext.java new file mode 100644 index 000000000000..569286d8b841 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/CacheContext.java @@ -0,0 +1,58 @@ +package org.apache.maven.caching; + +/* + * 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.maven.caching.jaxb.ProjectsInputInfoType; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * CacheContext + */ +public class CacheContext +{ + private final MavenProject project; + private final ProjectsInputInfoType inputInfo; + private final MavenSession session; + + public CacheContext( MavenProject project, ProjectsInputInfoType inputInfo, MavenSession session ) + { + this.project = checkNotNull( project ); + this.inputInfo = checkNotNull( inputInfo ); + this.session = checkNotNull( session ); + } + + public MavenProject getProject() + { + return project; + } + + public ProjectsInputInfoType getInputInfo() + { + return inputInfo; + } + + public MavenSession getSession() + { + return session; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheController.java b/maven-core/src/main/java/org/apache/maven/caching/CacheController.java new file mode 100644 index 000000000000..a91c284e2414 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/CacheController.java @@ -0,0 +1,52 @@ +package org.apache.maven.caching; + +/* + * 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.maven.execution.MavenSession; +import org.apache.maven.execution.MojoExecutionEvent; +import org.apache.maven.lifecycle.internal.ProjectIndex; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; + +import java.util.List; +import java.util.Map; + +/** + * CacheController + */ +public interface CacheController +{ + + CacheResult findCachedBuild( MavenSession session, + MavenProject project, + ProjectIndex projectIndex, + List mojoExecutions ); + + boolean restoreProjectArtifacts( CacheResult cacheResult ); + + void save( CacheResult cacheResult, + List mojoExecutions, + Map executionEvents ); + + boolean isForcedExecution( MavenProject project, MojoExecution execution ); + + void saveCacheReport( MavenSession session ); + +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheControllerImpl.java b/maven-core/src/main/java/org/apache/maven/caching/CacheControllerImpl.java new file mode 100644 index 000000000000..ad9d490c6ebb --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/CacheControllerImpl.java @@ -0,0 +1,942 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.mutable.MutableBoolean; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.handler.ArtifactHandler; +import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; +import org.apache.maven.caching.checksum.KeyUtils; +import org.apache.maven.caching.checksum.MavenProjectInput; +import org.apache.maven.caching.hash.HashAlgorithm; +import org.apache.maven.caching.hash.HashFactory; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.jaxb.BuildDiffType; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.caching.jaxb.CacheReportType; +import org.apache.maven.caching.jaxb.CompletedExecutionType; +import org.apache.maven.caching.jaxb.DigestItemType; +import org.apache.maven.caching.jaxb.ProjectReportType; +import org.apache.maven.caching.jaxb.ProjectsInputInfoType; +import org.apache.maven.caching.jaxb.PropertyNameType; +import org.apache.maven.caching.jaxb.TrackedPropertyType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheConfig; +import org.apache.maven.caching.xml.CacheSource; +import org.apache.maven.caching.xml.DtoUtils; +import org.apache.maven.caching.xml.XmlService; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.execution.MojoExecutionEvent; +import org.apache.maven.lifecycle.internal.ProjectIndex; +import org.apache.maven.plugin.MavenPluginManager; +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.descriptor.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; +import org.apache.maven.repository.RepositorySystem; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.util.ReflectionUtils; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.replace; +import static org.apache.commons.lang3.StringUtils.split; +import static org.apache.maven.caching.CacheResult.empty; +import static org.apache.maven.caching.CacheResult.failure; +import static org.apache.maven.caching.CacheResult.partialSuccess; +import static org.apache.maven.caching.CacheResult.rebuilded; +import static org.apache.maven.caching.CacheResult.success; +import static org.apache.maven.caching.HttpRepositoryImpl.BUILDINFO_XML; +import static org.apache.maven.caching.checksum.KeyUtils.getVersionlessProjectKey; +import static org.apache.maven.caching.checksum.MavenProjectInput.CACHE_IMPLMENTATION_VERSION; + +/** + * CacheControllerImpl + */ +@Component( role = CacheController.class ) +public class CacheControllerImpl implements CacheController +{ + + public static final String FILE_SEPARATOR_SUBST = "_"; + private static final String GENERATEDSOURCES = "generatedsources"; + private static final String GENERATEDSOURCES_PREFIX = GENERATEDSOURCES + FILE_SEPARATOR_SUBST; + @Requirement + private Logger logger; + + @Requirement + private MavenPluginManager mavenPluginManager; + + @Requirement + private MavenProjectHelper projectHelper; + + @Requirement + private LocalArtifactsRepository localCache; + + @Requirement + private RemoteArtifactsRepository remoteCache; + + @Requirement + private CacheConfig cacheConfig; + + @Requirement + private RepositorySystem repoSystem; + + @Requirement + private ArtifactHandlerManager artifactHandlerManager; + + @Requirement + private XmlService xmlService; + + private final ConcurrentMap artifactDigestByKey = new ConcurrentHashMap<>(); + + private final ConcurrentMap cacheResults = new ConcurrentHashMap<>(); + + private volatile BuildInfoType.Scm scm; + + @Override + @Nonnull + public CacheResult findCachedBuild( MavenSession session, MavenProject project, ProjectIndex projectIndex, + List mojoExecutions ) + { + + final String highestRequestPhase = Iterables.getLast( mojoExecutions ).getLifecyclePhase(); + if ( !ProjectUtils.isLaterPhase( highestRequestPhase, "post-clean" ) ) + { + return empty(); + } + + logInfo( project, "Attempting to restore project from build cache" ); + + ProjectsInputInfoType inputInfo = calculateInput( project, session, projectIndex ); + + final CacheContext context = new CacheContext( project, inputInfo, session ); + // remote build first + CacheResult result = findCachedBuild( mojoExecutions, context ); + + if ( !result.isSuccess() && result.getContext() != null ) + { + + logDebug( project, "Remote cache is incomplete or missing, trying local build" ); + + CacheResult localBuild = findLocalBuild( mojoExecutions, context ); + + if ( localBuild.isSuccess() || ( localBuild.isPartialSuccess() && !result.isPartialSuccess() ) ) + { + result = localBuild; + } + } + cacheResults.put( getVersionlessProjectKey( project ), result ); + + return result; + } + + private CacheResult findCachedBuild( List mojoExecutions, CacheContext context ) + { + BuildInfo cachedBuild = null; + try + { + cachedBuild = localCache.findBuild( context ); + return analyzeResult( context, mojoExecutions, cachedBuild ); + } + catch ( Exception e ) + { + logError( context.getProject(), "Cannot read cached build", e ); + return cachedBuild != null ? failure( cachedBuild, context ) : failure( context ); + } + } + + private CacheResult findLocalBuild( List mojoExecutions, CacheContext context ) + { + BuildInfo localBuild = null; + try + { + localBuild = localCache.findLocalBuild( context ); + return analyzeResult( context, mojoExecutions, localBuild ); + } + catch ( Exception e ) + { + logError( context.getProject(), "Cannot read local build", e ); + return localBuild != null ? failure( localBuild, context ) : failure( context ); + } + } + + private CacheResult analyzeResult( CacheContext context, List mojoExecutions, BuildInfo info ) + { + + try + { + if ( info != null ) + { + + final MavenProject project = context.getProject(); + final ProjectsInputInfoType inputInfo = context.getInputInfo(); + + logInfo( project, "Found cached build, restoring from cache " + inputInfo.getChecksum() ); + + if ( logger.isDebugEnabled() ) + { + logDebug( project, "Cached build details: " + info.toString() ); + } + + final String cacheImplementationVersion = info.getCacheImplementationVersion(); + if ( !CACHE_IMPLMENTATION_VERSION.equals( cacheImplementationVersion ) ) + { + logger.warn( + "Maven and cached build implementations mismatch, caching might not work correctly. " + + "Implementation version: " + CACHE_IMPLMENTATION_VERSION + ", cached build: " + + info.getCacheImplementationVersion() ); + } + + final List cachedSegment = info.getCachedSegment( mojoExecutions ); + + if ( !info.isAllExecutionsPresent( cachedSegment, logger ) ) + { + logInfo( project, "Cached build doesn't contains all requested plugin executions, cannot restore" ); + return failure( info, context ); + } + + if ( !isCachedSegmentPropertiesPresent( project, info, cachedSegment ) ) + { + logInfo( project, "Cached build violates cache rules, cannot restore" ); + return failure( info, context ); + } + + final String highestRequestPhase = Iterables.getLast( mojoExecutions ).getLifecyclePhase(); + final String highestCompletedGoal = info.getHighestCompletedGoal(); + if ( ProjectUtils.isLaterPhase( highestRequestPhase, highestCompletedGoal ) && !canIgnoreMissingSegment( + info, mojoExecutions ) ) + { + logInfo( project, + "Project restored partially. Highest cached goal: " + highestCompletedGoal + ", requested: " + + highestRequestPhase ); + return partialSuccess( info, context ); + } + + return success( info, context ); + } + else + { + logInfo( context.getProject(), "Project is not found in cache" ); + return empty( context ); + } + } + catch ( Exception e ) + { + logger.error( "Failed to restore project", e ); + localCache.clearCache( context ); + return empty( context ); + } + } + + private boolean canIgnoreMissingSegment( BuildInfo info, List mojoExecutions ) + { + final List postCachedSegment = info.getPostCachedSegment( mojoExecutions ); + for ( MojoExecution mojoExecution : postCachedSegment ) + { + if ( !cacheConfig.canIgnore( mojoExecution ) ) + { + return false; + } + } + return true; + } + + @Override + public boolean restoreProjectArtifacts( CacheResult cacheResult ) + { + + final BuildInfo buildInfo = cacheResult.getBuildInfo(); + final CacheContext context = cacheResult.getContext(); + final MavenProject project = context.getProject(); + + final ArtifactType artifact = buildInfo.getArtifact(); + artifact.setVersion( project.getVersion() ); + + try + { + if ( isNotBlank( artifact.getFileName() ) ) + { + // TODO if remote is forced, probably need to refresh or reconcile all files + final Path artifactFile = localCache.getArtifactFile( context, cacheResult.getSource(), artifact ); + if ( !Files.exists( artifactFile ) ) + { + logInfo( project, "Missing file for cached build, cannot restore. File: " + artifactFile ); + return false; + } + logDebug( project, "Setting project artifact " + artifact.getArtifactId() + " from: " + artifactFile ); + project.getArtifact().setFile( artifactFile.toFile() ); + project.getArtifact().setResolved( true ); + putChecksum( artifact, context.getInputInfo().getChecksum() ); + } + + for ( ArtifactType attachedArtifact : buildInfo.getAttachedArtifacts() ) + { + attachedArtifact.setVersion( project.getVersion() ); + if ( isNotBlank( attachedArtifact.getFileName() ) ) + { + final Path attachedArtifactFile = localCache.getArtifactFile( context, cacheResult.getSource(), + attachedArtifact ); + if ( !Files.exists( attachedArtifactFile ) ) + { + logInfo( project, + "Missing file for cached build, cannot restore project. File: " + + attachedArtifactFile ); + project.getArtifact().setFile( null ); + project.getArtifact().setResolved( false ); + project.getAttachedArtifacts().clear(); + return false; + } + logDebug( project, + "Attaching artifact " + artifact.getArtifactId() + " from: " + attachedArtifactFile ); + if ( StringUtils.startsWith( attachedArtifact.getClassifier(), GENERATEDSOURCES_PREFIX ) ) + { + // generated sources artifact + restoreGeneratedSources( attachedArtifact, attachedArtifactFile, project ); + } + else + { + projectHelper.attachArtifact( project, attachedArtifact.getType(), + attachedArtifact.getClassifier(), attachedArtifactFile.toFile() ); + } + putChecksum( attachedArtifact, context.getInputInfo().getChecksum() ); + } + } + } + catch ( Exception e ) + { + project.getArtifact().setFile( null ); + project.getArtifact().setResolved( false ); + project.getAttachedArtifacts().clear(); + logError( project, "Cannot restore cache, continuing with normal build.", e ); + return false; + } + + return true; + } + + private void putChecksum( ArtifactType artifact, String projectChecksum ) + { + + final DigestItemType projectArtifact = DtoUtils.createdDigestedByProjectChecksum( artifact, projectChecksum ); + final String dependencyKey = KeyUtils.getArtifactKey( artifact ); + artifactDigestByKey.put( dependencyKey, projectArtifact ); + + if ( !"pom".equals( artifact.getType() ) ) + { + final ArtifactHandler artifactHandler = artifactHandlerManager.getArtifactHandler( artifact.getType() ); + ArtifactType copy = DtoUtils.copy( artifact ); + copy.setType( artifactHandler.getPackaging() ); + artifactDigestByKey.put( KeyUtils.getArtifactKey( copy ), projectArtifact ); + copy.setType( artifactHandler.getExtension() ); + artifactDigestByKey.put( KeyUtils.getArtifactKey( copy ), projectArtifact ); + } + } + + private ProjectsInputInfoType calculateInput( MavenProject project, MavenSession session, + ProjectIndex projectIndex ) + { + try + { + final MavenProjectInput inputs = new MavenProjectInput( project, session, cacheConfig, projectIndex, + artifactDigestByKey, repoSystem, artifactHandlerManager, logger, localCache, remoteCache ); + return inputs.calculateChecksum( cacheConfig.getHashFactory() ); + } + catch ( Exception e ) + { + throw new RuntimeException( "Failed to calculate checksums for " + project.getArtifactId(), e ); + } + } + + @Override + public void save( CacheResult cacheResult, List mojoExecutions, + Map executionEvents ) + { + + CacheContext context = cacheResult.getContext(); + + if ( context == null || context.getInputInfo() == null ) + { + logger.info( "Cannot save project in cache, skipping" ); + return; + } + + final MavenProject project = context.getProject(); + final MavenSession session = context.getSession(); + try + { + + attachGeneratedSources( project ); + attachOutputs( project ); + + final Artifact projectArtifact = project.getArtifact(); + final HashFactory hashFactory = cacheConfig.getHashFactory(); + final HashAlgorithm algorithm = hashFactory.createAlgorithm(); + final ArtifactType projectArtifactDto = artifactDto( project.getArtifact(), algorithm ); + + final List attachedArtifacts = + project.getAttachedArtifacts() != null ? project.getAttachedArtifacts() : Collections.emptyList(); + List attachedArtifactDtos = artifactDtos( attachedArtifacts, algorithm ); + + List completedExecution = buildExecutionInfo( mojoExecutions, executionEvents ); + + final BuildInfo buildInfo = new BuildInfo( session.getGoals(), projectArtifactDto, attachedArtifactDtos, + context.getInputInfo(), completedExecution, hashFactory.getAlgorithm() ); + populateGitInfo( buildInfo, session ); + buildInfo.getDto().setFinal( cacheConfig.isSaveFinal() ); + cacheResults.put( getVersionlessProjectKey( project ), rebuilded( cacheResult, buildInfo ) ); + + // if package phase presence means new artifacts were packaged + if ( project.hasLifecyclePhase( "package" ) ) + { + localCache.beforeSave( context ); + localCache.saveBuildInfo( cacheResult, buildInfo ); + if ( projectArtifact.getFile() != null ) + { + localCache.saveArtifactFile( cacheResult, projectArtifact ); + putChecksum( projectArtifactDto, context.getInputInfo().getChecksum() ); + } + for ( Artifact attachedArtifact : attachedArtifacts ) + { + if ( attachedArtifact.getFile() != null && isOutputArtifact( + attachedArtifact.getFile().getName() ) ) + { + localCache.saveArtifactFile( cacheResult, attachedArtifact ); + } + } + for ( ArtifactType attachedArtifactDto : attachedArtifactDtos ) + { + putChecksum( attachedArtifactDto, context.getInputInfo().getChecksum() ); + } + } + else + { + localCache.saveBuildInfo( cacheResult, buildInfo ); + } + + if ( cacheConfig.isBaselineDiffEnabled() ) + { + produceDiffReport( cacheResult, buildInfo ); + } + + } + catch ( Exception e ) + { + try + { + logger.error( "Failed to save project, cleaning cache. Project: " + project, e ); + localCache.clearCache( context ); + } + catch ( Exception ex ) + { + logger.error( "Failed to clean cache due to unexpected error:", e ); + } + } + } + + public void produceDiffReport( CacheResult cacheResult, BuildInfo buildInfo ) + { + MavenProject project = cacheResult.getContext().getProject(); + Optional baselineHolder = remoteCache.findBaselineBuild( project ); + if ( baselineHolder.isPresent() ) + { + BuildInfo baseline = baselineHolder.get(); + String outputDirectory = project.getBuild().getDirectory(); + Path reportOutputDir = Paths.get( outputDirectory, "incremental-maven" ); + logInfo( project, "Saving cache builds diff to: " + reportOutputDir ); + BuildDiffType diff = new CacheDiff( buildInfo.getDto(), baseline.getDto(), cacheConfig ).compare(); + try + { + Files.createDirectories( reportOutputDir ); + final ProjectsInputInfoType baselineInputs = baseline.getDto().getProjectsInputInfo(); + final String checksum = baselineInputs.getChecksum(); + Files.write( reportOutputDir.resolve( "buildinfo-baseline-" + checksum + ".xml" ), + xmlService.toBytes( baseline.getDto() ), TRUNCATE_EXISTING, CREATE ); + Files.write( reportOutputDir.resolve( "buildinfo-" + checksum + ".xml" ), + xmlService.toBytes( buildInfo.getDto() ), TRUNCATE_EXISTING, CREATE ); + Files.write( reportOutputDir.resolve( "buildsdiff-" + checksum + ".xml" ), + xmlService.toBytes( diff ), TRUNCATE_EXISTING, CREATE ); + final Optional pom = CacheDiff.findPom( buildInfo.getDto().getProjectsInputInfo() ); + if ( pom.isPresent() ) + { + Files.write( reportOutputDir.resolve( "effective-pom-" + checksum + ".xml" ), + pom.get().getValue().getBytes( StandardCharsets.UTF_8 ), + TRUNCATE_EXISTING, CREATE ); + } + final Optional baselinePom = CacheDiff.findPom( baselineInputs ); + if ( baselinePom.isPresent() ) + { + Files.write( reportOutputDir.resolve( + "effective-pom-baseline-" + baselineInputs.getChecksum() + ".xml" ), + baselinePom.get().getValue().getBytes( StandardCharsets.UTF_8 ), + TRUNCATE_EXISTING, CREATE ); + } + } + catch ( IOException e ) + { + logError( project, "Cannot produce build diff for project", e ); + } + } + else + { + logInfo( project, "Cannot find project in baseline build, skipping diff" ); + } + } + + private List artifactDtos( List attachedArtifacts, HashAlgorithm digest ) throws IOException + { + List result = new ArrayList<>(); + for ( Artifact attachedArtifact : attachedArtifacts ) + { + if ( attachedArtifact.getFile() != null && isOutputArtifact( attachedArtifact.getFile().getName() ) ) + { + result.add( artifactDto( attachedArtifact, digest ) ); + } + } + return result; + } + + private ArtifactType artifactDto( Artifact projectArtifact, HashAlgorithm algorithm ) throws IOException + { + final ArtifactType dto = DtoUtils.createDto( projectArtifact ); + if ( projectArtifact.getFile() != null && projectArtifact.getFile().isFile() ) + { + final Path file = projectArtifact.getFile().toPath(); + dto.setFileHash( algorithm.hash( file ) ); + dto.setFileSize( BigInteger.valueOf( Files.size( file ) ) ); + } + return dto; + } + + private List buildExecutionInfo( List mojoExecutions, + Map executionEvents ) + { + List list = new ArrayList<>(); + for ( MojoExecution mojoExecution : mojoExecutions ) + { + final String executionKey = ProjectUtils.mojoExecutionKey( mojoExecution ); + final MojoExecutionEvent executionEvent = executionEvents.get( executionKey ); + CompletedExecutionType executionInfo = new CompletedExecutionType(); + executionInfo.setExecutionKey( executionKey ); + executionInfo.setMojoClassName( mojoExecution.getMojoDescriptor().getImplementation() ); + if ( executionEvent != null ) + { + recordMojoProperties( executionInfo, executionEvent ); + } + list.add( executionInfo ); + } + return list; + } + + private void recordMojoProperties( CompletedExecutionType execution, MojoExecutionEvent executionEvent ) + { + final MojoExecution mojoExecution = executionEvent.getExecution(); + + final boolean logAll = cacheConfig.isLogAllProperties( mojoExecution ); + List trackedProperties = cacheConfig.getTrackedProperties( mojoExecution ); + List noLogProperties = cacheConfig.getNologProperties( mojoExecution ); + List forceLogProperties = cacheConfig.getLoggedProperties( mojoExecution ); + final Mojo mojo = executionEvent.getMojo(); + + final File baseDir = executionEvent.getProject().getBasedir(); + final String baseDirPath = FilenameUtils.normalizeNoEndSeparator( baseDir.getAbsolutePath() ) + File.separator; + + final List parameters = mojoExecution.getMojoDescriptor().getParameters(); + for ( Parameter parameter : parameters ) + { + // editable parameters could be configured by user + if ( !parameter.isEditable() ) + { + continue; + } + + final String propertyName = parameter.getName(); + final boolean tracked = isTracked( propertyName, trackedProperties ); + if ( !tracked && isExcluded( propertyName, logAll, noLogProperties, forceLogProperties ) ) + { + continue; + } + + try + { + final Object value = ReflectionUtils.getValueIncludingSuperclasses( propertyName, mojo ); + DtoUtils.addProperty( execution, propertyName, value, baseDirPath, tracked ); + } + catch ( IllegalAccessException e ) + { + logInfo( executionEvent.getProject(), + "Cannot get property " + propertyName + " value from " + mojo + ": " + e.getMessage() ); + if ( tracked ) + { + throw new IllegalArgumentException( + "Property configured in cache introspection config for " + mojo + " is not accessible: " + + propertyName ); + } + } + } + } + + private boolean isExcluded( String propertyName, boolean logAll, List excludedProperties, + List forceLogProperties ) + { + + if ( !forceLogProperties.isEmpty() ) + { + for ( PropertyNameType logProperty : forceLogProperties ) + { + if ( StringUtils.equals( propertyName, logProperty.getPropertyName() ) ) + { + return false; + } + } + return true; + } + + if ( !excludedProperties.isEmpty() ) + { + for ( PropertyNameType excludedProperty : excludedProperties ) + { + if ( StringUtils.equals( propertyName, excludedProperty.getPropertyName() ) ) + { + return true; + } + } + return false; + } + + return !logAll; + } + + private boolean isTracked( String propertyName, List trackedProperties ) + { + for ( TrackedPropertyType trackedProperty : trackedProperties ) + { + if ( StringUtils.equals( propertyName, trackedProperty.getPropertyName() ) ) + { + return true; + } + } + return false; + } + + private boolean isCachedSegmentPropertiesPresent( MavenProject project, BuildInfo buildInfo, + List mojoExecutions ) + { + for ( MojoExecution mojoExecution : mojoExecutions ) + { + + // completion of all mojos checked above, so we expect tp have execution info here + final List trackedProperties = cacheConfig.getTrackedProperties( mojoExecution ); + final CompletedExecutionType cachedExecution = buildInfo.findMojoExecutionInfo( mojoExecution ); + + if ( cachedExecution == null ) + { + logInfo( project, + "Execution is not cached. Plugin: " + mojoExecution.getExecutionId() + ", goal" + + mojoExecution.getGoal() ); + return false; + } + + if ( !DtoUtils.containsAllProperties( cachedExecution, trackedProperties ) ) + { + logInfo( project, "Build info doesn't match rules. Plugin: " + mojoExecution.getExecutionId() ); + return false; + } + } + return true; + } + + private void logDebug( MavenProject project, String message ) + { + if ( logger.isDebugEnabled() ) + { + logger.debug( "[CACHE][" + project.getArtifactId() + "] " + message ); + } + } + + private void logInfo( MavenProject project, String message ) + { + logger.info( "[CACHE][" + project.getArtifactId() + "] " + message ); + } + + private void logError( MavenProject project, String message, Exception e ) + { + logger.error( "[CACHE][" + project.getArtifactId() + "] " + message, e ); + } + + @Override + public boolean isForcedExecution( MavenProject project, MojoExecution execution ) + { + + if ( cacheConfig.isForcedExecution( execution ) ) + { + return true; + } + final Properties properties = project.getProperties(); + final String alwaysRunPlugins = properties.getProperty( "remote.cache.alwaysRunPlugins" ); + ArrayList alwaysRunPluginsList = new ArrayList<>(); + if ( alwaysRunPlugins != null ) + { + alwaysRunPluginsList = Lists.newArrayList( split( alwaysRunPlugins, "," ) ); + } + for ( String pluginAndGoal : alwaysRunPluginsList ) + { + final String[] tokens = pluginAndGoal.split( ":" ); + final String alwaysRunPlugin = tokens[0]; + String alwaysRunGoal = tokens.length == 1 ? "*" : tokens[1]; + if ( StringUtils.equals( execution.getPlugin().getArtifactId(), alwaysRunPlugin ) && ( "*".equals( + alwaysRunGoal ) || StringUtils.equals( execution.getGoal(), alwaysRunGoal ) ) ) + { + return true; + } + } + return false; + } + + @Override + public void saveCacheReport( MavenSession session ) + { + try + { + + CacheReportType cacheReport = new CacheReportType(); + for ( CacheResult result : cacheResults.values() ) + { + ProjectReportType projectReport = new ProjectReportType(); + CacheContext context = result.getContext(); + MavenProject project = context.getProject(); + projectReport.setGroupId( project.getGroupId() ); + projectReport.setArtifactId( project.getArtifactId() ); + projectReport.setChecksum( context.getInputInfo().getChecksum() ); + boolean checksumMatched = result.getStatus() != RestoreStatus.EMPTY; + projectReport.setChecksumMatched( checksumMatched ); + projectReport.setLifecycleMatched( checksumMatched && result.isSuccess() ); + projectReport.setSource( String.valueOf( result.getSource() ) ); + if ( result.getSource() == CacheSource.REMOTE ) + { + projectReport.setUrl( remoteCache.getResourceUrl( context, BUILDINFO_XML ) ); + } + else if ( result.getSource() == CacheSource.BUILD && cacheConfig.isSaveToRemote() ) + { + projectReport.setSharedToRemote( true ); + projectReport.setUrl( remoteCache.getResourceUrl( context, BUILDINFO_XML ) ); + } + cacheReport.getProject().add( projectReport ); + } + + String buildId = UUID.randomUUID().toString(); + localCache.saveCacheReport( buildId, session, cacheReport ); + } + catch ( Exception e ) + { + logger.error( "Cannot save incremental build aggregated report", e ); + } + } + + private void populateGitInfo( BuildInfo buildInfo, MavenSession session ) throws IOException + { + if ( scm == null ) + { + synchronized ( this ) + { + if ( scm == null ) + { + try + { + scm = ProjectUtils.readGitInfo( session ); + } + catch ( IOException e ) + { + scm = new BuildInfoType.Scm(); + logger.error( "Cannot populate git info", e ); + } + } + } + } + buildInfo.getDto().setScm( scm ); + } + + private void zipAndAttachArtifact( MavenProject project, Path dir, String classifier ) throws IOException + { + + try ( final InputStream inputStream = ZipUtils.zipFolder( dir ) ) + { + File tempFile = File.createTempFile( "maven-incremental", project.getArtifactId() ); + tempFile.deleteOnExit(); + Files.copy( inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING ); + projectHelper.attachArtifact( project, "zip", classifier, tempFile ); + } + } + + private String pathToClassifier( Path relative ) + { + final int nameCount = relative.getNameCount(); + List segments = new ArrayList<>( nameCount + 1 ); + for ( int i = 0; i < nameCount; i++ ) + { + segments.add( relative.getName( i ).toFile().getName() ); + } + // todo handle _ in file names + return GENERATEDSOURCES_PREFIX + StringUtils.join( segments.iterator(), FILE_SEPARATOR_SUBST ); + } + + private Path classifierToPath( Path outputDir, String classifier ) + { + classifier = StringUtils.removeStart( classifier, GENERATEDSOURCES_PREFIX ); + final String relPath = replace( classifier, FILE_SEPARATOR_SUBST, File.separator ); + return outputDir.resolve( relPath ); + } + + private void restoreGeneratedSources( ArtifactType artifact, Path artifactFilePath, MavenProject project ) + throws IOException + { + final Path targetDir = Paths.get( project.getBuild().getDirectory() ); + final Path outputDir = classifierToPath( targetDir, artifact.getClassifier() ); + try ( InputStream is = Files.newInputStream( artifactFilePath ) ) + { + if ( Files.exists( outputDir ) ) + { + FileUtils.cleanDirectory( outputDir.toFile() ); + } + else + { + Files.createDirectories( outputDir ); + } + ZipUtils.unzip( is, outputDir ); + } + } + + //TODO: move to config + public void attachGeneratedSources( MavenProject project ) throws IOException + { + final Path targetDir = Paths.get( project.getBuild().getDirectory() ); + + final Path generatedSourcesDir = targetDir.resolve( "generated-sources" ); + attachDirIfNotEmpty( generatedSourcesDir, targetDir, project ); + + final Path generatedTestSourcesDir = targetDir.resolve( "generated-test-sources" ); + attachDirIfNotEmpty( generatedTestSourcesDir, targetDir, project ); + + Set sourceRoots = new TreeSet<>(); + if ( project.getCompileSourceRoots() != null ) + { + sourceRoots.addAll( project.getCompileSourceRoots() ); + } + if ( project.getTestCompileSourceRoots() != null ) + { + sourceRoots.addAll( project.getTestCompileSourceRoots() ); + } + + for ( String sourceRoot : sourceRoots ) + { + final Path sourceRootPath = Paths.get( sourceRoot ); + if ( Files.isDirectory( sourceRootPath ) && sourceRootPath.startsWith( + targetDir ) && !( sourceRootPath.startsWith( generatedSourcesDir ) || sourceRootPath.startsWith( + generatedTestSourcesDir ) ) ) + { // dir within target + attachDirIfNotEmpty( sourceRootPath, targetDir, project ); + } + } + } + + private void attachOutputs( MavenProject project ) throws IOException + { + final List attachedDirs = cacheConfig.getAttachedOutputs(); + for ( String dir : attachedDirs ) + { + final Path targetDir = Paths.get( project.getBuild().getDirectory() ); + final Path outputDir = targetDir.resolve( dir ); + attachDirIfNotEmpty( outputDir, targetDir, project ); + } + } + + private void attachDirIfNotEmpty( Path candidateSubDir, Path parentDir, MavenProject project ) throws IOException + { + if ( Files.isDirectory( candidateSubDir ) && hasFiles( candidateSubDir ) ) + { + final Path relativePath = parentDir.relativize( candidateSubDir ); + final String classifier = pathToClassifier( relativePath ); + zipAndAttachArtifact( project, candidateSubDir, classifier ); + logDebug( project, "Attached directory: " + candidateSubDir ); + } + } + + private boolean hasFiles( Path candidateSubDir ) throws IOException + { + final MutableBoolean hasFiles = new MutableBoolean(); + Files.walkFileTree( candidateSubDir, new SimpleFileVisitor() + { + @Override + public FileVisitResult visitFile( Path path, BasicFileAttributes basicFileAttributes ) + { + hasFiles.setTrue(); + return FileVisitResult.TERMINATE; + } + } ); + return hasFiles.booleanValue(); + } + + private boolean isOutputArtifact( String name ) + { + List excludePatterns = cacheConfig.getExcludePatterns(); + for ( Pattern pattern : excludePatterns ) + { + if ( pattern.matcher( name ).matches() ) + { + return false; + } + } + return true; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheDiff.java b/maven-core/src/main/java/org/apache/maven/caching/CacheDiff.java new file mode 100644 index 000000000000..9e066a598eff --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/CacheDiff.java @@ -0,0 +1,345 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.base.Optional; +import com.google.common.collect.Sets; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.caching.jaxb.BuildDiffType; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.caching.jaxb.CompletedExecutionType; +import org.apache.maven.caching.jaxb.DigestItemType; +import org.apache.maven.caching.jaxb.MismatchType; +import org.apache.maven.caching.jaxb.ProjectsInputInfoType; +import org.apache.maven.caching.jaxb.PropertyValueType; +import org.apache.maven.caching.xml.CacheConfig; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Utility class for comparing 2 builds + */ +public class CacheDiff +{ + + private final CacheConfig config; + private final BuildInfoType current; + private final BuildInfoType baseline; + private final LinkedList report; + + public CacheDiff( BuildInfoType current, BuildInfoType baseline, CacheConfig config ) + { + this.current = current; + this.baseline = baseline; + this.config = config; + this.report = new LinkedList<>(); + } + + public BuildDiffType compare() + { + + if ( !StringUtils.equals( current.getHashFunction(), baseline.getHashFunction() ) ) + { + addNewMismatch( + "hashFunction", + current.getHashFunction(), + baseline.getHashFunction(), + "Different algorithms render caches not comparable and cached could not be reused", + "Ensure the same algorithm as remote" + ); + } + compareEffectivePoms( current.getProjectsInputInfo(), baseline.getProjectsInputInfo() ); + compareExecutions( current.getExecutions(), baseline.getExecutions() ); + compareFiles( current.getProjectsInputInfo(), baseline.getProjectsInputInfo() ); + compareDependencies( current.getProjectsInputInfo(), baseline.getProjectsInputInfo() ); + + final BuildDiffType buildDiffType = new BuildDiffType(); + buildDiffType.getMismatch().addAll( report ); + return buildDiffType; + } + + private void compareEffectivePoms( ProjectsInputInfoType current, ProjectsInputInfoType baseline ) + { + Optional currentPom = findPom( current ); + String currentPomHash = currentPom.isPresent() ? currentPom.get().getHash() : null; + + Optional baseLinePom = findPom( baseline ); + String baselinePomHash = baseLinePom.isPresent() ? baseLinePom.get().getHash() : null; + + if ( !StringUtils.equals( currentPomHash, baselinePomHash ) ) + { + addNewMismatch( + "effectivePom", currentPomHash, baselinePomHash, + "Difference in effective pom suggests effectively different builds which cannot be reused", + "Compare raw content of effective poms and eliminate differences. " + + "See How-To for common techniques" + ); + } + } + + public static Optional findPom( ProjectsInputInfoType projectInputs ) + { + for ( DigestItemType digestItemType : projectInputs.getItem() ) + { + if ( "pom".equals( digestItemType.getType() ) ) + { + return Optional.of( digestItemType ); + + } + } + return Optional.absent(); + } + + private void compareFiles( ProjectsInputInfoType current, ProjectsInputInfoType baseline ) + { + + final Map currentFiles = new HashMap<>(); + for ( DigestItemType item : current.getItem() ) + { + if ( "file".equals( item.getType() ) ) + { + currentFiles.put( item.getValue(), item ); + } + } + + final Map baselineFiles = new HashMap<>(); + for ( DigestItemType item : baseline.getItem() ) + { + if ( "file".equals( item.getType() ) ) + { + baselineFiles.put( item.getValue(), item ); + } + } + + final Sets.SetView currentVsBaseline = Sets.difference( currentFiles.keySet(), baselineFiles.keySet() ); + final Sets.SetView baselineVsCurrent = Sets.difference( baselineFiles.keySet(), currentFiles.keySet() ); + + if ( !currentVsBaseline.isEmpty() || !baselineVsCurrent.isEmpty() ) + { + addNewMismatch( "source files", + "Remote and local cache contain different sets of input files. " + + "Added files: " + currentVsBaseline + ". Removed files: " + baselineVsCurrent, + "To match remote and local caches should have identical file sets." + + " Unnecessary and transient files must be filtered out to make file sets match" + + " - see configuration guide" + ); + return; + } + + for ( Map.Entry entry : currentFiles.entrySet() ) + { + String filePath = entry.getKey(); + DigestItemType currentFile = entry.getValue(); + // should be null safe because sets are compared above for differences + final DigestItemType baselineFile = baselineFiles.get( filePath ); + if ( !StringUtils.equals( currentFile.getHash(), baselineFile.getHash() ) ) + { + + String reason = "File content is different."; + if ( currentFile.getEol() != null && baselineFile.getEol() != null && !StringUtils.equals( + baselineFile.getEol(), currentFile.getEol() ) ) + { + reason += " Different line endings detected (text files relevant). " + + "Remote: " + baselineFile.getEol() + ", local: " + currentFile.getEol() + "."; + } + if ( currentFile.getCharset() != null && baselineFile.getCharset() != null && !StringUtils.equals( + baselineFile.getCharset(), currentFile.getCharset() ) ) + { + reason += " Different charset detected (text files relevant). " + + "Remote: " + baselineFile.getEol() + ", local: " + currentFile.getEol() + "."; + } + + addNewMismatch( filePath, currentFile.getHash(), baselineFile.getHash(), reason, + "Different content manifests different build outcome. " + + "Ensure that difference is not caused by environment specifics, like line separators" + ); + } + } + } + + private void compareDependencies( ProjectsInputInfoType current, ProjectsInputInfoType baseline ) + { + final Map currentDependencies = new HashMap<>(); + for ( DigestItemType digestItemType : current.getItem() ) + { + if ( "dependency".equals( digestItemType.getType() ) ) + { + currentDependencies.put( digestItemType.getValue(), digestItemType ); + } + } + final Map baselineDependencies = new HashMap<>(); + for ( DigestItemType item : baseline.getItem() ) + { + if ( "dependency".equals( item.getType() ) ) + { + baselineDependencies.put( item.getValue(), item ); + } + } + + final Sets.SetView currentVsBaseline = + Sets.difference( currentDependencies.keySet(), baselineDependencies.keySet() ); + final Sets.SetView baselineVsCurrent = + Sets.difference( baselineDependencies.keySet(), currentDependencies.keySet() ); + + if ( !currentVsBaseline.isEmpty() || !baselineVsCurrent.isEmpty() ) + { + addNewMismatch( "dependencies files", + "Remote and local builds contain different sets of dependencies and cannot be matched. " + + "Added dependencies: " + currentVsBaseline + ". Removed dependencies: " + + baselineVsCurrent, + "Remote and local builds should have identical dependencies. " + + "The difference manifests changes in downstream dependencies or introduced snapshots." + ); + return; + } + + for ( Map.Entry entry : currentDependencies.entrySet() ) + { + String dependencyKey = entry.getKey(); + DigestItemType currentDependency = entry.getValue(); + // null safe - sets compared for differences above + final DigestItemType baselineDependency = baselineDependencies.get( dependencyKey ); + if ( !StringUtils.equals( currentDependency.getHash(), baselineDependency.getHash() ) ) + { + addNewMismatch( dependencyKey, currentDependency.getHash(), baselineDependency.getHash(), + "Downstream project or snapshot changed", + "Find downstream project and investigate difference in the downstream project. " + + "Enable fail fast mode and single threaded execution to simplify debug." + ); + } + } + } + + + private void compareExecutions( BuildInfoType.Executions current, BuildInfoType.Executions baseline ) + { + Map baselineExecutionsByKey = new HashMap<>(); + for ( CompletedExecutionType completedExecutionType : baseline.getExecution() ) + { + baselineExecutionsByKey.put( completedExecutionType.getExecutionKey(), completedExecutionType ); + } + + Map currentExecutionsByKey = new HashMap<>(); + for ( CompletedExecutionType e1 : current.getExecution() ) + { + currentExecutionsByKey.put( e1.getExecutionKey(), e1 ); + } + + // such situation normally means different poms and mismatch in effective poms, + // but in any case it is helpful to report + for ( CompletedExecutionType baselineExecution : baseline.getExecution() ) + { + if ( !currentExecutionsByKey.containsKey( baselineExecution.getExecutionKey() ) ) + { + addNewMismatch( + baselineExecution.getExecutionKey(), + "Baseline build contains excessive plugin " + baselineExecution.getExecutionKey(), + "Different set of plugins produces different build results. " + + "Exclude non-critical plugins or make sure plugin sets match" + ); + } + } + + for ( CompletedExecutionType currentExecution : current.getExecution() ) + { + if ( !baselineExecutionsByKey.containsKey( currentExecution.getExecutionKey() ) ) + { + addNewMismatch( + currentExecution.getExecutionKey(), + "Cached build doesn't contain plugin " + currentExecution.getExecutionKey(), + "Different set of plugins produces different build results. " + + "Filter out non-critical plugins or make sure remote cache always run full build " + + "with all plugins" + ); + continue; + } + + final CompletedExecutionType baselineExecution = + baselineExecutionsByKey.get( currentExecution.getExecutionKey() ); + comparePlugins( currentExecution, baselineExecution ); + } + } + + private void comparePlugins( CompletedExecutionType current, CompletedExecutionType baseline ) + { + // TODO add support for skip values + final List trackedProperties = new ArrayList<>(); + for ( PropertyValueType propertyValueType : current.getConfiguration().getProperty() ) + { + if ( propertyValueType.isTracked() ) + { + trackedProperties.add( propertyValueType ); + } + } + if ( trackedProperties.isEmpty() ) + { + return; + } + + final Map baselinePropertiesByName = new HashMap<>(); + for ( PropertyValueType propertyValueType : baseline.getConfiguration().getProperty() ) + { + baselinePropertiesByName.put( propertyValueType.getName(), propertyValueType ); + } + + for ( PropertyValueType p : trackedProperties ) + { + final PropertyValueType baselineValue = baselinePropertiesByName.get( p.getName() ); + if ( baselineValue == null || !StringUtils.equals( baselineValue.getValue(), p.getValue() ) ) + { + addNewMismatch( + p.getName(), + p.getValue(), + baselineValue == null ? null : baselineValue.getValue(), + "Plugin: " + current.getExecutionKey() + + " has mismatch in tracked property and cannot be reused", + "Align properties between remote and local build or remove property from tracked " + + "list if mismatch could be tolerated. In some cases it is possible to add skip value " + + "to ignore lax mismatch" + ); + } + } + } + + private void addNewMismatch( String item, String current, String baseline, String reason, + String resolution ) + { + final MismatchType mismatch = new MismatchType(); + mismatch.setItem( item ); + mismatch.setCurrent( current ); + mismatch.setBaseline( baseline ); + mismatch.setReason( reason ); + mismatch.setResolution( resolution ); + report.add( mismatch ); + } + + private void addNewMismatch( String property, String reason, String resolution ) + { + final MismatchType mismatchType = new MismatchType(); + mismatchType.setItem( property ); + mismatchType.setReason( reason ); + mismatchType.setResolution( resolution ); + report.add( mismatchType ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheEventSpy.java b/maven-core/src/main/java/org/apache/maven/caching/CacheEventSpy.java new file mode 100644 index 000000000000..99f7e1a72542 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/CacheEventSpy.java @@ -0,0 +1,49 @@ +package org.apache.maven.caching; /* * 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. */ /* * 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. */ + +/* + * 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.maven.eventspy.AbstractEventSpy; +import org.apache.maven.eventspy.EventSpy; +import org.apache.maven.execution.ExecutionEvent; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; + +/** + * Triggers cache report generation on build completion + */ +@Component( role = EventSpy.class ) +public class CacheEventSpy extends AbstractEventSpy +{ + @Requirement + private CacheController cacheController; + + @Override + public void onEvent( Object event ) throws Exception + { + if ( event instanceof ExecutionEvent ) + { + ExecutionEvent executionEvent = (ExecutionEvent) event; + if ( executionEvent.getType() == ExecutionEvent.Type.SessionEnded ) + { + cacheController.saveCacheReport( executionEvent.getSession() ); + } + } + } +} \ No newline at end of file diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheResult.java b/maven-core/src/main/java/org/apache/maven/caching/CacheResult.java new file mode 100644 index 000000000000..8d886764f8b8 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/CacheResult.java @@ -0,0 +1,122 @@ +package org.apache.maven.caching; + +/* + * 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.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheSource; + +import static java.util.Objects.requireNonNull; + +/** + * CacheResult + */ +public class CacheResult +{ + private final RestoreStatus status; + private final BuildInfo buildInfo; + private final CacheContext context; + + private CacheResult( RestoreStatus status, BuildInfo buildInfo, CacheContext context ) + { + this.status = requireNonNull( status ); + this.buildInfo = buildInfo; + this.context = context; + } + + public static CacheResult empty( CacheContext context ) + { + requireNonNull( context ); + return new CacheResult( RestoreStatus.EMPTY, null, context ); + } + + public static CacheResult empty() + { + return new CacheResult( RestoreStatus.EMPTY, null, null ); + } + + public static CacheResult failure( BuildInfo buildInfo, CacheContext context ) + { + requireNonNull( buildInfo ); + requireNonNull( context ); + return new CacheResult( RestoreStatus.FAILURE, buildInfo, context ); + } + + public static CacheResult success( BuildInfo buildInfo, CacheContext context ) + { + requireNonNull( buildInfo ); + requireNonNull( context ); + return new CacheResult( RestoreStatus.SUCCESS, buildInfo, context ); + } + + public static CacheResult partialSuccess( BuildInfo buildInfo, CacheContext context ) + { + requireNonNull( buildInfo ); + requireNonNull( context ); + return new CacheResult( RestoreStatus.PARTIAL, buildInfo, context ); + } + + public static CacheResult failure( CacheContext context ) + { + requireNonNull( context ); + return new CacheResult( RestoreStatus.FAILURE, null, context ); + } + + public static CacheResult rebuilded( CacheResult orig, BuildInfo buildInfo ) + { + requireNonNull( orig ); + requireNonNull( buildInfo ); + return new CacheResult( orig.status, buildInfo, orig.context ); + } + + public boolean isSuccess() + { + return status == RestoreStatus.SUCCESS; + } + + public BuildInfo getBuildInfo() + { + return buildInfo; + } + + public CacheSource getSource() + { + return buildInfo != null ? buildInfo.getSource() : null; + } + + public CacheContext getContext() + { + return context; + } + + public boolean isPartialSuccess() + { + return status == RestoreStatus.PARTIAL; + } + + public RestoreStatus getStatus() + { + return status; + } + + public boolean isFinal() + { + return buildInfo != null && buildInfo.getDto().isFinal(); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/Clock.java b/maven-core/src/main/java/org/apache/maven/caching/Clock.java new file mode 100644 index 000000000000..ce0c487c669c --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/Clock.java @@ -0,0 +1,42 @@ +package org.apache.maven.caching; + +/* + * 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 static java.util.concurrent.TimeUnit.NANOSECONDS; + +/** + * Clock + */ +public class Clock +{ + public static long time() + { + return System.nanoTime(); + } + + public static long elapsed( long time ) + { + return NANOSECONDS.toMillis( time() - time ); + } + + private Clock() + { + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/DefaultPluginScanConfig.java b/maven-core/src/main/java/org/apache/maven/caching/DefaultPluginScanConfig.java new file mode 100644 index 000000000000..408d097cf10e --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/DefaultPluginScanConfig.java @@ -0,0 +1,63 @@ +package org.apache.maven.caching; + +/* + * 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.maven.caching.jaxb.DirScanConfigType; + +import javax.annotation.Nonnull; + +/** + * DefaultPluginScanConfig + */ +public class DefaultPluginScanConfig implements PluginScanConfig +{ + + @Override + public boolean isSkip() + { + return false; + } + + @Override + public boolean accept( String propertyName ) + { + return true; + } + + @Override + @Nonnull + public PluginScanConfig mergeWith( PluginScanConfig overrideSource ) + { + return overrideSource; + } + + @Nonnull + @Override + public ScanConfigProperties getTagScanProperties( String tagName ) + { + return new ScanConfigProperties( true, "*" ); + } + + @Override + public DirScanConfigType dto() + { + return null; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/HttpRepositoryImpl.java b/maven-core/src/main/java/org/apache/maven/caching/HttpRepositoryImpl.java new file mode 100644 index 000000000000..fbbe509bb28d --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/HttpRepositoryImpl.java @@ -0,0 +1,336 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.base.Optional; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.caching.checksum.MavenProjectInput; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.caching.jaxb.CacheReportType; +import org.apache.maven.caching.jaxb.ProjectReportType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheConfig; +import org.apache.maven.caching.xml.CacheSource; +import org.apache.maven.caching.xml.XmlService; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.LegacySupport; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * HttpRepositoryImpl + */ +@Component( role = RemoteArtifactsRepository.class ) +public class HttpRepositoryImpl implements RemoteArtifactsRepository +{ + + public static final String BUILDINFO_XML = "buildinfo.xml"; + public static final String CACHE_REPORT_XML = "cache-report.xml"; + + @Requirement + private Logger logger; + + @Requirement + LegacySupport legacySupport; + + @Requirement + XmlService xmlService; + + @Requirement + private CacheConfig cacheConfig; + + @SuppressWarnings( {"checkstyle:constantname", "checkstyle:magicnumber"} ) + private static final ThreadLocal httpClient = new ThreadLocal() + { + @Override + protected HttpClient initialValue() + { + int timeoutSeconds = 60; + RequestConfig config = RequestConfig.custom().setConnectTimeout( + timeoutSeconds * 1000 ).setConnectionRequestTimeout( timeoutSeconds * 1000 ).setSocketTimeout( + timeoutSeconds * 1000 ).build(); + return HttpClientBuilder.create().setDefaultRequestConfig( config ).build(); + } + }; + + @Override + public BuildInfo findBuild( CacheContext context ) + { + final String resourceUrl = getResourceUrl( context, BUILDINFO_XML ); + String artifactId = context.getProject().getArtifactId(); + if ( exists( artifactId, resourceUrl ) ) + { + final byte[] bytes = getResourceContent( resourceUrl, artifactId ); + final BuildInfoType dto = xmlService.fromBytes( BuildInfoType.class, bytes ); + return new BuildInfo( dto, CacheSource.REMOTE ); + } + return null; + } + + @Override + public byte[] getArtifactContent( CacheContext context, ArtifactType artifact ) + { + return getResourceContent( getResourceUrl( context, artifact.getFileName() ), + context.getProject().getArtifactId() ); + } + + @Override + public void saveBuildInfo( CacheResult cacheResult, BuildInfo buildInfo ) throws IOException + { + CacheContext context = cacheResult.getContext(); + final String resourceUrl = getResourceUrl( cacheResult.getContext(), BUILDINFO_XML ); + putToRemoteCache( new ByteArrayInputStream( xmlService.toBytes( buildInfo.getDto() ) ), resourceUrl, + context.getProject().getArtifactId() ); + } + + + @Override + public void saveCacheReport( String buildId, MavenSession session, CacheReportType cacheReport ) throws IOException + { + MavenProject rootProject = session.getTopLevelProject(); + final String resourceUrl = cacheConfig.getUrl() + "/" + MavenProjectInput.CACHE_IMPLMENTATION_VERSION + + "/" + rootProject.getGroupId() + + "/" + rootProject.getArtifactId() + + "/" + buildId + + "/" + CACHE_REPORT_XML; + putToRemoteCache( new ByteArrayInputStream( xmlService.toBytes( cacheReport ) ), resourceUrl, + rootProject.getArtifactId() ); + } + + @Override + public void saveArtifactFile( CacheResult cacheResult, Artifact artifact ) throws IOException + { + CacheContext context = cacheResult.getContext(); + final String resourceUrl = getResourceUrl( cacheResult.getContext(), ProjectUtils.normalizedName( artifact ) ); + try ( InputStream inputStream = Files.newInputStream( artifact.getFile().toPath() ) ) + { + putToRemoteCache( inputStream, resourceUrl, context.getProject().getArtifactId() ); + } + } + + @SuppressWarnings( "checkstyle:magicnumber" ) + private boolean exists( String logReference, String url ) + { + HttpHead head = null; + try + { + head = new HttpHead( url ); + HttpResponse response = httpClient.get().execute( head ); + int statusCode = response.getStatusLine().getStatusCode(); + logger.info( "[CACHE][" + logReference + "] Checking " + url + ". Status: " + statusCode ); + return statusCode == 200; + } + catch ( IOException e ) + { + throw new RuntimeException( "Cannot check " + url, e ); + } + finally + { + if ( head != null ) + { + head.releaseConnection(); + } + } + } + + @SuppressWarnings( "checkstyle:magicnumber" ) + public byte[] getResourceContent( String url, String logReference ) + { + HttpGet get = null; + try + { + get = new HttpGet( url ); + HttpResponse response = httpClient.get().execute( get ); + int statusCode = response.getStatusLine().getStatusCode(); + logger.info( "[CACHE][" + logReference + "] Downloading " + url + ". Status: " + statusCode ); + if ( statusCode != 200 ) + { + throw new RuntimeException( "Cannot download " + url + ", unexpected status code: " + statusCode ); + } + try ( InputStream content = response.getEntity().getContent() ) + { + return IOUtils.toByteArray( content ); + } + } + catch ( IOException e ) + { + throw new RuntimeException( "Cannot get " + url, e ); + } + finally + { + if ( get != null ) + { + get.releaseConnection(); + } + } + } + + @Override + public String getResourceUrl( CacheContext context, String filename ) + { + return getResourceUrl( filename, context.getProject().getGroupId(), context.getProject().getArtifactId(), + context.getInputInfo().getChecksum() ); + } + + private String getResourceUrl( String filename, String groupId, String artifactId, String checksum ) + { + return cacheConfig.getUrl() + "/" + MavenProjectInput.CACHE_IMPLMENTATION_VERSION + "/" + groupId + "/" + + artifactId + "/" + checksum + "/" + filename; + } + + /** + * @param logReference + * @param instream to be closed externally + */ + private void putToRemoteCache( InputStream instream, String url, String logReference ) throws IOException + { + + HttpPut httpPut = null; + try + { + httpPut = new HttpPut( url ); + httpPut.setEntity( new InputStreamEntity( instream ) ); + HttpResponse response = httpClient.get().execute( httpPut ); + int statusCode = response.getStatusLine().getStatusCode(); + logInfo( "Saved to remote cache " + url + ". RESPONSE CODE: " + statusCode, logReference ); + } + finally + { + if ( httpPut != null ) + { + httpPut.releaseConnection(); + } + } + } + + private final AtomicReference>> cacheReportSupplier = new AtomicReference<>(); + + @Override + public Optional findBaselineBuild( MavenProject project ) + { + final Optional> cachedProjectsHolder = findCacheInfo() + .transform( CacheReportType::getProject ); + if ( !cachedProjectsHolder.isPresent() ) + { + return Optional.absent(); + } + + Optional cachedProjectHolder = Optional.absent(); + for ( ProjectReportType p : cachedProjectsHolder.get() ) + { + if ( project.getArtifactId().equals( p.getArtifactId() ) && project.getGroupId().equals( + p.getGroupId() ) ) + { + cachedProjectHolder = Optional.of( p ); + break; + } + } + + if ( cachedProjectHolder.isPresent() ) + { + String url; + final ProjectReportType projectReport = cachedProjectHolder.get(); + if ( projectReport.isSetUrl() ) + { + url = cachedProjectHolder.get().getUrl(); + logInfo( "Retrieving baseline buildinfo: " + projectReport.getUrl(), project.getArtifactId() ); + } + else + { + url = getResourceUrl( BUILDINFO_XML, project.getGroupId(), + project.getArtifactId(), projectReport.getChecksum() ); + logInfo( "Baseline project record doesn't have url, trying default location", project.getArtifactId() ); + } + + try + { + if ( exists( project.getArtifactId(), url ) ) + { + byte[] content = getResourceContent( url, project.getArtifactId() ); + final BuildInfoType dto = xmlService.fromBytes( BuildInfoType.class, content ); + return Optional.of( new BuildInfo( dto, CacheSource.REMOTE ) ); + } + else + { + logInfo( "Project buildinfo not found, skipping diff", + project.getArtifactId() ); + } + } + catch ( Exception e ) + { + logger.warn( "[CACHE][" + project.getArtifactId() + "] Error restoring baseline build at url: " + + projectReport.getUrl() + ", skipping diff" ); + return Optional.absent(); + } + } + return Optional.absent(); + } + + private Optional findCacheInfo() + { + + Supplier> candidate = Suppliers.memoize( () -> + { + try + { + logInfo( "Downloading baseline cache report from: " + cacheConfig.getBaselineCacheUrl(), + "DEBUG" ); + byte[] content = getResourceContent( cacheConfig.getBaselineCacheUrl(), "cache-info" ); + CacheReportType cacheReportType = xmlService.fromBytes( CacheReportType.class, content ); + return Optional.of( cacheReportType ); + } + catch ( Exception e ) + { + logger.error( "Error downloading baseline report from: " + cacheConfig.getBaselineCacheUrl() + + ", skipping diff.", e ); + return Optional.absent(); + } + } ); + cacheReportSupplier.compareAndSet( null, candidate ); + + return cacheReportSupplier.get().get(); + } + + private void logInfo( String message, String logReference ) + { + logger.info( "[CACHE][" + logReference + "] " + message ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/LocalArtifactsRepository.java b/maven-core/src/main/java/org/apache/maven/caching/LocalArtifactsRepository.java new file mode 100644 index 000000000000..c5916b2c64ba --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/LocalArtifactsRepository.java @@ -0,0 +1,47 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.base.Optional; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheSource; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Dependency; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * LocalArtifactsRepository + */ +public interface LocalArtifactsRepository extends ArtifactsRepository +{ + + void beforeSave( CacheContext environment ) throws IOException; + + Path getArtifactFile( CacheContext context, CacheSource source, ArtifactType artifact ) throws IOException; + + void clearCache( CacheContext context ); + + Optional findBestMatchingBuild( MavenSession session, Dependency dependency ) throws IOException; + + BuildInfo findLocalBuild( CacheContext context ) throws IOException; +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/LocalRepositoryImpl.java b/maven-core/src/main/java/org/apache/maven/caching/LocalRepositoryImpl.java new file mode 100644 index 000000000000..ec75b476e4b4 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/LocalRepositoryImpl.java @@ -0,0 +1,496 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.caching.jaxb.CacheReportType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheConfig; +import org.apache.maven.caching.xml.CacheSource; +import org.apache.maven.caching.xml.XmlService; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Dependency; +import org.apache.maven.plugin.LegacySupport; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.CREATE_NEW; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.maven.caching.ProjectUtils.getMultimoduleRoot; +import static org.apache.maven.caching.checksum.MavenProjectInput.CACHE_IMPLMENTATION_VERSION; + +/** + * LocalRepositoryImpl + */ +@Component( role = LocalArtifactsRepository.class ) +public class LocalRepositoryImpl implements LocalArtifactsRepository +{ + + private static final String BUILDINFO_XML = "buildinfo.xml"; + private static final String LOOKUPINFO_XML = "lookupinfo.xml"; + private static final long ONE_HOUR_MILLIS = HOURS.toMillis( 1 ); + private static final long ONE_MINUTE_MILLIS = MINUTES.toMillis( 1 ); + private static final long ONE_DAY_MILLIS = DAYS.toMillis( 1 ); + private static final String EMPTY = ""; + private static final LastModifiedComparator LAST_MODIFIED_COMPARATOR = new LastModifiedComparator(); + private static final Function, Long> GET_LAST_MODIFIED = + pair -> pair.getRight().lastModified(); + + @Requirement + private Logger logger; + + @Requirement + private LegacySupport legacySupport; + + @Requirement + private RemoteArtifactsRepository remoteRepository; + + @Requirement + private XmlService xmlService; + + @Requirement + private CacheConfig cacheConfig; + + private final LoadingCache, Optional> bestBuildCache = + CacheBuilder.newBuilder().build( + CacheLoader.from( new Function, Optional>() + { + @Override + public Optional apply( Pair input ) + { + try + { + return findBestMatchingBuildImpl( input ); + } + catch ( IOException e ) + { + logger.error( "Cannot find dependency in cache", e ); + return Optional.absent(); + } + } + } ) ); + + @Override + public BuildInfo findLocalBuild( CacheContext context ) throws IOException + { + Path localBuildInfoPath = localBuildPath( context, BUILDINFO_XML, false ); + logDebug( context, "Checking local build info: " + localBuildInfoPath ); + if ( Files.exists( localBuildInfoPath ) ) + { + logInfo( context, "Local build found by checksum " + context.getInputInfo().getChecksum() ); + try + { + final BuildInfoType dto = xmlService.fromFile( BuildInfoType.class, localBuildInfoPath.toFile() ); + return new BuildInfo( dto, CacheSource.LOCAL ); + } + catch ( Exception e ) + { + logger.error( "Local build info is not valid, deleting: " + localBuildInfoPath, e ); + Files.delete( localBuildInfoPath ); + } + } + return null; + } + + @Override + public BuildInfo findBuild( CacheContext context ) throws IOException + { + + Path buildInfoPath = remoteBuildPath( context, BUILDINFO_XML ); + logDebug( context, "Checking if build is already downloaded: " + buildInfoPath ); + + if ( Files.exists( buildInfoPath ) ) + { + logInfo( context, "Downloaded build found by checksum " + context.getInputInfo().getChecksum() ); + try + { + final BuildInfoType dto = xmlService.fromFile( BuildInfoType.class, buildInfoPath.toFile() ); + return new BuildInfo( dto, CacheSource.REMOTE ); + } + catch ( Exception e ) + { + logger.error( "Downloaded build info is not valid, deleting: " + buildInfoPath, e ); + Files.delete( buildInfoPath ); + } + } + + if ( !cacheConfig.isRemoteCacheEnabled() ) + { + return null; + } + + try + { + + Path lookupInfoPath = remoteBuildPath( context, LOOKUPINFO_XML ); + if ( Files.exists( lookupInfoPath ) ) + { + final BasicFileAttributes fileAttributes = Files.readAttributes( lookupInfoPath, + BasicFileAttributes.class ); + final long lastModified = fileAttributes.lastModifiedTime().toMillis(); + final long created = fileAttributes.creationTime().toMillis(); + final long now = System.currentTimeMillis(); + // throttle remote cache calls, maven like + if ( now < created + ONE_HOUR_MILLIS && now < lastModified + ONE_MINUTE_MILLIS ) + { // fresh file, allow lookup every minute + logInfo( context, "Skipping remote lookup, last unsuccessful lookup less than 1m ago." ); + return null; + } + else if ( now < created + ONE_DAY_MILLIS && now < lastModified + ONE_HOUR_MILLIS ) + { // less than 1 day file, allow 1 per hour lookup + logInfo( context, "Skipping remote lookup, last unsuccessful lookup less than 1h ago." ); + return null; + } + else if ( now > created + ONE_DAY_MILLIS && now < lastModified + ONE_DAY_MILLIS ) + { // more than 1 day file, allow 1 per day lookup + logInfo( context, "Skipping remote lookup, last unsuccessful lookup less than 1d ago." ); + return null; + } + } + + final BuildInfo buildInfo = remoteRepository.findBuild( context ); + if ( buildInfo != null ) + { + logInfo( context, "Build info downloaded from remote repo, saving to: " + buildInfoPath ); + Files.createDirectories( buildInfoPath.getParent() ); + Files.write( buildInfoPath, xmlService.toBytes( buildInfo.getDto() ), CREATE_NEW ); + } + else + { + FileUtils.touch( lookupInfoPath.toFile() ); + } + return buildInfo; + } + catch ( Exception e ) + { + logger.error( "Remote build info is not valid, cached data is not compatible", e ); + return null; + } + } + + @Override + public void clearCache( CacheContext context ) + { + try + { + final Path buildCacheDir = buildCacheDir( context ); + Path artifactCacheDir = buildCacheDir.getParent(); + + if ( !Files.exists( artifactCacheDir ) ) + { + return; + } + + List cacheDirs = new ArrayList<>(); + for ( Path dir : Files.newDirectoryStream( artifactCacheDir ) ) + { + if ( Files.isDirectory( dir ) ) + { + cacheDirs.add( dir ); + } + } + if ( cacheDirs.size() > cacheConfig.getMaxLocalBuildsCached() ) + { + Collections.sort( cacheDirs, LAST_MODIFIED_COMPARATOR ); + for ( Path dir : cacheDirs.subList( 0, cacheDirs.size() - cacheConfig.getMaxLocalBuildsCached() ) ) + { + FileUtils.deleteDirectory( dir.toFile() ); + } + } + final Path path = localBuildDir( context ); + if ( Files.exists( path ) ) + { + FileUtils.deleteDirectory( path.toFile() ); + } + } + catch ( IOException e ) + { + final String artifactId = context.getProject().getArtifactId(); + throw new RuntimeException( + "Failed to cleanup local cache of " + artifactId + " on build failure, it might be inconsistent", + e ); + } + } + + @Override + public Optional findBestMatchingBuild( MavenSession session, Dependency dependency ) + { + return bestBuildCache.getUnchecked( Pair.of( session, dependency ) ); + } + + + private Optional findBestMatchingBuildImpl( Pair dependencySession ) + throws IOException + { + final MavenSession session = dependencySession.getLeft(); + final Dependency dependency = dependencySession.getRight(); + + final Path artifactCacheDir = artifactCacheDir( session, dependency.getGroupId(), dependency.getArtifactId() ); + + final Multimap, Pair> filesByVersion = ArrayListMultimap.create(); + + Files.walkFileTree( artifactCacheDir, new SimpleFileVisitor() + { + @Override + public FileVisitResult visitFile( Path o, BasicFileAttributes basicFileAttributes ) + { + final File file = o.toFile(); + if ( file.getName().equals( BUILDINFO_XML ) ) + { + try + { + final BuildInfoType dto = xmlService.fromFile( BuildInfoType.class, file ); + final Pair buildInfoAndFile = Pair.of( new BuildInfo( dto, CacheSource.LOCAL ), + file ); + final String cachedVersion = dto.getArtifact().getVersion(); + final String cachedBranch = getScmRef( dto.getScm() ); + filesByVersion.put( Pair.of( cachedVersion, cachedBranch ), buildInfoAndFile ); + if ( isNotBlank( cachedBranch ) ) + { + filesByVersion.put( Pair.of( EMPTY, cachedBranch ), buildInfoAndFile ); + } + if ( isNotBlank( cachedVersion ) ) + { + filesByVersion.put( Pair.of( cachedVersion, EMPTY ), buildInfoAndFile ); + } + } + catch ( Exception e ) + { + // version is unusable nothing we can do here + logger.error( "Build info is not compatible to current maven implementation: " + file ); + } + } + return FileVisitResult.CONTINUE; + } + } ); + + if ( filesByVersion.isEmpty() ) + { + return Optional.absent(); + } + + final String currentRef = getScmRef( ProjectUtils.readGitInfo( session ) ); + // first lets try by branch and version + Collection> bestMatched = new LinkedList<>(); + if ( isNotBlank( currentRef ) ) + { + bestMatched = filesByVersion.get( Pair.of( dependency.getVersion(), currentRef ) ); + } + if ( Iterables.isEmpty( bestMatched ) ) + { + // then by version + bestMatched = filesByVersion.get( Pair.of( dependency.getVersion(), EMPTY ) ); + } + if ( Iterables.isEmpty( bestMatched ) && isNotBlank( currentRef ) ) + { + // then by branch + bestMatched = filesByVersion.get( Pair.of( EMPTY, currentRef ) ); + } + if ( Iterables.isEmpty( bestMatched ) ) + { + // ok lets take all + bestMatched = filesByVersion.values(); + } + + List> orderedFiles = Ordering.natural().onResultOf( + GET_LAST_MODIFIED ).reverse().sortedCopy( bestMatched ); + return Optional.of( orderedFiles.get( 0 ).getLeft() ); + } + + private String getScmRef( BuildInfoType.Scm scm ) + { + if ( scm != null ) + { + return scm.isSetSourceBranch() ? scm.getSourceBranch() : scm.getRevision(); + } + else + { + return EMPTY; + } + } + + @Override + public Path getArtifactFile( CacheContext context, CacheSource source, ArtifactType artifact ) throws IOException + { + if ( source == CacheSource.LOCAL ) + { + return localBuildPath( context, artifact.getFileName(), false ); + } + else + { + Path cachePath = remoteBuildPath( context, artifact.getFileName() ); + if ( !Files.exists( cachePath ) && cacheConfig.isRemoteCacheEnabled() ) + { + final byte[] artifactContent = remoteRepository.getArtifactContent( context, artifact ); + if ( artifactContent != null ) + { + Files.write( cachePath, artifactContent, CREATE_NEW ); + } + } + return cachePath; + } + } + + @Override + public void beforeSave( CacheContext environment ) + { + clearCache( environment ); + } + + @Override + public void saveBuildInfo( CacheResult cacheResult, BuildInfo buildInfo ) throws IOException + { + final Path path = localBuildPath( cacheResult.getContext(), BUILDINFO_XML, true ); + Files.write( path, xmlService.toBytes( buildInfo.getDto() ), TRUNCATE_EXISTING, CREATE ); + if ( cacheConfig.isRemoteCacheEnabled() && cacheConfig.isSaveToRemote() && !cacheResult.isFinal() ) + { + remoteRepository.saveBuildInfo( cacheResult, buildInfo ); + } + } + + @Override + public void saveCacheReport( String buildId, MavenSession session, CacheReportType cacheReport ) throws IOException + { + Path path = Paths.get( getMultimoduleRoot( session ), "target", "maven-incremental" ); + Files.createDirectories( path ); + Files.write( path.resolve( "cache-report." + buildId + ".xml" ), xmlService.toBytes( cacheReport ), + TRUNCATE_EXISTING, CREATE ); + if ( cacheConfig.isRemoteCacheEnabled() && cacheConfig.isSaveToRemote() ) + { + logger.info( "[CACHE] Saving cache report on build completion" ); + remoteRepository.saveCacheReport( buildId, session, cacheReport ); + } + } + + @Override + public void saveArtifactFile( CacheResult cacheResult, Artifact artifact ) throws IOException + { + // safe artifacts to cache + File artifactFile = artifact.getFile(); + Path cachePath = localBuildPath( cacheResult.getContext(), ProjectUtils.normalizedName( artifact ), true ); + Files.copy( artifactFile.toPath(), cachePath, StandardCopyOption.REPLACE_EXISTING ); + if ( cacheConfig.isRemoteCacheEnabled() && cacheConfig.isSaveToRemote() && !cacheResult.isFinal() ) + { + remoteRepository.saveArtifactFile( cacheResult, artifact ); + } + } + + private Path buildCacheDir( CacheContext context ) throws IOException + { + final MavenProject project = context.getProject(); + final Path artifactCacheDir = artifactCacheDir( context.getSession(), project.getGroupId(), + project.getArtifactId() ); + return artifactCacheDir.resolve( context.getInputInfo().getChecksum() ); + } + + private Path artifactCacheDir( MavenSession session, String groupId, String artifactId ) throws IOException + { + final String localRepositoryRoot = session.getLocalRepository().getBasedir(); + final Path path = Paths.get( localRepositoryRoot, "..", "cache", CACHE_IMPLMENTATION_VERSION, groupId, + artifactId ).normalize(); + if ( !Files.exists( path ) ) + { + Files.createDirectories( path ); + } + return path; + } + + private Path remoteBuildPath( CacheContext context, String filename ) throws IOException + { + return buildCacheDir( context ).resolve( filename ); + } + + private Path localBuildPath( CacheContext context, String filename, boolean createDir ) throws IOException + { + final Path localBuildDir = localBuildDir( context ); + if ( createDir ) + { + Files.createDirectories( localBuildDir ); + } + return localBuildDir.resolve( filename ); + } + + private Path localBuildDir( CacheContext context ) throws IOException + { + return buildCacheDir( context ).resolve( "local" ); + } + + private void logDebug( CacheContext context, String message ) + { + logger.debug( "[CACHE][" + context.getProject().getArtifactId() + "] " + message ); + } + + private void logInfo( CacheContext context, String message ) + { + logger.info( "[CACHE][" + context.getProject().getArtifactId() + "] " + message ); + } + + private static class LastModifiedComparator implements Comparator + { + @Override + public int compare( Path p1, Path p2 ) + { + try + { + return Files.getLastModifiedTime( p1 ).compareTo( Files.getLastModifiedTime( p2 ) ); + } + catch ( IOException e ) + { + return 0; + } + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/MojoExecutionManager.java b/maven-core/src/main/java/org/apache/maven/caching/MojoExecutionManager.java new file mode 100644 index 000000000000..9c07e5815e69 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/MojoExecutionManager.java @@ -0,0 +1,178 @@ +package org.apache.maven.caching; + +/* + * 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.commons.lang3.StringUtils; +import org.apache.maven.caching.jaxb.CompletedExecutionType; +import org.apache.maven.caching.jaxb.TrackedPropertyType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheConfig; +import org.apache.maven.caching.xml.DtoUtils; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.MojoCheker; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.util.ReflectionUtils; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.apache.maven.caching.ProjectUtils.mojoExecutionKey; + +/** + * MojoExecutionManager + */ +public class MojoExecutionManager implements MojoCheker +{ + + private final long createdTimestamp; + private final Logger logger; + private final MavenProject project; + private final BuildInfo buildInfo; + private final AtomicBoolean consistent; + private final CacheController cacheController; + private final CacheConfig cacheConfig; + + public MojoExecutionManager( MavenProject project, + CacheController cacheController, + BuildInfo buildInfo, + AtomicBoolean consistent, + Logger logger, CacheConfig cacheConfig ) + { + this.createdTimestamp = System.currentTimeMillis(); + this.project = project; + this.cacheController = cacheController; + this.buildInfo = buildInfo; + this.consistent = consistent; + this.logger = logger; + this.cacheConfig = cacheConfig; + } + + /** + * runtime check is rather expensive for cached build and better to be avoided when possible + */ + @Override + public boolean needCheck( MojoExecution mojoExecution, MavenSession session ) + { + return !cacheConfig.getTrackedProperties( mojoExecution ).isEmpty(); + } + + /** + * this implementation has side effect of consistency check to force local save if local run is different than + * cached + * + * @return false always returns false to prevent mojo execution + */ + @Override + public boolean check( MojoExecution execution, Mojo mojo, MavenSession session ) + { + + final CompletedExecutionType completedExecution = buildInfo.findMojoExecutionInfo( execution ); + final String fullGoalName = execution.getMojoDescriptor().getFullGoalName(); + + if ( completedExecution != null && !isParamsMatched( project, execution, mojo, completedExecution ) ) + { + logInfo( project, + "Mojo cached parameters mismatch with actual, forcing full project build. Mojo: " + fullGoalName ); + consistent.set( false ); + } + + if ( consistent.get() ) + { + long elapsed = System.currentTimeMillis() - createdTimestamp; + logInfo( project, "Skipping plugin execution (reconciled in " + elapsed + " millis): " + fullGoalName ); + } + + if ( logger.isDebugEnabled() ) + { + logger.debug( + "[CACHE][" + project.getArtifactId() + "] Checked " + fullGoalName + ", resolved mojo: " + mojo + + ", cached params:" + completedExecution ); + } + return false; + } + + private boolean isParamsMatched( MavenProject project, + MojoExecution mojoExecution, + Mojo mojo, + CompletedExecutionType completedExecution ) + { + + List tracked = cacheConfig.getTrackedProperties( mojoExecution ); + + for ( TrackedPropertyType trackedProperty : tracked ) + { + final String propertyName = trackedProperty.getPropertyName(); + + String expectedValue = DtoUtils.findPropertyValue( propertyName, completedExecution ); + if ( expectedValue == null && trackedProperty.isSetDefaultValue() ) + { + expectedValue = trackedProperty.getDefaultValue(); + } + + final String currentValue; + try + { + currentValue = String.valueOf( ReflectionUtils.getValueIncludingSuperclasses( propertyName, mojo ) ); + } + catch ( IllegalAccessException e ) + { + logError( project, "Cannot extract plugin property " + propertyName + " from mojo " + mojo, e ); + return false; + } + + if ( !StringUtils.equals( currentValue, expectedValue ) ) + { + if ( !StringUtils.equals( currentValue, trackedProperty.getSkipValue() ) ) + { + logInfo( project, + "Plugin parameter mismatch found. Parameter: " + propertyName + ", expected: " + + expectedValue + ", actual: " + currentValue ); + return false; + } + else + { + logWarn( project, + "Cache contains plugin execution with skip flag and might be incomplete. Property: " + + propertyName + ", execution: " + mojoExecutionKey( mojoExecution ) ); + } + } + } + return true; + } + + private void logInfo( MavenProject project, String message ) + { + logger.info( "[CACHE][" + project.getArtifactId() + "] " + message ); + } + + private void logError( MavenProject project, String message, Exception e ) + { + logger.error( "[CACHE][" + project.getArtifactId() + "] " + message, e ); + } + + private void logWarn( MavenProject project, String message ) + { + logger.warn( "[CACHE][" + project.getArtifactId() + "] " + message ); + } + +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/MojoParametersListener.java b/maven-core/src/main/java/org/apache/maven/caching/MojoParametersListener.java new file mode 100644 index 000000000000..8750a8dcca67 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/MojoParametersListener.java @@ -0,0 +1,102 @@ +package org.apache.maven.caching; + +/* + * 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.maven.execution.MojoExecutionEvent; +import org.apache.maven.execution.MojoExecutionListener; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * MojoParametersListener + */ +@Component( role = MojoExecutionListener.class, + hint = "MojoParametersListener" ) +public class MojoParametersListener implements MojoExecutionListener +{ + + private final ConcurrentMap> projectExecutions = + new ConcurrentHashMap<>(); + + @Requirement + private Logger logger; + + @Override + public void beforeMojoExecution( MojoExecutionEvent event ) + { + final String executionKey = ProjectUtils.mojoExecutionKey( event.getExecution() ); + logDebug( event.getProject(), + "Starting mojo execution: " + executionKey + ", class: " + event.getMojo().getClass() ); + final MavenProject project = event.getProject(); + Map projectEvents = projectExecutions.get( project ); + if ( projectEvents == null ) + { + Map candidate = new ConcurrentHashMap<>(); + projectEvents = projectExecutions.putIfAbsent( project, candidate ); + if ( projectEvents == null ) + { + projectEvents = candidate; + } + } + projectEvents.put( executionKey, event ); + } + + @Override + public void afterMojoExecutionSuccess( MojoExecutionEvent event ) throws MojoExecutionException + { + // do nothing + } + + @Override + public void afterExecutionFailure( MojoExecutionEvent event ) + { + //do nothing + } + + public Map getProjectExecutions( MavenProject project ) + { + return projectExecutions.get( project ); + } + + public void remove( MavenProject project ) + { + projectExecutions.remove( project ); + } + + private void logDebug( MavenProject project, String message ) + { + if ( logger.isDebugEnabled() ) + { + logger.debug( "[CACHE][" + project.getArtifactId() + "] " + message ); + } + } + + // private void logInfo(String message) { + // logger.info("[CACHE][" + project.getArtifactId() + "] " + message); + // } + +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfig.java b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfig.java new file mode 100644 index 000000000000..93bcccba0b1c --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfig.java @@ -0,0 +1,41 @@ +package org.apache.maven.caching; + +/* + * 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.maven.caching.jaxb.DirScanConfigType; + +import javax.annotation.Nonnull; + +/** + * PluginScanConfig + */ +public interface PluginScanConfig +{ + boolean isSkip(); + + boolean accept( String propertyName ); + + PluginScanConfig mergeWith( PluginScanConfig overrideSource ); + + @Nonnull + ScanConfigProperties getTagScanProperties( String tagName ); + + DirScanConfigType dto(); +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfigImpl.java b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfigImpl.java new file mode 100644 index 000000000000..87791905100d --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfigImpl.java @@ -0,0 +1,161 @@ +package org.apache.maven.caching; + +/* + * 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.commons.lang3.StringUtils; +import org.apache.maven.caching.jaxb.DirScanConfigType; +import org.apache.maven.caching.jaxb.TagNameType; +import org.apache.maven.caching.jaxb.TagScanConfigType; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * PluginScanConfigImpl + */ +public class PluginScanConfigImpl implements PluginScanConfig +{ + + private final DirScanConfigType dto; + + public PluginScanConfigImpl( DirScanConfigType scanConfig ) + { + this.dto = scanConfig; + } + + @Override + public boolean isSkip() + { + return StringUtils.equals( dto.getMode(), "skip" ); + } + + @Override + public boolean accept( String tagName ) + { + // include or exclude is a choice element, could be only obe property set + + //noinspection ConstantConditions + final List includes = dto.getInclude(); + if ( !includes.isEmpty() ) + { + return findTagScanProperties( tagName ) != null; + } + + return !contains( dto.getExclude(), tagName ); + } + + private boolean contains( List excludes, String tagName ) + { + for ( TagNameType exclude : excludes ) + { + if ( StringUtils.equals( exclude.getTagName(), tagName ) ) + { + return true; + } + } + return false; + } + + @Nonnull + @Override + public PluginScanConfig mergeWith( final PluginScanConfig overrideConfig ) + { + + if ( dto == null ) + { + return overrideConfig; + } + + final DirScanConfigType override = overrideConfig.dto(); + if ( override == null ) + { + return this; + } + + if ( override.isIgnoreParent() ) + { + return overrideConfig; + } + + DirScanConfigType merged = new DirScanConfigType(); + if ( override.isSetMode() ) + { + merged.setMode( override.getMode() ); + } + else + { + merged.setMode( dto.getMode() ); + } + + merged.getExclude().addAll( dto.getExclude() ); + merged.getExclude().addAll( override.getExclude() ); + + merged.getInclude().addAll( dto.getInclude() ); + merged.getInclude().addAll( override.getInclude() ); + + return new PluginScanConfigImpl( merged ); + } + + @Nonnull + public ScanConfigProperties getTagScanProperties( String tagName ) + { + ScanConfigProperties scanProperties = findTagScanProperties( tagName ); + return scanProperties != null ? scanProperties : defaultScanConfig(); + } + + @Override + public DirScanConfigType dto() + { + return dto; + } + + private ScanConfigProperties findTagScanProperties( String tagName ) + { + ScanConfigProperties scanConfigProperties = findConfigByName( tagName, dto.getInclude() ); + if ( scanConfigProperties == null ) + { + scanConfigProperties = findConfigByName( tagName, dto.getTagScanConfig() ); + } + return scanConfigProperties; + } + + private ScanConfigProperties findConfigByName( String tagName, List configs ) + { + + if ( configs == null ) + { + return null; + } + + for ( TagScanConfigType config : configs ) + { + if ( StringUtils.equals( tagName, config.getTagName() ) ) + { + return new ScanConfigProperties( config.isRecursive(), config.getGlob() ); + } + } + return null; + } + + private static ScanConfigProperties defaultScanConfig() + { + return new ScanConfigProperties( true, null ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/ProjectUtils.java b/maven-core/src/main/java/org/apache/maven/caching/ProjectUtils.java new file mode 100644 index 000000000000..176556850377 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/ProjectUtils.java @@ -0,0 +1,197 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.handler.ArtifactHandler; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.internal.ProjectIndex; +import org.apache.maven.lifecycle.internal.builder.BuilderCommon; +import org.apache.maven.model.Dependency; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.apache.commons.lang3.StringUtils.removeStart; +import static org.apache.commons.lang3.StringUtils.trim; +import static org.apache.maven.artifact.Artifact.LATEST_VERSION; +import static org.apache.maven.artifact.Artifact.SNAPSHOT_VERSION; + +/** + * ProjectUtils + */ +public class ProjectUtils +{ + + private static final List PHASES = Lists.newArrayList( + //clean + "pre-clean", "clean", "post-clean", + // default + "validate", "initialize", "generate-sources", "process-sources", "generate-resources", "process-resources", + "compile", "process-classes", "generate-test-sources", "process-test-sources", "generate-test-resources", + "process-test-resources", "test-compile", "process-test-classes", "test", "prepare-package", "package", + "pre-integration-test", "integration-test", "post-integration-test", "verify", "install", "deploy", + //site + "pre-site", "site", "post-site", "site-deploy" ); + + /** + * @param phase + * @param other + * @return true if the given phase is later than the other in maven lifecycle. Example: isLaterPhase("install", + * "clean") returns true; + */ + public static boolean isLaterPhase( String phase, String other ) + { + checkArgument( PHASES.contains( phase ), "Unsupported phase: " + phase ); + checkArgument( PHASES.contains( other ), "Unsupported phase: " + other ); + + return PHASES.indexOf( phase ) > PHASES.indexOf( other ); + } + + public static boolean isBuilding( Dependency dependency, ProjectIndex projectIndex ) + { + final MavenProject key = new MavenProject(); + key.setGroupId( dependency.getGroupId() ); + key.setArtifactId( dependency.getArtifactId() ); + key.setVersion( dependency.getVersion() ); + return projectIndex.getProjects().containsKey( BuilderCommon.getKey( key ) ); + } + + public static boolean isPomPackaging( MavenProject project ) + { + return project.getPackaging().equals( "pom" ) && !new File( getSrcDir( project ) ).exists(); + } + + public static boolean isPom( Artifact artifact ) + { + return artifact.getType().equals( "pom" ); + } + + public static boolean isPom( Dependency dependency ) + { + return dependency.getType().equals( "pom" ); + } + + public static boolean isSnapshot( String version ) + { + return version.endsWith( SNAPSHOT_VERSION ) || version.endsWith( LATEST_VERSION ); + } + + public static String getTargetDir( MavenProject project ) + { + return FilenameUtils.concat( project.getBasedir().getAbsolutePath(), "target" ); + } + + public static String getSrcDir( MavenProject project ) + { + return FilenameUtils.concat( project.getBasedir().getAbsolutePath(), "src" ); + } + + public static String normalizedName( Artifact artifact ) + { + + if ( artifact.getFile() == null ) + { + return null; + } + + StringBuilder filename = new StringBuilder( artifact.getArtifactId() ); + + if ( artifact.hasClassifier() ) + { + filename.append( "-" ).append( artifact.getClassifier() ); + } + + final ArtifactHandler artifactHandler = artifact.getArtifactHandler(); + if ( artifactHandler != null && StringUtils.isNotBlank( artifactHandler.getExtension() ) ) + { + filename.append( "." ).append( artifactHandler.getExtension() ); + } + return filename.toString(); + } + + public static String mojoExecutionKey( MojoExecution mojo ) + { + return StringUtils.join( Lists.newArrayList( StringUtils.defaultIfEmpty( mojo.getExecutionId(), "emptyExecId" ), + StringUtils.defaultIfEmpty( mojo.getGoal(), "emptyGoal" ), + StringUtils.defaultIfEmpty( mojo.getLifecyclePhase(), "emptyLifecyclePhase" ), + StringUtils.defaultIfEmpty( mojo.getArtifactId(), "emptyArtifactId" ), + StringUtils.defaultIfEmpty( mojo.getGroupId(), "emptyGroupId" ), + StringUtils.defaultIfEmpty( mojo.getVersion(), "emptyVersion" ) ), ":" ); + } + + public static String getMultimoduleRoot( MavenSession session ) + { + return System.getProperty( "maven.multiModuleProjectDirectory", session.getExecutionRootDirectory() ); + } + + public static BuildInfoType.Scm readGitInfo( MavenSession session ) throws IOException + { + final BuildInfoType.Scm scmCandidate = new BuildInfoType.Scm(); + final Path gitDir = Paths.get( getMultimoduleRoot( session ), ".git" ); + if ( Files.isDirectory( gitDir ) ) + { + final Path headFile = gitDir.resolve( "HEAD" ); + if ( Files.exists( headFile ) ) + { + String headRef = readFirstLine( headFile, "" ); + if ( headRef.startsWith( "ref: " ) ) + { + String branch = trim( removeStart( headRef, "ref: " ) ); + scmCandidate.setSourceBranch( branch ); + final Path refPath = gitDir.resolve( branch ); + if ( Files.exists( refPath ) ) + { + String revision = readFirstLine( refPath, "" ); + scmCandidate.setRevision( trim( revision ) ); + } + } + else + { + scmCandidate.setSourceBranch( headRef ); + scmCandidate.setRevision( headRef ); + } + } + } + return scmCandidate; + } + + + private static String readFirstLine( Path path, String defaultValue ) throws IOException + { + final List lines = Files.readAllLines( path, StandardCharsets.UTF_8 ); + return Iterables.getFirst( lines, defaultValue ); + } + + +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/RemoteArtifactsRepository.java b/maven-core/src/main/java/org/apache/maven/caching/RemoteArtifactsRepository.java new file mode 100644 index 000000000000..7c118c405739 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/RemoteArtifactsRepository.java @@ -0,0 +1,42 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.base.Optional; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.project.MavenProject; + +import java.io.IOException; + +/** + * RemoteArtifactsRepository + */ +public interface RemoteArtifactsRepository extends ArtifactsRepository +{ + + byte[] getArtifactContent( CacheContext context, ArtifactType artifact ) throws IOException; + + byte[] getResourceContent( String resourceUrl, String logReference ); + + String getResourceUrl( CacheContext context, String filename ); + + Optional findBaselineBuild( MavenProject project ); +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/RestoreStatus.java b/maven-core/src/main/java/org/apache/maven/caching/RestoreStatus.java new file mode 100644 index 000000000000..6bf8fefc2832 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/RestoreStatus.java @@ -0,0 +1,31 @@ +package org.apache.maven.caching; + +/* + * 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. + */ + +/** + * RestoreStatus + */ +public enum RestoreStatus +{ + EMPTY, + FAILURE, + PARTIAL, + SUCCESS +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/ScanConfigProperties.java b/maven-core/src/main/java/org/apache/maven/caching/ScanConfigProperties.java new file mode 100644 index 000000000000..bc9622a59fa8 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/ScanConfigProperties.java @@ -0,0 +1,45 @@ +package org.apache.maven.caching; + +/* + * 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. + */ + +/** + * ScanConfigProperties + */ +public class ScanConfigProperties +{ + private final boolean recursive; + private final String glob; + + public ScanConfigProperties( boolean recursive, String glob ) + { + this.recursive = recursive; + this.glob = glob; + } + + public boolean isRecursive() + { + return recursive; + } + + public String getGlob() + { + return glob; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/ZipUtils.java b/maven-core/src/main/java/org/apache/maven/caching/ZipUtils.java new file mode 100644 index 000000000000..9f15bb1d2460 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/ZipUtils.java @@ -0,0 +1,116 @@ +package org.apache.maven.caching; + +/* + * 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.commons.io.IOUtils; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * ZipUtils + */ +public class ZipUtils +{ + + private static final int BUFFER_SIZE = 4096; + + public static InputStream zipFolder( final Path dir ) throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try ( ZipOutputStream zipOutputStream = new ZipOutputStream( out ) ) + { + processFolder( dir, zipOutputStream ); + } + return new ByteArrayInputStream( out.toByteArray() ); + } + + private static void processFolder( final Path dir, final ZipOutputStream zipOutputStream ) throws IOException + { + Files.walkFileTree( dir, new SimpleFileVisitor() + { + @Override + public FileVisitResult visitFile( Path path, BasicFileAttributes basicFileAttributes ) throws IOException + { + final ZipEntry zipEntry = new ZipEntry( dir.relativize( path ).toString() ); + zipOutputStream.putNextEntry( zipEntry ); + try ( InputStream inputStream = Files.newInputStream( path ) ) + { + IOUtils.copy( inputStream, zipOutputStream ); + } + zipOutputStream.closeEntry(); + return FileVisitResult.CONTINUE; + } + } ); + } + + public static void unzip( InputStream is, Path out ) throws IOException + { + try ( ZipInputStream zis = new ZipInputStream( is ) ) + { + + ZipEntry entry = zis.getNextEntry(); + + while ( entry != null ) + { + File file = new File( out.toFile(), entry.getName() ); + file.setLastModified( entry.getTime() ); + + if ( entry.isDirectory() ) + { + file.mkdirs(); + } + else + { + File parent = file.getParentFile(); + + if ( !parent.exists() ) + { + parent.mkdirs(); + } + + try ( BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream( file ) ) ) + { + byte[] buffer = new byte[BUFFER_SIZE]; + int location; + while ( ( location = zis.read( buffer ) ) != -1 ) + { + bos.write( buffer, 0, location ); + } + } + } + entry = zis.getNextEntry(); + } + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/DependencyNotResolvedException.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/DependencyNotResolvedException.java new file mode 100644 index 000000000000..bcc017b8caab --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/DependencyNotResolvedException.java @@ -0,0 +1,31 @@ +package org.apache.maven.caching.checksum; + +/* + * 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. + */ + +/** + * DependencyNotResolvedException + */ +public class DependencyNotResolvedException extends RuntimeException +{ + public DependencyNotResolvedException( String message ) + { + super( message ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/DigestUtils.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/DigestUtils.java new file mode 100644 index 000000000000..e837da1f8587 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/DigestUtils.java @@ -0,0 +1,189 @@ +package org.apache.maven.caching.checksum; + +/* + * 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.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.caching.hash.HashChecksum; +import org.apache.maven.caching.jaxb.DigestItemType; +import org.mozilla.universalchardet.UniversalDetector; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.lang3.StringUtils.containsAny; +import static org.apache.commons.lang3.StringUtils.equalsAny; +import static org.apache.commons.lang3.StringUtils.startsWith; +import static org.apache.commons.lang3.StringUtils.startsWithAny; + +/** + * DigestUtils + */ +public class DigestUtils +{ + + private static final ThreadLocal ENCODING_DETECTOR = new ThreadLocal() + { + @Override + protected UniversalDetector initialValue() + { + return new UniversalDetector( null ); + } + }; + + + public static DigestItemType pom( HashChecksum checksum, String effectivePom ) + { + return item( "pom", effectivePom, checksum.update( effectivePom.getBytes( UTF_8 ) ) ); + } + + public static DigestItemType file( HashChecksum checksum, Path basedir, Path file ) throws IOException + { + byte[] content = Files.readAllBytes( file ); + DigestItemType item = item( "file", normalize( basedir, file ), checksum.update( content ) ); + try + { + populateContentDetails( file, content, item ); + } + catch ( IOException ignore ) + { + System.out.println( "hello" ); + } + return item; + } + + private static void populateContentDetails( Path file, byte[] content, DigestItemType item ) throws IOException + { + String contentType = Files.probeContentType( file ); + if ( contentType != null ) + { + item.setContent( contentType ); + } + final boolean binary = isBinary( contentType ); + item.setIsText( isText( contentType ) ? "yes" : binary ? "no" : "unknown" ); + if ( !binary ) + { // probing application/ files as well though might be binary + UniversalDetector detector = ENCODING_DETECTOR.get(); + detector.reset(); + detector.handleData( content, 0, Math.min( content.length, 16 * 1024 ) ); + detector.dataEnd(); + String detectedCharset = detector.getDetectedCharset(); + Charset charset = UTF_8; + if ( detectedCharset != null ) + { + item.setCharset( detectedCharset ); + charset = Charset.forName( detectedCharset ); + } + CharBuffer charBuffer = charset.decode( ByteBuffer.wrap( content ) ); + String lineSeparator = detectLineSeparator( charBuffer ); + item.setEol( StringUtils.defaultString( lineSeparator, "unknown" ) ); + } + } + + // TODO add support for .gitattributes to statically configure file type before falling back to probe based content checks + private static boolean isText( String contentType ) + { + return startsWith( contentType, "text/" ) + || containsAny( contentType, "+json", "+xml" ) // common mime type suffixes + || equalsAny( contentType, // some common text types + "application/json", + "application/rtf", + "application/x-sh", + "application/xml", + "application/javascript", + "application/sql" + ); + } + + private static boolean isBinary( String contentType ) + { + return startsWithAny( contentType, "image/", "audio/", "video/", "font/" ) + || containsAny( contentType, "+zip", "+gzip" ) + || equalsAny( contentType, + "application/octet-stream", + "application/java-archive", + "application/x-bzip", + "application/x-bzip2", + "application/zip", + "application/gzip", + "application/x-tar", + "application/msword", + "application/vnd.ms-excel", + "application/vnd.ms-powerpoint", + "application/pdf" + ); + } + + public static DigestItemType dependency( HashChecksum checksum, String key, String hash ) + { + return item( "dependency", key, checksum.update( hash ) ); + } + + private static String normalize( Path basedirPath, Path file ) + { + return FilenameUtils.separatorsToUnix( relativize( basedirPath, file ).toString() ); + } + + private static Path relativize( Path basedirPath, Path file ) + { + try + { + return basedirPath.relativize( file ); + } + catch ( Exception ignore ) + { + return file; + } + } + + private static DigestItemType item( String type, String reference, String hash ) + { + final DigestItemType item = new DigestItemType(); + item.setType( type ); + item.setValue( reference ); + item.setHash( hash ); + return item; + } + + private DigestUtils() + { + } + + public static String detectLineSeparator( CharSequence text ) + { + // first line break only + int index = StringUtils.indexOfAny( text, "\n\r" ); + if ( index == -1 || index >= text.length() ) + { + return null; + } + char ch = text.charAt( index ); + if ( ch == '\r' ) + { + return index + 1 < text.length() && text.charAt( index + 1 ) == '\n' ? "CRLF" : "CR"; + } + return ch == '\n' ? "LF" : null; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/KeyUtils.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/KeyUtils.java new file mode 100644 index 000000000000..04d9207cb0dc --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/KeyUtils.java @@ -0,0 +1,64 @@ +package org.apache.maven.caching.checksum; + +/* + * 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.commons.lang3.StringUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.model.Dependency; +import org.apache.maven.project.MavenProject; + +/** + * KeyUtils + */ +public class KeyUtils +{ + + private static final String SEPARATOR = ":"; + + public static String getProjectKey( MavenProject project ) + { + return StringUtils.joinWith( SEPARATOR, project.getGroupId(), project.getArtifactId(), project.getVersion() ); + } + + public static String getVersionlessProjectKey( MavenProject project ) + { + return StringUtils.joinWith( SEPARATOR, project.getGroupId(), project.getArtifactId() ); + } + + public static String getVersionlessDependencyKey( Dependency dependency ) + { + return StringUtils.joinWith( SEPARATOR, dependency.getGroupId(), dependency.getArtifactId() ); + } + + public static String getArtifactKey( ArtifactType artifact ) + { + return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + + ( artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" ) + + ":" + artifact.getVersion(); + } + + public static String getArtifactKey( Artifact artifact ) + { + return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + + ( artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" ) + + ":" + artifact.getVersion(); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/MavenProjectInput.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/MavenProjectInput.java new file mode 100644 index 000000000000..8e5873ce7dcd --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/MavenProjectInput.java @@ -0,0 +1,950 @@ +package org.apache.maven.caching.checksum; + +/* + * 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 com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; +import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; +import org.apache.maven.artifact.resolver.ArtifactResolutionResult; +import org.apache.maven.caching.Clock; +import org.apache.maven.caching.LocalArtifactsRepository; +import org.apache.maven.caching.PluginScanConfig; +import org.apache.maven.caching.ProjectUtils; +import org.apache.maven.caching.RemoteArtifactsRepository; +import org.apache.maven.caching.ScanConfigProperties; +import org.apache.maven.caching.hash.HashAlgorithm; +import org.apache.maven.caching.hash.HashChecksum; +import org.apache.maven.caching.hash.HashFactory; +import org.apache.maven.caching.jaxb.DigestItemType; +import org.apache.maven.caching.jaxb.ProjectsInputInfoType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheConfig; +import org.apache.maven.caching.xml.DtoUtils; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.internal.ProjectIndex; +import org.apache.maven.model.Build; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.model.PluginManagement; +import org.apache.maven.model.Resource; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.apache.maven.project.MavenProject; +import org.apache.maven.repository.RepositorySystem; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.WriterFactory; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +import javax.annotation.Nonnull; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentMap; + +import static org.apache.commons.lang3.StringUtils.contains; +import static org.apache.commons.lang3.StringUtils.defaultIfEmpty; +import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.replaceEachRepeatedly; +import static org.apache.commons.lang3.StringUtils.startsWithAny; +import static org.apache.commons.lang3.StringUtils.stripToEmpty; +import static org.apache.maven.caching.ProjectUtils.isBuilding; +import static org.apache.maven.caching.ProjectUtils.isSnapshot; +import static org.apache.maven.caching.jaxb.PathSetType.Include; + +/** + * MavenProjectInput + */ +public class MavenProjectInput +{ + + /** + * Version ov hashing algorithm implementation. It is recommended to change to simplify remote cache maintenance + */ + public static final String CACHE_IMPLMENTATION_VERSION = "v10"; + + /** + * property name to pass glob value. The glob to be used to list directory files in plugins scanning + */ + private static final String CACHE_INPUT_GLOB_NAME = "remote.cache.input.glob"; + /** + * default glob, bbsdk/abfx specific + */ + public static final String DEFAULT_GLOB = "{*.java,*.groovy,*.yaml,*.svcd,*.proto,*assembly.xml,assembly" + + "*.xml,*logback.xml,*.vm,*.ini,*.jks,*.properties,*.sh,*.bat}"; + /** + * property name prefix to pass input files with project properties. smth like remote.cache.input.1 will be + * accepted + */ + private static final String CACHE_INPUT_NAME = "remote.cache.input"; + /** + * property name prefix to exclude files from input. smth like remote.cache.exclude.1 should be set in project + * props + */ + private static final String CACHE_EXCLUDE_NAME = "remote.cache.exclude"; + /** + * Flag to control if we should check values from plugin configs as file system objects + */ + private static final String CACHE_PROCESS_PLUGINS = "remote.cache.processPlugins"; + private final Logger logger; + private final MavenProject project; + private final MavenSession session; + private final LocalArtifactsRepository localCache; + private final RemoteArtifactsRepository remoteCache; + private final RepositorySystem repoSystem; + private final ArtifactHandlerManager artifactHandlerManager; + private final CacheConfig config; + private final ConcurrentMap projectArtifactsByKey; + private final PathIgnoringCaseComparator fileComparator; + private final DependencyComparator dependencyComparator; + private final List filteredOutPaths; + private final Path baseDirPath; + private final String dirGlob; + private final boolean processPlugins; + private final ProjectIndex projectIndex; + + @SuppressWarnings( "checkstyle:parameternumber" ) + public MavenProjectInput( MavenProject project, + MavenSession session, + CacheConfig config, + ProjectIndex projectIndex, + ConcurrentMap artifactsByKey, + RepositorySystem repoSystem, + ArtifactHandlerManager artifactHandlerManager, + Logger logger, + LocalArtifactsRepository localCache, + RemoteArtifactsRepository remoteCache ) + { + this.project = project; + this.session = session; + this.config = config; + this.projectIndex = projectIndex; + this.projectArtifactsByKey = artifactsByKey; + this.baseDirPath = project.getBasedir().toPath().toAbsolutePath(); + this.repoSystem = repoSystem; + this.artifactHandlerManager = artifactHandlerManager; + this.logger = logger; + this.localCache = localCache; + this.remoteCache = remoteCache; + Properties properties = project.getProperties(); + this.dirGlob = properties.getProperty( CACHE_INPUT_GLOB_NAME, config.getDefaultGlob() ); + this.processPlugins = Boolean.parseBoolean( + properties.getProperty( CACHE_PROCESS_PLUGINS, config.isProcessPlugins() ) ); + + Build build = project.getBuild(); + filteredOutPaths = new ArrayList<>( Arrays.asList( normalizedPath( build.getDirectory() ), // target by default + normalizedPath( build.getOutputDirectory() ), normalizedPath( build.getTestOutputDirectory() ) ) ); + + for ( String excludePath : config.getGlobalExcludePaths() ) + { + filteredOutPaths.add( Paths.get( excludePath ) ); + } + + for ( String propertyName : properties.stringPropertyNames() ) + { + if ( propertyName.startsWith( CACHE_EXCLUDE_NAME ) ) + { + filteredOutPaths.add( Paths.get( properties.getProperty( propertyName ) ) ); + } + } + + this.fileComparator = new PathIgnoringCaseComparator(); + this.dependencyComparator = new DependencyComparator(); + } + + public ProjectsInputInfoType calculateChecksum( HashFactory hashFactory ) throws IOException + { + long time = Clock.time(); + + final String effectivePom = getEffectivePom( project.getOriginalEffectiveModel() ); + final SortedSet inputFiles = getInputFiles(); + final SortedMap dependenciesChecksum = getMutableDependencies(); + + final long inputTime = Clock.elapsed( time ); + time = Clock.time(); + + // hash items: effective pom + input files + dependencies + final int count = 1 + inputFiles.size() + dependenciesChecksum.size(); + final List items = new ArrayList<>( count ); + final HashChecksum checksum = hashFactory.createChecksum( count ); + + Optional baselineHolder = Optional.absent(); + if ( config.isBaselineDiffEnabled() ) + { + baselineHolder = + remoteCache.findBaselineBuild( project ).transform( b -> b.getDto().getProjectsInputInfo() ); + } + + DigestItemType effectivePomChecksum = DigestUtils.pom( checksum, effectivePom ); + items.add( effectivePomChecksum ); + final boolean compareWithBaseline = config.isBaselineDiffEnabled() && baselineHolder.isPresent(); + if ( compareWithBaseline ) + { + checkEffectivePomMatch( baselineHolder.get(), effectivePomChecksum ); + } + + boolean sourcesMatched = true; + for ( Path file : inputFiles ) + { + DigestItemType fileDigest = DigestUtils.file( checksum, baseDirPath, file ); + items.add( fileDigest ); + if ( compareWithBaseline ) + { + sourcesMatched &= checkItemMatchesBaseline( baselineHolder.get(), fileDigest ); + } + } + if ( compareWithBaseline ) + { + logInfo( "Source code: " + ( sourcesMatched ? "MATCHED" : "OUT OF DATE" ) ); + } + + boolean dependenciesMatched = true; + for ( Map.Entry entry : dependenciesChecksum.entrySet() ) + { + DigestItemType dependencyDigest = + DigestUtils.dependency( checksum, entry.getKey(), entry.getValue().getHash() ); + items.add( dependencyDigest ); + if ( compareWithBaseline ) + { + dependenciesMatched &= checkItemMatchesBaseline( baselineHolder.get(), dependencyDigest ); + } + } + + if ( compareWithBaseline ) + { + logInfo( "Dependencies: " + ( dependenciesMatched ? "MATCHED" : "OUT OF DATE" ) ); + } + + final ProjectsInputInfoType projectsInputInfoType = new ProjectsInputInfoType(); + projectsInputInfoType.setChecksum( checksum.digest() ); + projectsInputInfoType.getItem().addAll( items ); + + final long checksumTime = Clock.elapsed( time ); + + if ( logger.isDebugEnabled() ) + { + for ( DigestItemType item : projectsInputInfoType.getItem() ) + { + logger.debug( "Hash calculated, item: " + item.getType() + ", hash: " + item.getHash() ); + } + } + logInfo( + "Project inputs calculated in " + inputTime + " ms. " + hashFactory.getAlgorithm() + + " checksum [" + projectsInputInfoType.getChecksum() + "] calculated in " + + checksumTime + " ms." ); + return projectsInputInfoType; + } + + private void checkEffectivePomMatch( ProjectsInputInfoType baselineBuild, DigestItemType effectivePomChecksum ) + { + Optional pomHolder = Optional.absent(); + for ( DigestItemType it : baselineBuild.getItem() ) + { + if ( it.getType().equals( "pom" ) ) + { + pomHolder = Optional.of( it ); + break; + } + } + + if ( pomHolder.isPresent() ) + { + DigestItemType pomItem = pomHolder.get(); + final boolean matches = StringUtils.equals( pomItem.getHash(), effectivePomChecksum.getHash() ); + if ( !matches ) + { + logInfo( + "Mismatch in effective poms. Current: " + effectivePomChecksum.getHash() + ", remote: " + + pomItem.getHash() ); + } + logInfo( "Effective pom: " + ( matches ? "MATCHED" : "OUT OF DATE" ) ); + } + } + + private boolean checkItemMatchesBaseline( ProjectsInputInfoType baselineBuild, DigestItemType fileDigest ) + { + Optional baselineFileDigest = Optional.absent(); + for ( DigestItemType it : baselineBuild.getItem() ) + { + if ( it.getType().equals( fileDigest.getType() ) + && fileDigest.getValue().equals( it.getValue().trim() ) ) + { + baselineFileDigest = Optional.of( it ); + break; + } + } + + boolean matched = false; + if ( baselineFileDigest.isPresent() ) + { + String hash = baselineFileDigest.get().getHash(); + matched = StringUtils.equals( hash, fileDigest.getHash() ); + if ( !matched ) + { + logInfo( + "Mismatch in " + fileDigest.getType() + ": " + fileDigest.getValue() + ". Local hash: " + + fileDigest.getHash() + ", remote: " + hash ); + } + } + else + { + logInfo( + "Mismatch in " + fileDigest.getType() + ": " + fileDigest.getValue() + + ". Not found in remote cache" ); + } + return matched; + } + + /** + * @param prototype effective model fully resolved by maven build. Do not pass here just parsed Model. + */ + private String getEffectivePom( Model prototype ) throws IOException + { + // TODO validate status of the model - it should be in resolved state + Model toHash = new Model(); + + toHash.setGroupId( prototype.getGroupId() ); + toHash.setArtifactId( prototype.getArtifactId() ); + toHash.setVersion( prototype.getVersion() ); + toHash.setModules( prototype.getModules() ); + + Collections.sort( prototype.getDependencies(), dependencyComparator ); + toHash.setDependencies( prototype.getDependencies() ); + + PluginManagement pluginManagement = prototype.getBuild().getPluginManagement(); + pluginManagement.setPlugins( normalizePlugins( prototype.getBuild().getPluginManagement().getPlugins() ) ); + + List plugins = normalizePlugins( prototype.getBuild().getPlugins() ); + + Build build = new Build(); + build.setPluginManagement( pluginManagement ); + build.setPlugins( plugins ); + + toHash.setBuild( build ); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + Writer writer = null; + try + { + writer = WriterFactory.newXmlWriter( output ); + new MavenXpp3Writer().write( writer, toHash ); + + //normalize env specifics + final String[] searchList = {baseDirPath.toString(), "\\", "windows", "linux"}; + final String[] replacementList = {"", "/", "os.classifier", "os.classifier"}; + + return replaceEachRepeatedly( output.toString(), searchList, replacementList ); + + } + finally + { + IOUtil.close( writer ); + } + } + + private SortedSet getInputFiles() + { + long start = System.currentTimeMillis(); + HashSet visitedDirs = new HashSet<>(); + ArrayList collectedFiles = new ArrayList<>(); + + Build build = project.getBuild(); + + final boolean recursive = true; + startWalk( Paths.get( build.getSourceDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs ); + for ( Resource resource : build.getResources() ) + { + startWalk( Paths.get( resource.getDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs ); + } + + startWalk( Paths.get( build.getTestSourceDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs ); + for ( Resource testResource : build.getTestResources() ) + { + startWalk( Paths.get( testResource.getDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs ); + } + + Properties properties = project.getProperties(); + for ( String name : properties.stringPropertyNames() ) + { + if ( name.startsWith( CACHE_INPUT_NAME ) ) + { + String path = properties.getProperty( name ); + startWalk( Paths.get( path ), dirGlob, recursive, collectedFiles, visitedDirs ); + } + } + + List includes = config.getGlobalIncludePaths(); + for ( Include include : includes ) + { + final String path = include.getValue(); + final String glob = defaultIfEmpty( include.getGlob(), dirGlob ); + startWalk( Paths.get( path ), glob, include.isRecursive(), collectedFiles, visitedDirs ); + } + + long walkKnownPathsFinished = System.currentTimeMillis() - start; + + String message = processPlugins ? "enabled, values will be checked for presence in file system" : "disabled, " + + "only tags with attribute " + CACHE_INPUT_NAME + "=\"true\" will be added"; + logInfo( "Scanning plugins configurations to find input files. Probing is " + message ); + + if ( processPlugins ) + { + collectFromPlugins( collectedFiles, visitedDirs ); + } + else + { + logInfo( "Skipping check plugins scan (probing is disabled by config)" ); + } + + long pluginsFinished = System.currentTimeMillis() - start - walkKnownPathsFinished; + + TreeSet sorted = new TreeSet<>( fileComparator ); + for ( Path collectedFile : collectedFiles ) + { + sorted.add( collectedFile.normalize().toAbsolutePath() ); + } + + logInfo( + "Found " + sorted.size() + " input files. Project dir processing: " + walkKnownPathsFinished + + ", plugins: " + pluginsFinished + " millis" ); + if ( logger.isDebugEnabled() ) + { + logDebug( "Src input: " + sorted ); + } + + return sorted; + } + + /** + * entry point for directory walk + */ + private void startWalk( Path candidate, + String glob, + boolean recursive, + List collectedFiles, + Set visitedDirs ) + { + + Path normalized = candidate.isAbsolute() ? candidate : baseDirPath.resolve( candidate ); + normalized = normalized.toAbsolutePath().normalize(); + WalkKey key = new WalkKey( normalized, glob, recursive ); + if ( visitedDirs.contains( key ) || !Files.exists( normalized ) ) + { + return; + } + + if ( Files.isDirectory( normalized ) ) + { + if ( baseDirPath.startsWith( normalized ) ) + { // requested to walk parent, can do only non recursive + key = new WalkKey( normalized, glob, false ); + } + try + { + walkDir( key, collectedFiles, visitedDirs ); + visitedDirs.add( key ); + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + } + else + { + if ( !isFilteredOutSubpath( normalized ) ) + { + if ( logger.isDebugEnabled() ) + { + logDebug( "Adding: " + normalized ); + } + collectedFiles.add( normalized ); + } + } + } + + private Path normalizedPath( String directory ) + { + return Paths.get( directory ).normalize(); + } + + private void collectFromPlugins( List files, HashSet visitedDirs ) + { + + List plugins = project.getBuild().getPlugins(); + for ( Plugin plugin : plugins ) + { + + PluginScanConfig scanConfig = config.getPluginDirScanConfig( plugin ); + + if ( scanConfig.isSkip() ) + { + logDebug( "Skipping plugin config scan (skip by config): " + plugin.getArtifactId() ); + continue; + } + + Xpp3Dom configuration = (Xpp3Dom) plugin.getConfiguration(); + logDebug( "Processing plugin config: " + plugin.getArtifactId() ); + if ( configuration != null ) + { + addInputsFromPluginConfigs( configuration.getChildren(), scanConfig, files, visitedDirs ); + } + + for ( PluginExecution exec : plugin.getExecutions() ) + { + + final PluginScanConfig executionScanConfig = config.getExecutionDirScanConfig( plugin, exec ); + PluginScanConfig mergedConfig = scanConfig.mergeWith( executionScanConfig ); + + if ( mergedConfig.isSkip() ) + { + logDebug( + "Skipping plugin execution config scan (skip by config): " + + plugin.getArtifactId() + ", execId: " + exec.getId() ); + continue; + } + + Xpp3Dom execConfiguration = (Xpp3Dom) exec.getConfiguration(); + logDebug( "Processing plugin: " + plugin.getArtifactId() + ", execution: " + exec.getId() ); + + if ( execConfiguration != null ) + { + addInputsFromPluginConfigs( execConfiguration.getChildren(), mergedConfig, files, visitedDirs ); + } + } + } + } + + private Path walkDir( final WalkKey key, + final List collectedFiles, + final Set visitedDirs ) throws IOException + { + return Files.walkFileTree( key.getPath(), new SimpleFileVisitor() + { + @Override + public FileVisitResult preVisitDirectory( Path path, + BasicFileAttributes basicFileAttributes ) throws IOException + { + WalkKey currentDirKey = new WalkKey( path.toAbsolutePath().normalize(), key.getGlob(), + key.isRecursive() ); + if ( isHidden( path ) ) + { + if ( logger.isDebugEnabled() ) + { + logDebug( "Skipping subtree (hidden): " + path ); + } + return FileVisitResult.SKIP_SUBTREE; + } + else if ( isFilteredOutSubpath( path ) ) + { + if ( logger.isDebugEnabled() ) + { + logDebug( "Skipping subtree (blacklisted): " + path ); + } + return FileVisitResult.SKIP_SUBTREE; + } + else if ( visitedDirs.contains( currentDirKey ) ) + { + if ( logger.isDebugEnabled() ) + { + logDebug( "Skipping subtree (visited): " + path ); + } + return FileVisitResult.SKIP_SUBTREE; + } + + walkDirectoryFiles( path, collectedFiles, key.getGlob() ); + + if ( !key.isRecursive() ) + { + if ( logger.isDebugEnabled() ) + { + logDebug( "Skipping subtree (non recursive): " + path ); + } + return FileVisitResult.SKIP_SUBTREE; + } + + if ( logger.isDebugEnabled() ) + { + logDebug( "Visiting subtree: " + path ); + } + return FileVisitResult.CONTINUE; + } + } ); + } + + private void addInputsFromPluginConfigs( Xpp3Dom[] configurationChildren, + PluginScanConfig scanConfig, + List files, HashSet visitedDirs ) + { + + if ( configurationChildren == null ) + { + return; + } + + for ( Xpp3Dom configChild : configurationChildren ) + { + + String tagValue = configChild.getValue(); + String tagName = configChild.getName(); + + if ( !scanConfig.accept( tagName ) ) + { + logDebug( "Skipping property (scan config)): " + tagName + ", value: " + stripToEmpty( tagValue ) ); + continue; + } + + logDebug( "Checking xml tag. Tag: " + tagName + ", value: " + stripToEmpty( tagValue ) ); + + addInputsFromPluginConfigs( configChild.getChildren(), scanConfig, files, visitedDirs ); + + final ScanConfigProperties propertyConfig = scanConfig.getTagScanProperties( tagName ); + final String glob = defaultIfEmpty( propertyConfig.getGlob(), dirGlob ); + if ( "true".equals( configChild.getAttribute( CACHE_INPUT_NAME ) ) ) + { + logInfo( + "Found tag marked with " + CACHE_INPUT_NAME + " attribute. Tag: " + tagName + + ", value: " + tagValue ); + startWalk( Paths.get( tagValue ), glob, propertyConfig.isRecursive(), files, visitedDirs ); + } + else + { + final Path candidate = getPathOrNull( tagValue ); + if ( candidate != null ) + { + startWalk( candidate, glob, propertyConfig.isRecursive(), files, visitedDirs ); + if ( "descriptorRef".equals( tagName ) ) + { // hardcoded logic for assembly plugin which could reference files omitting .xml suffix + startWalk( Paths.get( tagValue + ".xml" ), glob, propertyConfig.isRecursive(), files, + visitedDirs ); + } + } + } + } + } + + private Path getPathOrNull( String text ) + { + // small optimization to not probe not-paths + boolean blacklisted = isBlank( text ) + || equalsAnyIgnoreCase( text, "true", "false", "utf-8", "null", "\\" ) // common values + || contains( text, "*" ) // tag value is a glob or regex - unclear how to process + || ( contains( text, ":" ) && !contains( text, ":\\" ) )// artifactId + || startsWithAny( text, "com.", "org.", "io.", "java.", "javax." ) // java packages + || startsWithAny( text, "${env." ) // env variables in maven notation + || startsWithAny( text, "http:", "https:", "scm:", "ssh:", "git:", "svn:", "cp:", + "classpath:" ); // urls identified by common protocols + if ( !blacklisted ) + { + try + { + return Paths.get( text ); + } + catch ( Exception ignore ) + { + } + } + if ( logger.isDebugEnabled() ) + { + logDebug( text + ( blacklisted ? ": skipped(blacklisted literal)" : ": invalid path" ) ); + } + return null; + } + + private void logDebug( String message ) + { + if ( logger.isDebugEnabled() ) + { + logger.debug( "[CACHE][" + project.getArtifactId() + "] " + message ); + } + } + + private void logInfo( String message ) + { + if ( logger.isInfoEnabled() ) + { + logger.info( "[CACHE][" + project.getArtifactId() + "] " + message ); + } + } + + static void walkDirectoryFiles( Path dir, List collectedFiles, String glob ) + { + if ( !Files.isDirectory( dir ) ) + { + return; + } + + try + { + try ( DirectoryStream stream = Files.newDirectoryStream( dir, glob ) ) + { + for ( Path entry : stream ) + { + File file = entry.toFile(); + if ( file.isFile() && !isHidden( entry ) ) + { + collectedFiles.add( entry ); + } + } + } + } + catch ( IOException e ) + { + throw new RuntimeException( "Cannot process directory: " + dir, e ); + } + } + + private static boolean isHidden( Path entry ) throws IOException + { + return Files.isHidden( entry ) || entry.toFile().getName().startsWith( "." ); + } + + private boolean isFilteredOutSubpath( Path path ) + { + Path normalized = path.normalize(); + for ( Path filteredOutDir : filteredOutPaths ) + { + if ( normalized.startsWith( filteredOutDir ) ) + { + return true; + } + } + return false; + } + + private SortedMap getMutableDependencies() throws IOException + { + MultimoduleDiscoveryStrategy strategy = config.getMultimoduleDiscoveryStrategy(); + SortedMap result = new TreeMap<>(); + + for ( Dependency dependency : project.getDependencies() ) + { + // saved to index by the end of dependency build + final boolean currentlyBuilding = isBuilding( dependency, projectIndex ); + final boolean partOfMultiModule = strategy.isPartOfMultiModule( dependency ); + if ( !currentlyBuilding && !partOfMultiModule && !isSnapshot( dependency.getVersion() ) ) + { + // external immutable dependency, should skip + continue; + } + + if ( ProjectUtils.isPom( dependency ) ) + { + // POM dependency will be resolved by maven system to actual dependencies + // and will contribute to effective pom. + // Effective result will be recorded by #getNormalizedPom + // so pom dependencies must be skipped as meaningless by themselves + continue; + } + + final Artifact dependencyArtifact = repoSystem.createDependencyArtifact( dependency ); + final String artifactKey = KeyUtils.getArtifactKey( dependencyArtifact ); + DigestItemType resolved = null; + if ( currentlyBuilding ) + { + resolved = projectArtifactsByKey.get( artifactKey ); + if ( resolved == null ) + { + throw new DependencyNotResolvedException( "Expected dependency not resolved: " + dependency ); + } + } + else + { + if ( partOfMultiModule ) + { + // TODO lookup in remote cache is not necessary for abfx, for versioned projects - make sense + final Optional bestMatchResult = localCache.findBestMatchingBuild( session, dependency ); + if ( bestMatchResult.isPresent() ) + { + final BuildInfo bestMatched = bestMatchResult.get(); + resolved = bestMatched.findArtifact( dependency ); + } + } + if ( resolved == null && !ProjectUtils.isPom( dependency ) ) + { + try + { + resolved = resolveArtifact( dependencyArtifact, strategy ); + } + catch ( Exception e ) + { + throw new RuntimeException( "Cannot resolve dependency " + dependency, e ); + } + } + } + result.put( artifactKey, resolved ); + } + return result; + } + + @Nonnull + private DigestItemType resolveArtifact( final Artifact dependencyArtifact, + MultimoduleDiscoveryStrategy strategy ) throws IOException + { + + ArtifactResolutionRequest request = new ArtifactResolutionRequest().setArtifact( + dependencyArtifact ).setResolveRoot( true ).setResolveTransitively( false ).setLocalRepository( + session.getLocalRepository() ).setRemoteRepositories( + project.getRemoteArtifactRepositories() ).setOffline( + session.isOffline() || !strategy.isLookupRemoteMavenRepo( dependencyArtifact ) ).setForceUpdate( + session.getRequest().isUpdateSnapshots() ).setServers( session.getRequest().getServers() ).setMirrors( + session.getRequest().getMirrors() ).setProxies( session.getRequest().getProxies() ); + + final ArtifactResolutionResult result = repoSystem.resolve( request ); + + if ( !result.isSuccess() ) + { + throw new DependencyNotResolvedException( "Cannot resolve in-project dependency: " + dependencyArtifact ); + } + + if ( !result.getMissingArtifacts().isEmpty() ) + { + throw new DependencyNotResolvedException( + "Cannot resolve artifact: " + dependencyArtifact + ", missing: " + result.getMissingArtifacts() ); + } + + if ( result.getArtifacts().size() > 1 ) + { + throw new IllegalStateException( + "Unexpected number of artifacts returned. Requested: " + dependencyArtifact + + ", expected: 1, actual: " + result.getArtifacts() ); + } + + final Artifact resolved = Iterables.getOnlyElement( result.getArtifacts() ); + + final HashAlgorithm algorithm = config.getHashFactory().createAlgorithm(); + final String hash = algorithm.hash( resolved.getFile().toPath() ); + return DtoUtils.createDigestedFile( resolved, hash ); + } + + /** + * PathIgnoringCaseComparator + */ + public static class PathIgnoringCaseComparator implements Comparator + { + @Override + public int compare( Path f1, Path f2 ) + { + String s1 = f1.toAbsolutePath().toString(); + String s2 = f2.toAbsolutePath().toString(); + if ( File.separator.equals( "\\" ) ) + { + s1 = s1.replaceAll( "\\\\", "/" ); + s2 = s2.replaceAll( "\\\\", "/" ); + } + return s1.compareToIgnoreCase( s2 ); + } + } + + /** + * DependencyComparator + */ + public static class DependencyComparator implements Comparator + { + @Override + public int compare( Dependency d1, Dependency d2 ) + { + return d1.getArtifactId().compareTo( d2.getArtifactId() ); + } + } + + private List normalizePlugins( List plugins ) + { + for ( Plugin plugin : plugins ) + { + List excludeProperties = config.getEffectivePomExcludeProperties( plugin ); + removeBlacklistedAttributes( (Xpp3Dom) plugin.getConfiguration(), excludeProperties ); + for ( PluginExecution execution : plugin.getExecutions() ) + { + Xpp3Dom config = (Xpp3Dom) execution.getConfiguration(); + removeBlacklistedAttributes( config, excludeProperties ); + } + Collections.sort( plugin.getDependencies(), dependencyComparator ); + } + return plugins; + } + + private void removeBlacklistedAttributes( Xpp3Dom node, List excludeProperties ) + { + if ( node == null ) + { + return; + } + + Xpp3Dom[] children = node.getChildren(); + for ( int i = 0; i < children.length; i++ ) + { + Xpp3Dom child = children[i]; + if ( excludeProperties.contains( child.getName() ) ) + { + node.removeChild( i ); + continue; + } + removeBlacklistedAttributes( child, excludeProperties ); + } + } + + public Logger getLogger() + { + return logger; + } + + public CacheConfig getConfig() + { + return config; + } + + public MavenSession getSession() + { + return session; + } + + public MavenProject getProject() + { + return project; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/MultimoduleDiscoveryStrategy.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/MultimoduleDiscoveryStrategy.java new file mode 100644 index 000000000000..b968f7764dca --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/MultimoduleDiscoveryStrategy.java @@ -0,0 +1,33 @@ +package org.apache.maven.caching.checksum; + +/* + * 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.maven.artifact.Artifact; +import org.apache.maven.model.Dependency; + +/** + * MultimoduleDiscoveryStrategy + */ +public interface MultimoduleDiscoveryStrategy +{ + boolean isPartOfMultiModule( Dependency dependency ); + + boolean isLookupRemoteMavenRepo( Artifact dependency ); +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/WalkKey.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/WalkKey.java new file mode 100644 index 000000000000..0d6f274df5b6 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/WalkKey.java @@ -0,0 +1,95 @@ +package org.apache.maven.caching.checksum; + +/* + * 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.nio.file.Path; + +/** + * WalkKey + */ +public class WalkKey +{ + private final Path normalized; + private final String glob; + private final boolean recursive; + + public WalkKey( Path normalized, String glob, boolean recursive ) + { + + this.normalized = normalized; + this.glob = glob; + this.recursive = recursive; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + + WalkKey key = (WalkKey) o; + + if ( recursive != key.recursive ) + { + return false; + } + if ( !normalized.equals( key.normalized ) ) + { + return false; + } + return glob.equals( key.glob ); + } + + @Override + public int hashCode() + { + int result = normalized.hashCode(); + result = 31 * result + glob.hashCode(); + result = 31 * result + ( recursive ? 1 : 0 ); + return result; + } + + public Path getPath() + { + return normalized; + } + + public String getGlob() + { + return glob; + } + + public boolean isRecursive() + { + return recursive; + } + + @Override + public String toString() + { + return "WalkKey{" + "normalized=" + normalized + ", glob='" + glob + '\'' + ", recursive=" + recursive + '}'; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/CloseableBuffer.java b/maven-core/src/main/java/org/apache/maven/caching/hash/CloseableBuffer.java new file mode 100644 index 000000000000..0fec3646ffa4 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/CloseableBuffer.java @@ -0,0 +1,185 @@ +package org.apache.maven.caching.hash; + +/* + * 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.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.security.PrivilegedAction; + +import static java.security.AccessController.doPrivileged; +import static org.apache.maven.caching.hash.ReflectionUtils.getField; +import static org.apache.maven.caching.hash.ReflectionUtils.getMethod; + +/** + * CloseableBuffer https://stackoverflow.com/a/54046774 + */ +public class CloseableBuffer implements AutoCloseable +{ + /* Java 8 + private static final Cleaner CLEANER = doPrivileged((PrivilegedAction) () -> { + final boolean isOldJava = System.getProperty("java.specification.version", "9").startsWith("1."); + if (isOldJava) { + return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner(); + } else { + return UnsafeCleaner.isSupported() ? new UnsafeCleaner() : new NoopCleaner(); + } + }); + */ + private static final Cleaner CLEANER = doPrivileged( new PrivilegedAction() + { + @Override + public Cleaner run() + { + return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner(); + } + } ); + + public static CloseableBuffer directBuffer( int capacity ) + { + return new CloseableBuffer( ByteBuffer.allocateDirect( capacity ) ); + } + + public static CloseableBuffer mappedBuffer( FileChannel channel, MapMode mode ) throws IOException + { + return new CloseableBuffer( channel.map( mode, 0, channel.size() ) ); + } + + private ByteBuffer buffer; + + /** + * Unmap only DirectByteBuffer and MappedByteBuffer + */ + private CloseableBuffer( ByteBuffer buffer ) + { + // Java 8: buffer.isDirect() + this.buffer = buffer; + } + + /** + * Do not use buffer after close + */ + public ByteBuffer getBuffer() + { + return buffer; + } + + @Override + public void close() + { + // Java 8: () -> CLEANER.clean(buffer) + boolean done = doPrivileged( new PrivilegedAction() + { + @Override + public Boolean run() + { + return CLEANER.clean( buffer ); + } + } ); + if ( done ) + { + buffer = null; + } + } + + // Java 8: @FunctionalInterface + private interface Cleaner + { + boolean clean( ByteBuffer buffer ); + } + + private static class NoopCleaner implements Cleaner + { + @Override + public boolean clean( ByteBuffer buffer ) + { + return false; + } + } + + private static class DirectCleaner implements Cleaner + { + private static final Method ATTACHMENT = getMethod( "sun.nio.ch.DirectBuffer", + "attachment" ); + private static final Method CLEANER = getMethod( "sun.nio.ch.DirectBuffer", "cleaner" ); + private static final Method CLEAN = getMethod( "sun.misc.Cleaner", "clean" ); + + public static boolean isSupported() + { + return ATTACHMENT != null && CLEAN != null && CLEANER != null; + } + + /** + * Make sure duplicates and slices are not cleaned, since this can result in duplicate attempts to clean the + * same buffer, which trigger a crash with: "A fatal error has been detected by the Java Runtime Environment: + * EXCEPTION_ACCESS_VIOLATION" See: https://stackoverflow.com/a/31592947/3950982 + */ + @Override + public boolean clean( ByteBuffer buffer ) + { + try + { + if ( ATTACHMENT.invoke( buffer ) == null ) + { + CLEAN.invoke( CLEANER.invoke( buffer ) ); + return true; + } + } + catch ( Exception ignore ) + { + } + return false; + } + } + + private static class UnsafeCleaner implements Cleaner + { + // Java 9: getMethod("jdk.internal.misc.Unsafe", "invokeCleaner", ByteBuffer.class); + private static final Method INVOKE_CLEANER = getMethod( "sun.misc.Unsafe", "invokeCleaner", ByteBuffer.class ); + private static final Object UNSAFE = getField( "sun.misc.Unsafe", "theUnsafe" ); + + public static boolean isSupported() + { + return UNSAFE != null && INVOKE_CLEANER != null; + } + + /** + * Calling the above code in JDK9+ gives a reflection warning on stderr, + * Unsafe.theUnsafe.invokeCleaner(byteBuffer) + * makes the same call, but does not print the reflection warning + */ + @Override + public boolean clean( ByteBuffer buffer ) + { + try + { + // throws IllegalArgumentException if buffer is a duplicate or slice + INVOKE_CLEANER.invoke( UNSAFE, buffer ); + return true; + } + catch ( Exception ignore ) + { + } + return false; + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/Hash.java b/maven-core/src/main/java/org/apache/maven/caching/hash/Hash.java new file mode 100644 index 000000000000..82220634cc6b --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/Hash.java @@ -0,0 +1,61 @@ +package org.apache.maven.caching.hash; + +/* + * 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.nio.file.Path; + +/** + * Hash + */ +public class Hash +{ + /** + * Algorithm + */ + public interface Algorithm + { + byte[] hash( byte[] array ); + + byte[] hash( Path path ) throws IOException; + } + + /** + * accumulates states and should be completed by {@link #digest()} + */ + public interface Checksum + { + void update( byte[] hash ); + + byte[] digest(); + } + + /** + * Factory + */ + public interface Factory + { + String getAlgorithm(); + + Algorithm algorithm(); + + Checksum checksum( int count ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HashAlgorithm.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HashAlgorithm.java new file mode 100644 index 000000000000..ec6ad68a053d --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HashAlgorithm.java @@ -0,0 +1,46 @@ +package org.apache.maven.caching.hash; + +/* + * 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.nio.file.Path; + +/** + * HashAlgorithm + */ +public class HashAlgorithm +{ + private final Hash.Algorithm algorithm; + + HashAlgorithm( Hash.Algorithm algorithm ) + { + this.algorithm = algorithm; + } + + public String hash( Path path ) throws IOException + { + return HexUtils.encode( algorithm.hash( path ) ); + } + + public String hash( byte[] bytes ) + { + return HexUtils.encode( algorithm.hash( bytes ) ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HashChecksum.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HashChecksum.java new file mode 100644 index 000000000000..904077a6abae --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HashChecksum.java @@ -0,0 +1,67 @@ +package org.apache.maven.caching.hash; + +/* + * 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.nio.file.Path; + +/** + * HashChecksum + */ +public class HashChecksum +{ + private final Hash.Algorithm algorithm; + private final Hash.Checksum checksum; + + HashChecksum( Hash.Algorithm algorithm, Hash.Checksum checksum ) + { + this.algorithm = algorithm; + this.checksum = checksum; + } + + public String update( Path path ) throws IOException + { + return updateAndEncode( algorithm.hash( path ) ); + } + + public String update( byte[] bytes ) + { + return updateAndEncode( algorithm.hash( bytes ) ); + } + + /** + * @param hexHash hash value in hex format. This method doesn't accept generic text - could result in error + */ + public String update( String hexHash ) + { + return updateAndEncode( HexUtils.decode( hexHash ) ); + } + + private String updateAndEncode( byte[] hash ) + { + checksum.update( hash ); + return HexUtils.encode( hash ); + } + + public String digest() + { + return HexUtils.encode( checksum.digest() ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HashFactory.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HashFactory.java new file mode 100644 index 000000000000..d610fceab924 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HashFactory.java @@ -0,0 +1,79 @@ +package org.apache.maven.caching.hash; + +/* + * 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.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +/** + * HashFactory + */ +public enum HashFactory +{ + SHA1( new SHA( "SHA-1" ) ), + SHA256( new SHA( "SHA-256" ) ), + SHA384( new SHA( "SHA-384" ) ), + SHA512( new SHA( "SHA-512" ) ), + XX( new XX() ), + XXMM( new XXMM() ); + + private static final Map LOOKUP = new HashMap<>(); + + static + { + for ( HashFactory factory : HashFactory.values() ) + { + LOOKUP.put( factory.getAlgorithm(), factory ); + } + } + + public static HashFactory of( String algorithm ) throws NoSuchAlgorithmException + { + final HashFactory factory = LOOKUP.get( algorithm ); + if ( factory == null ) + { + throw new NoSuchAlgorithmException( algorithm ); + } + return factory; + } + + private final Hash.Factory factory; + + HashFactory( Hash.Factory factory ) + { + this.factory = factory; + } + + public String getAlgorithm() + { + return factory.getAlgorithm(); + } + + public HashAlgorithm createAlgorithm() + { + return new HashAlgorithm( factory.algorithm() ); + } + + public HashChecksum createChecksum( int count ) + { + return new HashChecksum( factory.algorithm(), factory.checksum( count ) ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HexUtils.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HexUtils.java new file mode 100644 index 000000000000..f85d0f9c515c --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HexUtils.java @@ -0,0 +1,56 @@ +package org.apache.maven.caching.hash; + +/* + * 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 com.google.common.base.CharMatcher; +import com.google.common.io.BaseEncoding; + +/** + * HexUtils + */ +public class HexUtils +{ + + private static final BaseEncoding ENCODING = BaseEncoding.base16().lowerCase(); + private static final CharMatcher MATCHER = CharMatcher.is( '0' ); + + public static String encode( byte[] hash ) + { + return trimLeadingZero( ENCODING.encode( hash ) ); + } + + public static byte[] decode( String hex ) + { + return ENCODING.decode( padLeadingZero( hex ) ); + } + + private static String trimLeadingZero( String hex ) + { + String value = MATCHER.trimLeadingFrom( hex ); + return value.isEmpty() ? "0" : value; + } + + private static String padLeadingZero( String hex ) + { + String value = hex.toLowerCase(); + return value.length() % 2 == 0 ? value : "0" + value; + } + +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/ReflectionUtils.java b/maven-core/src/main/java/org/apache/maven/caching/hash/ReflectionUtils.java new file mode 100644 index 000000000000..259e6f0b7feb --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/ReflectionUtils.java @@ -0,0 +1,61 @@ +package org.apache.maven.caching.hash; + +/* + * 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.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * ReflectionUtils + */ +class ReflectionUtils +{ + static Method getMethod( String className, String methodName, Class... parameterTypes ) + { + try + { + final Method method = Class.forName( className ).getMethod( methodName, parameterTypes ); + method.setAccessible( true ); + return method; + } + catch ( Exception ignore ) + { + return null; + } + } + + static Object getField( String className, String fieldName ) + { + try + { + final Field field = Class.forName( className ).getDeclaredField( fieldName ); + field.setAccessible( true ); + return field.get( null ); + } + catch ( Exception ignore ) + { + return null; + } + } + + private ReflectionUtils() + { + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/SHA.java b/maven-core/src/main/java/org/apache/maven/caching/hash/SHA.java new file mode 100644 index 000000000000..e02dfe2f25e2 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/SHA.java @@ -0,0 +1,103 @@ +package org.apache.maven.caching.hash; + +/* + * 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.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; + +/** + * SHA + */ +public class SHA implements Hash.Factory +{ + private static final ThreadLocal ALGORITHM = new ThreadLocal<>(); + private static final ThreadLocal CHECKSUM = new ThreadLocal<>(); + + private final String algorithm; + + SHA( String algorithm ) + { + this.algorithm = algorithm; + } + + @Override + public String getAlgorithm() + { + return algorithm; + } + + @Override + public Hash.Algorithm algorithm() + { + return new SHA.Algorithm( ThreadLocalDigest.get( ALGORITHM, algorithm ) ); + } + + @Override + public Hash.Checksum checksum( int count ) + { + return new SHA.Checksum( ThreadLocalDigest.get( CHECKSUM, algorithm ) ); + } + + private static class Algorithm implements Hash.Algorithm + { + private final MessageDigest digest; + + private Algorithm( MessageDigest digest ) + { + this.digest = digest; + } + + @Override + public byte[] hash( byte[] array ) + { + return digest.digest( array ); + } + + @Override + public byte[] hash( Path path ) throws IOException + { + return hash( Files.readAllBytes( path ) ); + } + } + + private static class Checksum implements Hash.Checksum + { + private final MessageDigest digest; + + private Checksum( MessageDigest digest ) + { + this.digest = digest; + } + + @Override + public void update( byte[] hash ) + { + digest.update( hash ); + } + + @Override + public byte[] digest() + { + return digest.digest(); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalBuffer.java b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalBuffer.java new file mode 100644 index 000000000000..6d9f9d6269b7 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalBuffer.java @@ -0,0 +1,86 @@ +package org.apache.maven.caching.hash; + +/* + * 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.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * ThreadLocalBuffer + */ +public class ThreadLocalBuffer +{ + private static final ConcurrentMap LOCALS = new ConcurrentHashMap<>(); + + public static ByteBuffer get( ThreadLocal local, int capacity ) + { + final CloseableBuffer buffer = local.get(); + if ( buffer == null ) + { + return create( local, capacity ); + } + + if ( capacity( buffer ) < capacity ) + { + close( buffer ); + return create( local, capacity * 2 ); + } + + return clear( buffer ); + } + + @Override + public void finalize() + { + for ( CloseableBuffer buffer : LOCALS.keySet() ) + { + buffer.close(); + } + } + + private static ByteBuffer create( ThreadLocal local, int capacity ) + { + final CloseableBuffer buffer = CloseableBuffer.directBuffer( capacity ); + local.set( buffer ); + LOCALS.put( buffer, false ); + return buffer.getBuffer(); + } + + private static int capacity( CloseableBuffer buffer ) + { + return buffer.getBuffer().capacity(); + } + + private static ByteBuffer clear( CloseableBuffer buffer ) + { + return (ByteBuffer) buffer.getBuffer().clear(); + } + + private static void close( CloseableBuffer buffer ) + { + LOCALS.remove( buffer ); + buffer.close(); + } + + private ThreadLocalBuffer() + { + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalDigest.java b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalDigest.java new file mode 100644 index 000000000000..2df800e26f7a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalDigest.java @@ -0,0 +1,71 @@ +package org.apache.maven.caching.hash; + +/* + * 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.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; + +/** + * ThreadLocalDigest + */ +public class ThreadLocalDigest +{ + public static MessageDigest get( ThreadLocal local, String algorithm ) + { + final MessageDigest digest = local.get(); + if ( digest == null ) + { + return create( local, algorithm ); + } + + if ( Objects.equals( digest.getAlgorithm(), algorithm ) ) + { + return reset( digest ); + } + + reset( digest ); + return create( local, algorithm ); + } + + private static MessageDigest create( ThreadLocal local, String algorithm ) + { + try + { + final MessageDigest digest = MessageDigest.getInstance( algorithm ); + local.set( digest ); + return digest; + } + catch ( NoSuchAlgorithmException e ) + { + throw new RuntimeException( "Cannot create message digest with algorithm: " + algorithm, e ); + } + } + + private static MessageDigest reset( MessageDigest digest ) + { + digest.reset(); + return digest; + } + + private ThreadLocalDigest() + { + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/XX.java b/maven-core/src/main/java/org/apache/maven/caching/hash/XX.java new file mode 100644 index 000000000000..24f9f4bff5e7 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/XX.java @@ -0,0 +1,98 @@ +package org.apache.maven.caching.hash; + +/* + * 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 net.openhft.hashing.LongHashFunction; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; + +import static com.google.common.primitives.Longs.toByteArray; + +/** + * XX + */ +public class XX implements Hash.Factory +{ + static final LongHashFunction INSTANCE = LongHashFunction.xx(); + + @Override + public String getAlgorithm() + { + return "XX"; + } + + @Override + public Hash.Algorithm algorithm() + { + return new XX.Algorithm(); + } + + @Override + public Hash.Checksum checksum( int count ) + { + return new XX.Checksum( ByteBuffer.allocate( capacity( count ) ) ); + } + + static int capacity( int count ) + { + // Java 8: Long.BYTES + return count * Long.SIZE / Byte.SIZE; + } + + static class Algorithm implements Hash.Algorithm + { + @Override + public byte[] hash( byte[] array ) + { + return toByteArray( INSTANCE.hashBytes( array ) ); + } + + @Override + public byte[] hash( Path path ) throws IOException + { + return hash( Files.readAllBytes( path ) ); + } + } + + static class Checksum implements Hash.Checksum + { + private final ByteBuffer buffer; + + Checksum( ByteBuffer buffer ) + { + this.buffer = buffer; + } + + @Override + public void update( byte[] hash ) + { + buffer.put( hash ); + } + + @Override + public byte[] digest() + { + return toByteArray( INSTANCE.hashBytes( buffer, 0, buffer.position() ) ); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/XXMM.java b/maven-core/src/main/java/org/apache/maven/caching/hash/XXMM.java new file mode 100644 index 000000000000..8e09299c247e --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/hash/XXMM.java @@ -0,0 +1,67 @@ +package org.apache.maven.caching.hash; + +/* + * 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.nio.channels.FileChannel; +import java.nio.file.Path; + +import static com.google.common.primitives.Longs.toByteArray; +import static java.nio.channels.FileChannel.MapMode.READ_ONLY; +import static java.nio.file.StandardOpenOption.READ; + +/** + * XXMM + */ +public class XXMM implements Hash.Factory +{ + private static final ThreadLocal BUFFER = new ThreadLocal<>(); + + @Override + public String getAlgorithm() + { + return "XXMM"; + } + + @Override + public Hash.Algorithm algorithm() + { + return new Algorithm(); + } + + @Override + public Hash.Checksum checksum( int count ) + { + return new XX.Checksum( ThreadLocalBuffer.get( BUFFER, XX.capacity( count ) ) ); + } + + private static class Algorithm extends XX.Algorithm + { + @Override + public byte[] hash( Path path ) throws IOException + { + try ( FileChannel channel = FileChannel.open( path, + READ ); CloseableBuffer buffer = CloseableBuffer.mappedBuffer( channel, READ_ONLY ) ) + { + return toByteArray( XX.INSTANCE.hashBytes( buffer.getBuffer() ) ); + } + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/AllExternalSrategy.java b/maven-core/src/main/java/org/apache/maven/caching/xml/AllExternalSrategy.java new file mode 100644 index 000000000000..00c1d4d84ac8 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/AllExternalSrategy.java @@ -0,0 +1,42 @@ +package org.apache.maven.caching.xml; + +/* + * 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.maven.artifact.Artifact; +import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy; +import org.apache.maven.model.Dependency; + +/** + * AllExternalSrategy + */ +public class AllExternalSrategy implements MultimoduleDiscoveryStrategy +{ + @Override + public boolean isPartOfMultiModule( Dependency dependency ) + { + return false; + } + + @Override + public boolean isLookupRemoteMavenRepo( Artifact artifact ) + { + return true; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/BuildInfo.java b/maven-core/src/main/java/org/apache/maven/caching/xml/BuildInfo.java new file mode 100644 index 000000000000..0b61bfd89d74 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/BuildInfo.java @@ -0,0 +1,273 @@ +package org.apache.maven.caching.xml; + +/* + * 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 com.google.common.collect.Iterables; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.caching.ProjectUtils; +import org.apache.maven.caching.checksum.MavenProjectInput; +import org.apache.maven.caching.hash.HashAlgorithm; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.caching.jaxb.CompletedExecutionType; +import org.apache.maven.caching.jaxb.DigestItemType; +import org.apache.maven.caching.jaxb.ProjectsInputInfoType; +import org.apache.maven.model.Dependency; +import org.apache.maven.plugin.MojoExecution; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.util.StringUtils; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Objects; + +import static org.apache.maven.caching.ProjectUtils.isLaterPhase; +import static org.apache.maven.caching.ProjectUtils.mojoExecutionKey; + +/** + * BuildInfo + */ +public class BuildInfo +{ + + final BuildInfoType dto; + CacheSource source; + + public BuildInfo( List goals, + ArtifactType artifact, + List attachedArtifacts, + ProjectsInputInfoType projectsInputInfo, + List completedExecutions, + String hashAlgorithm ) + { + this.dto = new BuildInfoType(); + this.dto.setCacheImplementationVersion( MavenProjectInput.CACHE_IMPLMENTATION_VERSION ); + try + { + this.dto.setBuildTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( new GregorianCalendar() ) ); + } + catch ( DatatypeConfigurationException ignore ) + { + } + try + { + this.dto.setBuildServer( InetAddress.getLocalHost().getCanonicalHostName() ); + } + catch ( UnknownHostException ignore ) + { + this.dto.setBuildServer( "unknown" ); + } + this.dto.setHashFunction( hashAlgorithm ); + this.dto.setArtifact( artifact ); + this.dto.setGoals( createGoals( goals ) ); + this.dto.setAttachedArtifacts( new BuildInfoType.AttachedArtifacts() ); + this.dto.getAttachedArtifacts().getArtifact().addAll( attachedArtifacts ); + this.dto.setExecutions( createExecutions( completedExecutions ) ); + this.dto.setProjectsInputInfo( projectsInputInfo ); + this.source = CacheSource.BUILD; + } + + public CacheSource getSource() + { + return source; + } + + private BuildInfoType.Executions createExecutions( List completedExecutions ) + { + BuildInfoType.Executions executions = new BuildInfoType.Executions(); + executions.getExecution().addAll( completedExecutions ); + return executions; + } + + public BuildInfo( BuildInfoType buildInfo, CacheSource source ) + { + this.dto = buildInfo; + this.source = source; + } + + public static BuildInfoType.Goals createGoals( List list ) + { + BuildInfoType.Goals goals = new BuildInfoType.Goals(); + goals.getGoal().addAll( list ); + return goals; + } + + public static BuildInfoType.AttachedArtifacts createAttachedArtifacts( List artifacts, + HashAlgorithm algorithm ) throws IOException + { + BuildInfoType.AttachedArtifacts attachedArtifacts = new BuildInfoType.AttachedArtifacts(); + for ( Artifact artifact : artifacts ) + { + final ArtifactType dto = DtoUtils.createDto( artifact ); + if ( artifact.getFile() != null ) + { + dto.setFileHash( algorithm.hash( artifact.getFile().toPath() ) ); + } + attachedArtifacts.getArtifact().add( dto ); + } + return attachedArtifacts; + } + + public boolean isAllExecutionsPresent( List mojos, Logger logger ) + { + for ( MojoExecution mojo : mojos ) + { + // TODO for strict check we might want exact match + if ( !hasCompletedExecution( mojoExecutionKey( mojo ) ) ) + { + logger.error( "Build mojo is not cached: " + mojo ); + return false; + } + } + return true; + } + + private boolean hasCompletedExecution( String mojoExecutionKey ) + { + final List completedExecutions = dto.getExecutions().getExecution(); + for ( CompletedExecutionType completedExecution : completedExecutions ) + { + if ( StringUtils.equals( completedExecution.getExecutionKey(), mojoExecutionKey ) ) + { + return true; + } + } + return false; + } + + @Override + public String toString() + { + return "BuildInfo{" + "dto=" + dto + '}'; + } + + public CompletedExecutionType findMojoExecutionInfo( MojoExecution mojoExecution ) + { + + if ( !dto.isSetExecutions() ) + { + return null; + } + + final List executions = dto.getExecutions().getExecution(); + for ( CompletedExecutionType execution : executions ) + { + if ( StringUtils.equals( execution.getExecutionKey(), mojoExecutionKey( mojoExecution ) ) ) + { + return execution; + } + } + return null; + } + + public String getCacheImplementationVersion() + { + return dto.getCacheImplementationVersion(); + } + + public ArtifactType getArtifact() + { + return dto.getArtifact(); + } + + public List getAttachedArtifacts() + { + if ( dto.isSetAttachedArtifacts() ) + { + return dto.getAttachedArtifacts().getArtifact(); + } + return Collections.emptyList(); + } + + public BuildInfoType getDto() + { + return dto; + } + + public String getHighestCompletedGoal() + { + return Iterables.getLast( dto.getGoals().getGoal() ); + } + + public List getCachedSegment( List mojoExecutions ) + { + List list = new ArrayList<>(); + for ( MojoExecution mojoExecution : mojoExecutions ) + { + if ( !isLaterPhase( mojoExecution.getLifecyclePhase(), "post-clean" ) ) + { + continue; + } + if ( isLaterPhase( mojoExecution.getLifecyclePhase(), getHighestCompletedGoal() ) ) + { + break; + } + list.add( mojoExecution ); + } + return list; + + } + + public List getPostCachedSegment( List mojoExecutions ) + { + List list = new ArrayList<>(); + for ( MojoExecution mojoExecution : mojoExecutions ) + { + if ( isLaterPhase( mojoExecution.getLifecyclePhase(), getHighestCompletedGoal() ) ) + { + list.add( mojoExecution ); + } + } + return list; + } + + public DigestItemType findArtifact( Dependency dependency ) + { + + if ( ProjectUtils.isPom( dependency ) ) + { + throw new IllegalArgumentException( "Pom dependencies should not be treated as artifacts: " + dependency ); + } + List artifacts = new ArrayList<>( getAttachedArtifacts() ); + artifacts.add( getArtifact() ); + for ( ArtifactType artifact : artifacts ) + { + if ( isEquals( dependency, artifact ) ) + { + return DtoUtils.createdDigestedByProjectChecksum( artifact, dto.getProjectsInputInfo().getChecksum() ); + } + } + return null; + } + + private boolean isEquals( Dependency dependency, ArtifactType artifact ) + { + return Objects.equals( dependency.getGroupId(), artifact.getArtifactId() ) && Objects.equals( + dependency.getArtifactId(), artifact.getArtifactId() ) && Objects.equals( dependency.getType(), + artifact.getType() ) && Objects.equals( dependency.getClassifier(), artifact.getClassifier() ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfig.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfig.java new file mode 100644 index 000000000000..2f5137d9a3ea --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfig.java @@ -0,0 +1,108 @@ +package org.apache.maven.caching.xml; + +/* + * 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.maven.caching.PluginScanConfig; +import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy; +import org.apache.maven.caching.hash.HashFactory; +import org.apache.maven.caching.jaxb.PathSetType; +import org.apache.maven.caching.jaxb.PropertyNameType; +import org.apache.maven.caching.jaxb.TrackedPropertyType; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.regex.Pattern; + +/** + * CacheConfig + */ +public interface CacheConfig +{ + + CacheState initialize( MavenProject project, MavenSession session ); + + @Nonnull + List getTrackedProperties( MojoExecution mojoExecution ); + + boolean isLogAllProperties( MojoExecution mojoExecution ); + + @Nonnull + List getLoggedProperties( MojoExecution mojoExecution ); + + @Nonnull + List getNologProperties( MojoExecution mojoExecution ); + + @Nonnull + List getEffectivePomExcludeProperties( Plugin plugin ); + + String isProcessPlugins(); + + String getDefaultGlob(); + + @Nonnull + List getGlobalIncludePaths(); + + @Nonnull + List getGlobalExcludePaths(); + + @Nonnull + PluginScanConfig getPluginDirScanConfig( Plugin plugin ); + + @Nonnull + PluginScanConfig getExecutionDirScanConfig( Plugin plugin, PluginExecution exec ); + + @Nonnull + MultimoduleDiscoveryStrategy getMultimoduleDiscoveryStrategy(); + + @Nonnull + HashFactory getHashFactory(); + + boolean isForcedExecution( MojoExecution execution ); + + String getUrl(); + + boolean isEnabled(); + + boolean isRemoteCacheEnabled(); + + boolean isSaveToRemote(); + + boolean isSaveFinal(); + + boolean isFailFast(); + + int getMaxLocalBuildsCached(); + + List getAttachedOutputs(); + + boolean canIgnore( MojoExecution mojoExecution ); + + @Nonnull + List getExcludePatterns(); + + boolean isBaselineDiffEnabled(); + + String getBaselineCacheUrl(); +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfigImpl.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfigImpl.java new file mode 100644 index 000000000000..9dd0e85f578f --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfigImpl.java @@ -0,0 +1,579 @@ +package org.apache.maven.caching.xml; + +/* + * 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 com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.caching.DefaultPluginScanConfig; +import org.apache.maven.caching.PluginScanConfig; +import org.apache.maven.caching.PluginScanConfigImpl; +import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy; +import org.apache.maven.caching.hash.HashFactory; +import org.apache.maven.caching.jaxb.CacheType; +import org.apache.maven.caching.jaxb.ConfigurationType; +import org.apache.maven.caching.jaxb.CoordinatesBaseType; +import org.apache.maven.caching.jaxb.ExecutablesType; +import org.apache.maven.caching.jaxb.ExecutionConfigurationScanType; +import org.apache.maven.caching.jaxb.ExecutionControlType; +import org.apache.maven.caching.jaxb.ExecutionIdsListType; +import org.apache.maven.caching.jaxb.GoalReconciliationType; +import org.apache.maven.caching.jaxb.GoalsListType; +import org.apache.maven.caching.jaxb.PathSetType; +import org.apache.maven.caching.jaxb.PluginConfigurationScanType; +import org.apache.maven.caching.jaxb.PluginSetType; +import org.apache.maven.caching.jaxb.PropertyNameType; +import org.apache.maven.caching.jaxb.TrackedPropertyType; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; + +import javax.annotation.Nonnull; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import static com.google.common.base.Preconditions.checkState; +import static java.lang.Boolean.TRUE; +import static java.lang.Boolean.parseBoolean; +import static org.apache.maven.caching.ProjectUtils.getMultimoduleRoot; + +/** + * CacheConfigImpl + */ +@Component( role = CacheConfig.class, + instantiationStrategy = "singleton" ) +public class CacheConfigImpl implements CacheConfig +{ + + public static final String CONFIG_PATH_PROPERTY_NAME = "remote.cache.configPath"; + public static final String CACHE_ENABLED_PROPERTY_NAME = "remote.cache.enabled"; + public static final String SAVE_TO_REMOTE_PROPERTY_NAME = "remote.cache.save.enabled"; + public static final String SAVE_NON_OVERRIDEABLE_NAME = "remote.cache.save.final"; + public static final String FAIL_FAST_PROPERTY_NAME = "remote.cache.failFast"; + public static final String BASELINE_BUILD_URL_PROPERTY_NAME = "remote.cache.baselineUrl"; + + @Requirement + private Logger logger; + + @Requirement + private XmlService xmlService; + + private volatile CacheState state = CacheState.NOT_INITIALIZED; + private volatile CacheType cacheConfig; + private volatile HashFactory hashFactory; + + private final Supplier> excludePatterns = Suppliers.memoize( new Supplier>() + { + @Override + public List get() + { + return compileExcludePatterns(); + } + } ); + + + @Override + public synchronized CacheState initialize( MavenProject project, MavenSession session ) + { + + if ( state != CacheState.NOT_INITIALIZED ) + { + return state; + } + + final String enabled = System.getProperty( CACHE_ENABLED_PROPERTY_NAME, "true" ); + if ( !parseBoolean( enabled ) ) + { + logger.info( "Cache disabled by command line flag, project will be built fully and not cached" ); + state = CacheState.DISABLED; + return state; + } + + Path configPath = null; + + String configPathText = System.getProperty( CONFIG_PATH_PROPERTY_NAME ); + if ( StringUtils.isNotBlank( configPathText ) ) + { + configPath = Paths.get( configPathText ); + } + if ( configPath == null ) + { + configPathText = project.getProperties().getProperty( CONFIG_PATH_PROPERTY_NAME ); + if ( StringUtils.isNotBlank( configPathText ) ) + { + configPath = Paths.get( configPathText ); + } + } + + if ( configPath == null ) + { + configPath = Paths.get( getMultimoduleRoot( session ), ".mvn", "maven-cache-config.xml" ); + } + + if ( !Files.exists( configPath ) ) + { + logger.warn( + "Cache configuration is not available at configured path " + configPath + ", cache is disabled" ); + state = CacheState.DISABLED; + return state; + } + + try + { + logger.info( "Loading cache configuration from " + configPath ); + cacheConfig = xmlService.fromFile( CacheType.class, configPath.toFile() ); + } + catch ( Exception e ) + { + throw new IllegalArgumentException( + "Cannot initialize cache because xml config is not valid or not available", e ); + } + + if ( !cacheConfig.getConfiguration().isEnabled() ) + { + state = CacheState.DISABLED; + return state; + } + + String hashAlgorithm = null; + try + { + hashAlgorithm = getConfiguration().getHashAlgorithm(); + hashFactory = HashFactory.of( hashAlgorithm ); + logger.info( "Using " + hashAlgorithm + " hash algorithm for cache" ); + } + catch ( Exception e ) + { + throw new IllegalArgumentException( "Unsupported hashing algorithm: " + hashAlgorithm, e ); + } + + state = CacheState.INITIALIZED; + return state; + } + + @Nonnull + @Override + public List getTrackedProperties( MojoExecution mojoExecution ) + { + checkInitializedState(); + final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution ); + if ( reconciliationConfig != null ) + { + return reconciliationConfig.getReconcile(); + } + else + { + return Collections.emptyList(); + } + } + + @Override + public boolean isLogAllProperties( MojoExecution mojoExecution ) + { + final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution ); + if ( reconciliationConfig != null && reconciliationConfig.isLogAll() ) + { + return true; + } + return cacheConfig.isSetExecutionControl() && cacheConfig.getExecutionControl().isSetReconcile() + && cacheConfig.getExecutionControl().getReconcile().isLogAllProperties(); + } + + private GoalReconciliationType findReconciliationConfig( MojoExecution mojoExecution ) + { + + if ( !cacheConfig.isSetExecutionControl() ) + { + return null; + } + + final ExecutionControlType executionControl = cacheConfig.getExecutionControl(); + if ( !executionControl.isSetReconcile() ) + { + return null; + } + + final List reconciliation = executionControl.getReconcile().getPlugin(); + + for ( GoalReconciliationType goalReconciliationConfig : reconciliation ) + { + + final String goal = mojoExecution.getGoal(); + + if ( isPluginMatch( mojoExecution.getPlugin(), goalReconciliationConfig ) && StringUtils.equals( goal, + goalReconciliationConfig.getGoal() ) ) + { + return goalReconciliationConfig; + } + } + return null; + } + + @Nonnull + @Override + public List getLoggedProperties( MojoExecution mojoExecution ) + { + checkInitializedState(); + + final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution ); + if ( reconciliationConfig != null ) + { + return reconciliationConfig.getLog(); + } + else + { + return Collections.emptyList(); + } + } + + @Nonnull + @Override + public List getNologProperties( MojoExecution mojoExecution ) + { + checkInitializedState(); + final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution ); + if ( reconciliationConfig != null ) + { + return reconciliationConfig.getNolog(); + } + else + { + return Collections.emptyList(); + } + } + + @Nonnull + @Override + public List getEffectivePomExcludeProperties( Plugin plugin ) + { + checkInitializedState(); + final PluginConfigurationScanType pluginConfig = findPluginScanConfig( plugin ); + + if ( pluginConfig != null && pluginConfig.isSetEffectivePom() ) + { + return pluginConfig.getEffectivePom().getExcludeProperty(); + } + return Collections.emptyList(); + } + + private PluginConfigurationScanType findPluginScanConfig( Plugin plugin ) + { + + if ( !cacheConfig.isSetInput() ) + { + return null; + } + + final List pluginConfigs = cacheConfig.getInput().getPlugin(); + for ( PluginConfigurationScanType pluginConfig : pluginConfigs ) + { + if ( isPluginMatch( plugin, pluginConfig ) ) + { + return pluginConfig; + } + } + return null; + } + + private boolean isPluginMatch( Plugin plugin, CoordinatesBaseType pluginConfig ) + { + return StringUtils.equals( pluginConfig.getArtifactId(), + plugin.getArtifactId() ) && ( !pluginConfig.isSetGroupId() || StringUtils.equals( + pluginConfig.getGroupId(), plugin.getGroupId() ) ); + } + + + @Nonnull + @Override + public PluginScanConfig getPluginDirScanConfig( Plugin plugin ) + { + checkInitializedState(); + final PluginConfigurationScanType pluginConfig = findPluginScanConfig( plugin ); + if ( pluginConfig == null || !pluginConfig.isSetDirScan() ) + { + return new DefaultPluginScanConfig(); + } + + return new PluginScanConfigImpl( pluginConfig.getDirScan() ); + } + + @Nonnull + @Override + public PluginScanConfig getExecutionDirScanConfig( Plugin plugin, PluginExecution exec ) + { + checkInitializedState(); + final PluginConfigurationScanType pluginScanConfig = findPluginScanConfig( plugin ); + + if ( pluginScanConfig != null ) + { + final ExecutionConfigurationScanType executionScanConfig = findExecutionScanConfig( exec, + pluginScanConfig.getExecution() ); + if ( executionScanConfig != null && executionScanConfig.isSetDirScan() ) + { + return new PluginScanConfigImpl( executionScanConfig.getDirScan() ); + } + } + + return new DefaultPluginScanConfig(); + } + + private ExecutionConfigurationScanType findExecutionScanConfig( PluginExecution execution, + List scanConfigs ) + { + for ( ExecutionConfigurationScanType executionScanConfig : scanConfigs ) + { + if ( executionScanConfig.getExecId().contains( execution.getId() ) ) + { + return executionScanConfig; + } + } + return null; + } + + @Override + public String isProcessPlugins() + { + checkInitializedState(); + return TRUE.toString(); + } + + @Override + public String getDefaultGlob() + { + checkInitializedState(); + return StringUtils.trim( cacheConfig.getInput().getGlobal().getGlob() ); + } + + @Nonnull + @Override + public List getGlobalIncludePaths() + { + checkInitializedState(); + return cacheConfig.getInput().getGlobal().getInclude(); + } + + @Nonnull + @Override + public List getGlobalExcludePaths() + { + checkInitializedState(); + return cacheConfig.getInput().getGlobal().getExclude(); + } + + @Nonnull + @Override + public MultimoduleDiscoveryStrategy getMultimoduleDiscoveryStrategy() + { + checkInitializedState(); + final ConfigurationType.ProjectDiscoveryStrategy projectDiscoveryStrategy = + cacheConfig.getConfiguration().getProjectDiscoveryStrategy(); + if ( projectDiscoveryStrategy.isSetSpecificVersion() ) + { + return new SentinelVersionStartegy( projectDiscoveryStrategy.getSpecificVersion() ); + } + return new AllExternalSrategy(); + } + + @Nonnull + @Override + public HashFactory getHashFactory() + { + checkInitializedState(); + return hashFactory; + } + + @Override + public boolean canIgnore( MojoExecution mojoExecution ) + { + checkInitializedState(); + if ( !cacheConfig.isSetExecutionControl() || !cacheConfig.getExecutionControl().isSetIgnoreMissing() ) + { + return false; + } + + return executionMatches( mojoExecution, cacheConfig.getExecutionControl().getIgnoreMissing() ); + } + + @Override + public boolean isForcedExecution( MojoExecution execution ) + { + checkInitializedState(); + if ( !cacheConfig.isSetExecutionControl() || !cacheConfig.getExecutionControl().isSetRunAlways() ) + { + return false; + } + + return executionMatches( execution, cacheConfig.getExecutionControl().getRunAlways() ); + } + + private boolean executionMatches( MojoExecution execution, ExecutablesType executablesType ) + { + final List pluginConfigs = executablesType.getPlugin(); + for ( PluginSetType pluginConfig : pluginConfigs ) + { + if ( isPluginMatch( execution.getPlugin(), pluginConfig ) ) + { + return true; + } + } + + final List executionIds = executablesType.getExecution(); + for ( ExecutionIdsListType executionConfig : executionIds ) + { + if ( isPluginMatch( execution.getPlugin(), executionConfig ) && executionConfig.getExecId().contains( + execution.getExecutionId() ) ) + { + return true; + } + } + + final List pluginsGoalsList = executablesType.getGoals(); + for ( GoalsListType pluginGoals : pluginsGoalsList ) + { + if ( isPluginMatch( execution.getPlugin(), pluginGoals ) && pluginGoals.getGoal().contains( + execution.getGoal() ) ) + { + return true; + } + } + + return false; + } + + @Override + public boolean isEnabled() + { + return state == CacheState.INITIALIZED; + } + + + @Override + public boolean isRemoteCacheEnabled() + { + checkInitializedState(); + return getRemote().isEnabled(); + } + + @Override + public boolean isSaveToRemote() + { + checkInitializedState(); + return Boolean.getBoolean( SAVE_TO_REMOTE_PROPERTY_NAME ) || getRemote().isSaveToRemote(); + } + + @Override + public boolean isSaveFinal() + { + return Boolean.getBoolean( SAVE_NON_OVERRIDEABLE_NAME ); + } + + @Override + public boolean isFailFast() + { + return Boolean.getBoolean( FAIL_FAST_PROPERTY_NAME ); + } + + @Override + public boolean isBaselineDiffEnabled() + { + return System.getProperties().containsKey( BASELINE_BUILD_URL_PROPERTY_NAME ); + } + + @Override + public String getBaselineCacheUrl() + { + return System.getProperty( BASELINE_BUILD_URL_PROPERTY_NAME ); + } + + @Override + public String getUrl() + { + checkInitializedState(); + return getRemote().getUrl(); + } + + + @Override + public int getMaxLocalBuildsCached() + { + checkInitializedState(); + return getLocal().getMaxBuildsCached().intValue(); + } + + @Override + public List getAttachedOutputs() + { + checkInitializedState(); + final ConfigurationType.AttachedOutputs attachedOutputs = getConfiguration().getAttachedOutputs(); + return attachedOutputs == null ? Collections.emptyList() : attachedOutputs.getDirName(); + } + + @Nonnull + @Override + public List getExcludePatterns() + { + checkInitializedState(); + return excludePatterns.get(); + } + + private List compileExcludePatterns() + { + if ( cacheConfig.isSetOutput() && cacheConfig.getOutput().isSetExclude() ) + { + List patterns = new ArrayList<>(); + for ( String pattern : cacheConfig.getOutput().getExclude().getPattern() ) + { + patterns.add( Pattern.compile( pattern ) ); + } + return patterns; + } + return Collections.emptyList(); + } + + private ConfigurationType.Remote getRemote() + { + return getConfiguration().getRemote(); + } + + private ConfigurationType.Local getLocal() + { + return getConfiguration().getLocal(); + } + + private ConfigurationType getConfiguration() + { + return cacheConfig.getConfiguration(); + } + + private void checkInitializedState() + { + checkState( state == CacheState.INITIALIZED, "Cache is not initialized. Actual state: " + state ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheSource.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheSource.java new file mode 100644 index 000000000000..b21944463632 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheSource.java @@ -0,0 +1,30 @@ +package org.apache.maven.caching.xml; + +/* + * 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. + */ + +/** + * CacheSource + */ +public enum CacheSource +{ + LOCAL, + REMOTE, + BUILD +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheState.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheState.java new file mode 100644 index 000000000000..99267ed01e89 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheState.java @@ -0,0 +1,30 @@ +package org.apache.maven.caching.xml; + +/* + * 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. + */ + +/** + * CacheState + */ +public enum CacheState +{ + NOT_INITIALIZED, + DISABLED, + INITIALIZED +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/DtoUtils.java b/maven-core/src/main/java/org/apache/maven/caching/xml/DtoUtils.java new file mode 100644 index 000000000000..a2810ab86824 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/DtoUtils.java @@ -0,0 +1,219 @@ +package org.apache.maven.caching.xml; + +/* + * 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.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.caching.ProjectUtils; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.jaxb.CompletedExecutionType; +import org.apache.maven.caching.jaxb.DigestItemType; +import org.apache.maven.caching.jaxb.PropertyValueType; +import org.apache.maven.caching.jaxb.TrackedPropertyType; +import org.apache.maven.model.Dependency; + +import javax.annotation.Nonnull; +import java.util.List; + +import static org.apache.maven.caching.checksum.KeyUtils.getArtifactKey; + +/** + * DtoUtils + */ +public class DtoUtils +{ + + public static String findPropertyValue( String propertyName, CompletedExecutionType completedExecution ) + { + final CompletedExecutionType.Configuration configuration = completedExecution.getConfiguration(); + if ( configuration == null ) + { + return null; + } + final List properties = configuration.getProperty(); + for ( PropertyValueType property : properties ) + { + if ( StringUtils.equals( propertyName, property.getName() ) ) + { + return property.getValue(); + } + } + return null; + } + + public static ArtifactType createDto( Artifact artifact ) + { + ArtifactType dto = new ArtifactType(); + dto.setArtifactId( artifact.getArtifactId() ); + dto.setGroupId( artifact.getGroupId() ); + dto.setVersion( artifact.getVersion() ); + dto.setClassifier( artifact.getClassifier() ); + dto.setType( artifact.getType() ); + dto.setScope( artifact.getScope() ); + dto.setFileName( ProjectUtils.normalizedName( artifact ) ); + return dto; + } + + public static DigestItemType createdDigestedByProjectChecksum( ArtifactType artifact, String projectChecksum ) + { + DigestItemType dit = new DigestItemType(); + dit.setType( "module" ); + dit.setHash( projectChecksum ); + dit.setFileChecksum( artifact.getFileHash() ); + dit.setValue( getArtifactKey( artifact ) ); + return dit; + } + + public static DigestItemType createDigestedFile( Artifact artifact, String fileHash ) + { + DigestItemType dit = new DigestItemType(); + dit.setType( "artifact" ); + dit.setHash( fileHash ); + dit.setFileChecksum( fileHash ); + dit.setValue( getArtifactKey( artifact ) ); + return dit; + } + + public static Dependency createDependency( ArtifactType artifact ) + { + final Dependency dependency = new Dependency(); + dependency.setArtifactId( artifact.getArtifactId() ); + dependency.setGroupId( artifact.getGroupId() ); + dependency.setVersion( artifact.getVersion() ); + dependency.setClassifier( artifact.getClassifier() ); + dependency.setType( artifact.getType() ); + dependency.setScope( artifact.getScope() ); + return dependency; + } + + public static Dependency createDependency( Artifact artifact ) + { + final Dependency dependency = new Dependency(); + dependency.setArtifactId( artifact.getArtifactId() ); + dependency.setGroupId( artifact.getGroupId() ); + dependency.setVersion( artifact.getVersion() ); + dependency.setType( artifact.getType() ); + dependency.setScope( artifact.getScope() ); + dependency.setClassifier( artifact.getClassifier() ); + return dependency; + } + + public static void addProperty( CompletedExecutionType execution, + String propertyName, + Object value, + String baseDirPath, + boolean tracked ) + { + if ( execution.getConfiguration() == null ) + { + execution.setConfiguration( new CompletedExecutionType.Configuration() ); + } + final PropertyValueType valueType = new PropertyValueType(); + valueType.setName( propertyName ); + if ( value != null && value.getClass().isArray() ) + { + value = ArrayUtils.toString( value ); + } + final String valueText = String.valueOf( value ); + valueType.setValue( StringUtils.remove( valueText, baseDirPath ) ); + valueType.setTracked( tracked ); + execution.getConfiguration().getProperty().add( valueType ); + } + + public static boolean containsAllProperties( + @Nonnull CompletedExecutionType cachedExecution, List trackedProperties ) + { + + if ( trackedProperties == null || trackedProperties.isEmpty() ) + { + return true; + } + + if ( !cachedExecution.isSetConfiguration() ) + { + return false; + } + + final List executionProperties = cachedExecution.getConfiguration().getProperty(); + for ( TrackedPropertyType trackedProperty : trackedProperties ) + { + if ( !contains( executionProperties, trackedProperty.getPropertyName() ) ) + { + return false; + } + } + return true; + } + + public static boolean contains( List executionProperties, String propertyName ) + { + for ( PropertyValueType executionProperty : executionProperties ) + { + if ( StringUtils.equals( executionProperty.getName(), propertyName ) ) + { + return true; + } + } + return false; + } + + public static ArtifactType copy( ArtifactType artifact ) + { + ArtifactType copy = new ArtifactType(); + if ( artifact.isSetArtifactId() ) + { + copy.setArtifactId( artifact.getArtifactId() ); + } + if ( artifact.isSetGroupId() ) + { + copy.setGroupId( artifact.getGroupId() ); + } + if ( artifact.isSetVersion() ) + { + copy.setVersion( artifact.getVersion() ); + } + if ( artifact.isSetType() ) + { + copy.setType( artifact.getType() ); + } + if ( artifact.isSetClassifier() ) + { + copy.setClassifier( artifact.getClassifier() ); + } + if ( artifact.isSetScope() ) + { + copy.setScope( artifact.getScope() ); + } + if ( artifact.isSetFileName() ) + { + copy.setFileName( artifact.getFileName() ); + } + if ( artifact.isSetFileHash() ) + { + copy.setFileHash( artifact.getFileHash() ); + } + if ( artifact.isSetFileSize() ) + { + copy.setFileSize( artifact.getFileSize() ); + } + return copy; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/SentinelVersionStartegy.java b/maven-core/src/main/java/org/apache/maven/caching/xml/SentinelVersionStartegy.java new file mode 100644 index 000000000000..6d332308cc07 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/SentinelVersionStartegy.java @@ -0,0 +1,51 @@ +package org.apache.maven.caching.xml; + +/* + * 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.maven.artifact.Artifact; +import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy; +import org.apache.maven.model.Dependency; +import org.codehaus.plexus.util.StringUtils; + +/** + * SentinelVersionStartegy + */ +public class SentinelVersionStartegy implements MultimoduleDiscoveryStrategy +{ + private final String version; + + public SentinelVersionStartegy( String version ) + { + this.version = version; + } + + @Override + public boolean isPartOfMultiModule( Dependency dependency ) + { + return StringUtils.equals( version, dependency.getVersion() ); + } + + @Override + public boolean isLookupRemoteMavenRepo( Artifact artifact ) + { + // TODO abfx specific - should be config driven + return !StringUtils.equals( version, artifact.getVersion() ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/XmlService.java b/maven-core/src/main/java/org/apache/maven/caching/xml/XmlService.java new file mode 100644 index 000000000000..658c9d0dd3b1 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/caching/xml/XmlService.java @@ -0,0 +1,145 @@ +package org.apache.maven.caching.xml; + +/* + * 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.maven.caching.jaxb.BuildDiffType; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.caching.jaxb.CacheReportType; +import org.apache.maven.caching.jaxb.ObjectFactory; +import org.codehaus.plexus.component.annotations.Component; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; + +/** + * XmlService + */ +@Component( role = XmlService.class ) +public class XmlService +{ + + private final ObjectFactory objectFactory; + private final JAXBContext jaxbContext; + private final Schema schema; + + public XmlService() throws JAXBException, SAXException + { + objectFactory = new ObjectFactory(); + jaxbContext = JAXBContext.newInstance( "org.apache.maven.caching.jaxb", XmlService.class.getClassLoader() ); + + SchemaFactory sf = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ); + final InputStream domainSchemaStream = getResourceAsStream( "cache-domain.xsd" ); + final Source domainSchema = new StreamSource( domainSchemaStream ); + final InputStream configSchemaStream = getResourceAsStream( "cache-config.xsd" ); + final Source configSchema = new StreamSource( configSchemaStream ); + schema = sf.newSchema( new Source[] {domainSchema, configSchema} ); + } + + public byte[] toBytes( BuildInfoType buildInfo ) + { + return serializeXml( objectFactory.createBuild( buildInfo ) ); + } + + public byte[] toBytes( BuildDiffType diff ) + { + return serializeXml( objectFactory.createDiff( diff ) ); + } + + public byte[] toBytes( CacheReportType cacheReportType ) + { + + return serializeXml( objectFactory.createReport( cacheReportType ) ); + } + + private byte[] serializeXml( JAXBElement element ) + { + try + { + Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + jaxbMarshaller.setSchema( schema ); + // output pretty printed + jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true ); + jaxbMarshaller.setProperty( Marshaller.JAXB_ENCODING, "UTF-8" ); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + jaxbMarshaller.marshal( element, baos ); + return baos.toByteArray(); + } + catch ( Exception e ) + { + throw new RuntimeException( "Errors in jaxb serialization: " + e.toString(), e ); + } + } + + public T fromFile( Class clazz, File file ) + { + + try + { + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + unmarshaller.setSchema( schema ); + JAXBElement result = (JAXBElement) unmarshaller.unmarshal( file ); + return result.getValue(); + } + catch ( Exception e ) + { + throw new RuntimeException( "Errors in jaxb serialization: " + e.toString(), e ); + } + } + + public static InputStream getResourceAsStream( String name ) + { + ClassLoader cl = XmlService.class.getClassLoader(); + if ( cl == null ) + { + // A system class. + return ClassLoader.getSystemResourceAsStream( name ); + } + return cl.getResourceAsStream( name ); + } + + public T fromBytes( Class clazz, byte[] bytes ) + { + try + { + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + unmarshaller.setSchema( schema ); + JAXBElement result = (JAXBElement) unmarshaller.unmarshal( new ByteArrayInputStream( bytes ) ); + return result.getValue(); + + } + catch ( Exception e ) + { + throw new RuntimeException( "Errors in jaxb serialization: " + e.toString(), e ); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java index fb7a9f45064d..5a7db543f80b 100644 --- a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java @@ -34,9 +34,11 @@ import org.apache.maven.MavenExecutionException; import org.apache.maven.ProjectCycleException; import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.caching.checksum.KeyUtils; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectDependencyGraph; +import org.apache.maven.model.Dependency; import org.apache.maven.model.Plugin; import org.apache.maven.model.building.DefaultModelProblem; import org.apache.maven.model.building.ModelProblem; @@ -56,6 +58,8 @@ import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.dag.CycleDetectedException; +import static org.apache.maven.artifact.versioning.VersionRange.createFromVersion; + /** * Builds the {@link ProjectDependencyGraph inter-dependencies graph} between projects in the reactor. */ @@ -64,6 +68,8 @@ public class DefaultGraphBuilder implements GraphBuilder { + public static final String PROJECT_VERSION_PROP_NAME = "org.maven.project.version"; + @Requirement private Logger logger; @@ -81,6 +87,11 @@ public Result build( MavenSession session ) { final List projects = getProjectsForMavenReactor( session ); validateProjects( projects ); + + if ( System.getProperties().containsKey( PROJECT_VERSION_PROP_NAME ) ) + { + overrideReactorVersions( projects, session ); + } result = reactorDependencyGraph( session, projects ); } @@ -100,6 +111,68 @@ public Result build( MavenSession session ) } } + private void overrideReactorVersions( List projects, MavenSession session ) + { + String injectedVersion = System.getProperty( PROJECT_VERSION_PROP_NAME ); + logger.info( "Overriding reactor projects version to " + injectedVersion ); + + Map reactorProjects = new HashMap<>( projects.size() ); + + for ( MavenProject project : projects ) + { + String projectKey = KeyUtils.getVersionlessProjectKey( project ); + logger.debug( + "[" + projectKey + "] Overriding version from " + project.getVersion() + " to " + injectedVersion ); + reactorProjects.put( projectKey, project.getVersion() ); + project.setVersion( injectedVersion ); + project.getArtifact().setVersionRange( createFromVersion( injectedVersion ) ); + project.getArtifact().updateVersion( injectedVersion, session.getLocalRepository() ); + } + + for ( MavenProject project : projects ) + { + String projectKey = KeyUtils.getVersionlessProjectKey( project ); + MavenProject parent = project.getParent(); + String parentKey = KeyUtils.getVersionlessProjectKey( parent ); + String overriddenParentVersion = reactorProjects.get( parentKey ); + if ( overriddenParentVersion != null && overriddenParentVersion.equals( + project.getParentArtifact().getVersion() ) ) + { + logger.debug( + "[" + projectKey + "] Parent " + parentKey + " overriding artefact version from " + + parent.getVersion() + " to " + injectedVersion ); + project.getParentArtifact().setVersionRange( createFromVersion( injectedVersion ) ); + project.getParentArtifact().updateVersion( injectedVersion, session.getLocalRepository() ); + } + + for ( Dependency dependency : project.getDependencies() ) + { + String dependencyKey = KeyUtils.getVersionlessDependencyKey( dependency ); + String overriddenReactorVersion = reactorProjects.get( dependencyKey ); + if ( overriddenReactorVersion != null && overriddenReactorVersion.equals( dependency.getVersion() ) ) + { + logger.debug( + "[" + projectKey + "] Dependency " + dependencyKey + " overriding version from " + + dependency.getVersion() + " to " + injectedVersion ); + dependency.setVersion( injectedVersion ); + } + } + + for ( Dependency dependency : project.getDependencyManagement().getDependencies() ) + { + String dependencyKey = KeyUtils.getVersionlessDependencyKey( dependency ); + String overriddenReactorVersion = reactorProjects.get( dependencyKey ); + if ( overriddenReactorVersion != null && overriddenReactorVersion.equals( dependency.getVersion() ) ) + { + logger.debug( + "[" + projectKey + "] Dependency management " + dependencyKey + + " overriding version from " + dependency.getVersion() + " to " + injectedVersion ); + dependency.setVersion( injectedVersion ); + } + } + } + } + private Result sessionDependencyGraph( final MavenSession session ) throws CycleDetectedException, DuplicateProjectException { diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java index 7ee499f4b1a0..41004bdb118c 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java @@ -31,13 +31,13 @@ * Context of dependency artifacts for a particular project. *

* NOTE: This class is not part of any public api and can be changed or deleted without prior notice. - * + * * @since 3.0 * @author Benjamin Bentmann * @author Kristian Rosenvold (class extract only) */ // TODO From a concurrency perspective, this class is not good. The combination of mutable/immutable state is not nice -public class DependencyContext +public class DependencyContext implements IDependencyContext { private static final Collection UNRESOLVED = Arrays.asList(); @@ -66,46 +66,53 @@ public DependencyContext( MavenProject project, Collection scopesToColle scopesToResolveForAggregatedProjects = Collections.synchronizedSet( new TreeSet() ); } + @Override public MavenProject getProject() { return project; } + @Override public Collection getScopesToCollectForCurrentProject() { return scopesToCollectForCurrentProject; } + @Override public Collection getScopesToResolveForCurrentProject() { return scopesToResolveForCurrentProject; } + @Override public Collection getScopesToCollectForAggregatedProjects() { return scopesToCollectForAggregatedProjects; } + @Override public Collection getScopesToResolveForAggregatedProjects() { return scopesToResolveForAggregatedProjects; } + @Override public boolean isResolutionRequiredForCurrentProject() { - return lastDependencyArtifacts != project.getDependencyArtifacts() || ( lastDependencyArtifacts != null - && lastDependencyArtifactCount != lastDependencyArtifacts.size() ); + return lastDependencyArtifacts != project.getDependencyArtifacts() + || ( lastDependencyArtifacts != null && lastDependencyArtifactCount != lastDependencyArtifacts.size() ); } + @Override public boolean isResolutionRequiredForAggregatedProjects( Collection scopesToCollect, Collection scopesToResolve ) { - boolean required = - scopesToCollectForAggregatedProjects.addAll( scopesToCollect ) - || scopesToResolveForAggregatedProjects.addAll( scopesToResolve ); + boolean required = scopesToCollectForAggregatedProjects.addAll( + scopesToCollect ) || scopesToResolveForAggregatedProjects.addAll( scopesToResolve ); return required; } + @Override public void synchronizeWithProjectState() { lastDependencyArtifacts = project.getDependencyArtifacts(); diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/IDependencyContext.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/IDependencyContext.java new file mode 100644 index 000000000000..1f81561b1c7f --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/IDependencyContext.java @@ -0,0 +1,47 @@ +package org.apache.maven.lifecycle.internal; + +/* + * 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.maven.project.MavenProject; + +import java.util.Collection; + +/** + * IDependencyContext + */ +public interface IDependencyContext +{ + MavenProject getProject(); + + Collection getScopesToCollectForCurrentProject(); + + Collection getScopesToResolveForCurrentProject(); + + Collection getScopesToCollectForAggregatedProjects(); + + Collection getScopesToResolveForAggregatedProjects(); + + boolean isResolutionRequiredForCurrentProject(); + + boolean isResolutionRequiredForAggregatedProjects( Collection scopesToCollect, + Collection scopesToResolve ); + + void synchronizeWithProjectState(); +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java index b78f54dc42f3..59f384983d9e 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java @@ -22,13 +22,23 @@ import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter; +import org.apache.maven.caching.CacheController; +import org.apache.maven.caching.CacheResult; +import org.apache.maven.caching.MojoExecutionManager; +import org.apache.maven.caching.MojoParametersListener; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.CacheConfig; +import org.apache.maven.caching.xml.CacheState; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenSession; +import org.apache.maven.execution.MojoExecutionEvent; +import org.apache.maven.execution.MojoExecutionListener; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.MissingProjectException; import org.apache.maven.plugin.BuildPluginManager; import org.apache.maven.plugin.MavenPluginManager; import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecution.Source; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.PluginConfigurationException; @@ -38,6 +48,7 @@ import org.apache.maven.project.MavenProject; import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.util.StringUtils; import java.util.ArrayList; @@ -48,6 +59,13 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.apache.maven.caching.ProjectUtils.isLaterPhase; +import static org.apache.maven.caching.checksum.KeyUtils.getVersionlessProjectKey; +import static org.apache.maven.caching.xml.CacheState.DISABLED; +import static org.apache.maven.caching.xml.CacheState.INITIALIZED; + /** *

@@ -64,6 +82,9 @@ public class MojoExecutor { + @Requirement + private Logger logger; + @Requirement private BuildPluginManager pluginManager; @@ -76,6 +97,15 @@ public class MojoExecutor @Requirement private ExecutionEventCatapult eventCatapult; + @Requirement + private CacheController cacheController; + + @Requirement + private CacheConfig cacheConfig; + + @Requirement( role = MojoExecutionListener.class, hint = "MojoParametersListener" ) + private MojoParametersListener mojoListener; + public MojoExecutor() { } @@ -120,7 +150,7 @@ else if ( Artifact.SCOPE_RUNTIME.equals( classpath ) ) else if ( Artifact.SCOPE_COMPILE_PLUS_RUNTIME.equals( classpath ) ) { scopes = Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED, - Artifact.SCOPE_RUNTIME ); + Artifact.SCOPE_RUNTIME ); } else if ( Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals( classpath ) ) { @@ -129,37 +159,215 @@ else if ( Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals( classpath ) ) else if ( Artifact.SCOPE_TEST.equals( classpath ) ) { scopes = Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED, - Artifact.SCOPE_RUNTIME, Artifact.SCOPE_TEST ); + Artifact.SCOPE_RUNTIME, Artifact.SCOPE_TEST ); } } return Collections.unmodifiableCollection( scopes ); } public void execute( MavenSession session, List mojoExecutions, ProjectIndex projectIndex ) - throws LifecycleExecutionException - + throws LifecycleExecutionException { DependencyContext dependencyContext = newDependencyContext( session, mojoExecutions ); PhaseRecorder phaseRecorder = new PhaseRecorder( session.getCurrentProject() ); + final MavenProject project = session.getCurrentProject(); + final Source source = getSource( mojoExecutions ); + + // execute clean bound goals before restoring to not interfere/slowdown clean + CacheState cacheState = DISABLED; + CacheResult result = CacheResult.empty(); + if ( source == Source.LIFECYCLE ) + { + List cleanPhase = getCleanPhase( mojoExecutions ); + for ( MojoExecution mojoExecution : cleanPhase ) + { + execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder ); + } + cacheState = cacheConfig.initialize( project, session ); + if ( cacheState == INITIALIZED ) + { + result = cacheController.findCachedBuild( session, project, projectIndex, mojoExecutions ); + } + } + + boolean restorable = result.isSuccess() || result.isPartialSuccess(); + boolean restored = result.isSuccess(); // if partially restored need to save increment + if ( restorable ) + { + restored &= restoreProject( result, mojoExecutions, projectIndex, dependencyContext, phaseRecorder ); + } + else + { + for ( MojoExecution mojoExecution : mojoExecutions ) + { + if ( source == Source.CLI || isLaterPhase( mojoExecution.getLifecyclePhase(), "post-clean" ) ) + { + execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder ); + } + } + } + + if ( cacheState == INITIALIZED && ( !restorable || !restored ) ) + { + final Map executionEvents = mojoListener.getProjectExecutions( project ); + cacheController.save( result, mojoExecutions, executionEvents ); + } + + if ( cacheConfig.isFailFast() && !result.isSuccess() ) + { + throw new LifecycleExecutionException( + "Failed to restore project[" + getVersionlessProjectKey( project ) + "] from cache, failing build.", + project ); + } + } + + private Source getSource( List mojoExecutions ) + { + if ( mojoExecutions == null || mojoExecutions.isEmpty() ) + { + return null; + } for ( MojoExecution mojoExecution : mojoExecutions ) + { + if ( mojoExecution.getSource() == Source.CLI ) + { + return Source.CLI; + } + } + return Source.LIFECYCLE; + } + + private boolean restoreProject( CacheResult cacheResult, + List mojoExecutions, + ProjectIndex projectIndex, + DependencyContext dependencyContext, + PhaseRecorder phaseRecorder ) throws LifecycleExecutionException + { + + final BuildInfo buildInfo = cacheResult.getBuildInfo(); + final MavenProject project = cacheResult.getContext().getProject(); + final MavenSession session = cacheResult.getContext().getSession(); + final List cachedSegment = buildInfo.getCachedSegment( mojoExecutions ); + + boolean restored = cacheController.restoreProjectArtifacts( cacheResult ); + if ( !restored ) + { + logger.info( + "[CACHE][" + project.getArtifactId() + + "] Cannot restore project artifacts, continuing with non cached build" ); + return false; + } + + for ( MojoExecution cacheCandidate : cachedSegment ) + { + + if ( cacheController.isForcedExecution( project, cacheCandidate ) ) + { + logger.info( + "[CACHE][" + project.getArtifactId() + "] Mojo execution is forced by project property: " + + cacheCandidate.getMojoDescriptor().getFullGoalName() ); + execute( session, cacheCandidate, projectIndex, dependencyContext, phaseRecorder ); + } + else + { + restored = verifyCacheConsistency( cacheCandidate, buildInfo, project, session, projectIndex, + dependencyContext, phaseRecorder ); + if ( !restored ) + { + break; + } + } + } + + if ( !restored ) + { + // cleanup partial state + project.getArtifact().setFile( null ); + project.getArtifact().setResolved( false ); + project.getAttachedArtifacts().clear(); + mojoListener.remove( project ); + // build as usual + for ( MojoExecution mojoExecution : cachedSegment ) + { + execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder ); + } + } + + for ( MojoExecution mojoExecution : buildInfo.getPostCachedSegment( mojoExecutions ) ) { execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder ); } + return restored; + } + + private boolean verifyCacheConsistency( MojoExecution cacheCandidate, + BuildInfo cachedBuildInfo, + MavenProject project, + MavenSession session, + ProjectIndex projectIndex, + DependencyContext dependencyContext, + PhaseRecorder phaseRecorder ) throws LifecycleExecutionException + { + + AtomicBoolean consistent = new AtomicBoolean( true ); + final MojoExecutionManager mojoChecker = new MojoExecutionManager( project, cacheController, cachedBuildInfo, + consistent, logger, cacheConfig ); + + if ( mojoChecker.needCheck( cacheCandidate, session ) ) + { + try + { + // actual execution will not happen (if not forced). decision delayed to execution time + // then all properties are resolved. + cacheCandidate.setMojoExecutionManager( mojoChecker ); + IDependencyContext nop = new NoResolutionContext( dependencyContext ); + execute( session, cacheCandidate, projectIndex, nop, phaseRecorder ); + } + finally + { + cacheCandidate.setMojoExecutionManager( null ); + } + } + else + { + logger.info( + "[CACHE][" + project.getArtifactId() + "] Skipping plugin execution (cached): " + + cacheCandidate.getMojoDescriptor().getFullGoalName() ); + } + + return consistent.get(); + } + + private List getCleanPhase( List mojoExecutions ) + { + List list = new ArrayList<>(); + for ( MojoExecution mojoExecution : mojoExecutions ) + { + if ( isLaterPhase( mojoExecution.getLifecyclePhase(), "post-clean" ) ) + { + break; + } + list.add( mojoExecution ); + } + return list; } - public void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex, - DependencyContext dependencyContext, PhaseRecorder phaseRecorder ) - throws LifecycleExecutionException + public void execute( MavenSession session, + MojoExecution mojoExecution, + ProjectIndex projectIndex, + IDependencyContext dependencyContext, + PhaseRecorder phaseRecorder ) throws LifecycleExecutionException { execute( session, mojoExecution, projectIndex, dependencyContext ); phaseRecorder.observeExecution( mojoExecution ); } - private void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex, - DependencyContext dependencyContext ) - throws LifecycleExecutionException + private void execute( MavenSession session, + MojoExecution mojoExecution, + ProjectIndex projectIndex, + IDependencyContext dependencyContext ) throws LifecycleExecutionException { MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); @@ -175,18 +383,18 @@ private void execute( MavenSession session, MojoExecution mojoExecution, Project if ( mojoDescriptor.isProjectRequired() && !session.getRequest().isProjectPresent() ) { Throwable cause = new MissingProjectException( - "Goal requires a project to execute" + " but there is no POM in this directory (" - + session.getExecutionRootDirectory() + ")." - + " Please verify you invoked Maven from the correct directory." ); + "Goal requires a project to execute" + " but there is no POM in this directory (" + + session.getExecutionRootDirectory() + ")." + + " Please verify you invoked Maven from the correct directory." ); throw new LifecycleExecutionException( mojoExecution, null, cause ); } if ( mojoDescriptor.isOnlineRequired() && session.isOffline() ) { - if ( MojoExecution.Source.CLI.equals( mojoExecution.getSource() ) ) + if ( Source.CLI.equals( mojoExecution.getSource() ) ) { Throwable cause = new IllegalStateException( - "Goal requires online mode for execution" + " but Maven is currently offline." ); + "Goal requires online mode for execution" + " but Maven is currently offline." ); throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), cause ); } else @@ -209,8 +417,10 @@ private void execute( MavenSession session, MojoExecution mojoExecution, Project { pluginManager.executeMojo( session, mojoExecution ); } - catch ( MojoFailureException | PluginManagerException | PluginConfigurationException - | MojoExecutionException e ) + catch ( MojoFailureException + | PluginManagerException + | PluginConfigurationException + | MojoExecutionException e ) { throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e ); } @@ -232,9 +442,9 @@ private void execute( MavenSession session, MojoExecution mojoExecution, Project } } - public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenSession session, - DependencyContext dependencyContext ) - throws LifecycleExecutionException + public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, + MavenSession session, + IDependencyContext dependencyContext ) throws LifecycleExecutionException { MavenProject project = dependencyContext.getProject(); @@ -246,7 +456,7 @@ public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenS Collection scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject(); lifeCycleDependencyResolver.resolveProjectDependencies( project, scopesToCollect, scopesToResolve, session, - aggregating, Collections.emptySet() ); + aggregating, Collections.emptySet() ); dependencyContext.synchronizeWithProjectState(); } @@ -263,17 +473,15 @@ public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenS if ( aggregatedProject != project ) { lifeCycleDependencyResolver.resolveProjectDependencies( aggregatedProject, scopesToCollect, - scopesToResolve, session, aggregating, - Collections.emptySet() ); + scopesToResolve, session, aggregating, Collections.emptySet() ); } } } } ArtifactFilter artifactFilter = getArtifactFilter( mojoDescriptor ); - List projectsToResolve = - LifecycleDependencyResolver.getProjects( session.getCurrentProject(), session, - mojoDescriptor.isAggregator() ); + List projectsToResolve = LifecycleDependencyResolver.getProjects( session.getCurrentProject(), + session, mojoDescriptor.isAggregator() ); for ( MavenProject projectToResolve : projectsToResolve ) { projectToResolve.setArtifactFilter( artifactFilter ); @@ -305,9 +513,9 @@ private ArtifactFilter getArtifactFilter( MojoDescriptor mojoDescriptor ) } } - public List executeForkedExecutions( MojoExecution mojoExecution, MavenSession session, - ProjectIndex projectIndex ) - throws LifecycleExecutionException + public List executeForkedExecutions( MojoExecution mojoExecution, + MavenSession session, + ProjectIndex projectIndex ) throws LifecycleExecutionException { List forkedProjects = Collections.emptyList(); diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/NoResolutionContext.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/NoResolutionContext.java new file mode 100644 index 000000000000..e5ba54545ab0 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/NoResolutionContext.java @@ -0,0 +1,88 @@ +package org.apache.maven.lifecycle.internal; + +/* + * 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.maven.project.MavenProject; + +import java.util.Collection; + +/** + * NoResolutionContext + */ +public class NoResolutionContext implements IDependencyContext +{ + private final DependencyContext delegate; + + public NoResolutionContext( DependencyContext delegate ) + { + this.delegate = delegate; + } + + @Override + public MavenProject getProject() + { + return delegate.getProject(); + } + + @Override + public Collection getScopesToCollectForCurrentProject() + { + return delegate.getScopesToCollectForCurrentProject(); + } + + @Override + public Collection getScopesToResolveForCurrentProject() + { + + return delegate.getScopesToResolveForCurrentProject(); + } + + @Override + public Collection getScopesToCollectForAggregatedProjects() + { + return delegate.getScopesToCollectForAggregatedProjects(); + } + + @Override + public Collection getScopesToResolveForAggregatedProjects() + { + return delegate.getScopesToCollectForAggregatedProjects(); + } + + @Override + public boolean isResolutionRequiredForCurrentProject() + { + return false; + } + + @Override + public boolean isResolutionRequiredForAggregatedProjects( Collection scopesToCollect, + Collection scopesToResolve ) + { + return false; + } + + @Override + public void synchronizeWithProjectState() + { + delegate.synchronizeWithProjectState(); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java b/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java index 1a007424e760..e3b35ccf7182 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java @@ -44,8 +44,7 @@ * DefaultBuildPluginManager */ @Component( role = BuildPluginManager.class ) -public class DefaultBuildPluginManager - implements BuildPluginManager +public class DefaultBuildPluginManager implements BuildPluginManager { @Requirement @@ -74,8 +73,8 @@ public void setMojoExecutionListeners( final List listene * @param repositories * @param session * @return PluginDescriptor The component descriptor for the Maven plugin. - * @throws PluginNotFoundException The plugin could not be found in any repositories. - * @throws PluginResolutionException The plugin could be found but could not be resolved. + * @throws PluginNotFoundException The plugin could not be found in any repositories. + * @throws PluginResolutionException The plugin could be found but could not be resolved. * @throws InvalidPluginDescriptorException */ public PluginDescriptor loadPlugin( Plugin plugin, List repositories, @@ -134,7 +133,10 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution ) mojoExecutionListener.beforeMojoExecution( mojoExecutionEvent ); - mojo.execute(); + if ( mojoExecution.canRun( mojo, session ) ) + { + mojo.execute(); + } mojoExecutionListener.afterMojoExecutionSuccess( mojoExecutionEvent ); } @@ -150,20 +152,20 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution ) } catch ( PluginContainerException e ) { - mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo, - e ) ); + mojoExecutionListener.afterExecutionFailure( + new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) ); throw new PluginExecutionException( mojoExecution, project, e ); } catch ( NoClassDefFoundError e ) { - mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo, - e ) ); + mojoExecutionListener.afterExecutionFailure( + new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) ); ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); PrintStream ps = new PrintStream( os ); - ps.println( "A required class was missing while executing " + mojoDescriptor.getId() + ": " - + e.getMessage() ); + ps.println( + "A required class was missing while executing " + mojoDescriptor.getId() + ": " + e.getMessage() ); pluginRealm.display( ps ); Exception wrapper = new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), e ); @@ -172,8 +174,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution ) } catch ( LinkageError e ) { - mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo, - e ) ); + mojoExecutionListener.afterExecutionFailure( + new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) ); ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); PrintStream ps = new PrintStream( os ); @@ -187,8 +189,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution ) } catch ( ClassCastException e ) { - mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo, - e ) ); + mojoExecutionListener.afterExecutionFailure( + new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) ); ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); PrintStream ps = new PrintStream( os ); @@ -200,8 +202,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution ) } catch ( RuntimeException e ) { - mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo, - e ) ); + mojoExecutionListener.afterExecutionFailure( + new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) ); throw e; } @@ -218,8 +220,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution ) } /** - * TODO pluginDescriptor classRealm and artifacts are set as a side effect of this - * call, which is not nice. + * TODO pluginDescriptor classRealm and artifacts are set as a side effect of this call, which is not nice. + * * @throws PluginResolutionException */ public ClassRealm getPluginRealm( MavenSession session, PluginDescriptor pluginDescriptor ) diff --git a/maven-core/src/main/java/org/apache/maven/plugin/MojoCheker.java b/maven-core/src/main/java/org/apache/maven/plugin/MojoCheker.java new file mode 100644 index 000000000000..c82064b78bbf --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/MojoCheker.java @@ -0,0 +1,38 @@ +package org.apache.maven.plugin; + +/* + * 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.maven.execution.MavenSession; + +/** + * MojoCheker + */ +public interface MojoCheker +{ + /** + * @return true if this execution needs to be checked + */ + boolean needCheck( MojoExecution mojoExecution, MavenSession session ); + + /** + * @return true if check passed and mojo could be run + */ + boolean check( MojoExecution mojoExecution, Mojo mojo, MavenSession session ); +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java b/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java index fa72c1866e4c..0cac2a7fba9e 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java @@ -19,14 +19,15 @@ * under the License. */ -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - +import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Plugin; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.codehaus.plexus.util.xml.Xpp3Dom; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + /** * MojoExecution */ @@ -41,8 +42,15 @@ public class MojoExecution private MojoDescriptor mojoDescriptor; + private MojoCheker mojoCheker; + private Xpp3Dom configuration; + public boolean canRun( Mojo mojo, MavenSession session ) + { + return mojoCheker == null || mojoCheker.check( this, mojo, session ); + } + /** * Describes the source of an execution. */ @@ -63,8 +71,8 @@ public enum Source private Source source = Source.LIFECYCLE; /** - * The phase may or may not have been bound to a phase but once the plan has been calculated we know what phase - * this mojo execution is going to run in. + * The phase may or may not have been bound to a phase but once the plan has been calculated we know what phase this + * mojo execution is going to run in. */ private String lifecyclePhase; @@ -237,4 +245,8 @@ public void setForkedExecutions( String projectKey, List forkedEx this.forkedExecutions.put( projectKey, forkedExecutions ); } + public void setMojoExecutionManager( MojoCheker mojoCheker ) + { + this.mojoCheker = mojoCheker; + } } diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java index 7e18f1ef3ce2..b611758bf565 100644 --- a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java @@ -19,20 +19,6 @@ * under the License. */ -import java.io.File; -import java.io.IOException; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.InvalidArtifactRTException; @@ -76,6 +62,20 @@ import org.eclipse.aether.resolution.ArtifactRequest; import org.eclipse.aether.resolution.ArtifactResult; +import java.io.File; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * DefaultProjectBuilder */ @@ -675,6 +675,7 @@ private void initProject( MavenProject project, Map projec project.setModel( model ); project.setOriginalModel( result.getRawModel() ); + project.setOriginalEffectiveModel( model.clone() ); project.setFile( model.getPomFile() ); initParent( project, projects, buildParentIfNotExisting, result, projectBuildingRequest ); @@ -791,7 +792,7 @@ private void initProject( MavenProject project, Map projec HashMap delegate; @Override - public Set> entrySet() + public Set> entrySet() { return Collections.unmodifiableSet( compute().entrySet() ); } diff --git a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java index ceb39e21a231..b33f441b0c0b 100644 --- a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java +++ b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java @@ -151,6 +151,8 @@ public class MavenProject private Model originalModel; + private Model originalEffectiveModel; + private Map pluginArtifactMap; private Set reportArtifacts; @@ -1032,6 +1034,16 @@ public Model getOriginalModel() return originalModel; } + public Model getOriginalEffectiveModel() + { + return originalEffectiveModel; + } + + public void setOriginalEffectiveModel( Model originalEffectiveModel ) + { + this.originalEffectiveModel = originalEffectiveModel; + } + public void setManagedVersionMap( Map map ) { managedVersionMap = map; diff --git a/maven-core/src/main/resources/cache-config-instance.xml b/maven-core/src/main/resources/cache-config-instance.xml new file mode 100644 index 000000000000..79f7422c24c4 --- /dev/null +++ b/maven-core/src/main/resources/cache-config-instance.xml @@ -0,0 +1,92 @@ + + + + + true + SHA-256 + true + + http://host:port + + + 3 + + + + + + + {*.java,*.groovy,*.yaml,*.svcd,*.proto,*assembly.xml,assembly*.xml,*logback.xml,*.vm,*.ini,*.jks,*.properties,*.sh,*.bat} + + src/ + pom.xml + + + + 111 + + + + + + + + 1 + 2 + + + + + + + + + .*-processes.*\.zip + + + + + + + aaa + + + install + + + deploy + + + deploy-local + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maven-core/src/main/resources/cache-config.xsd b/maven-core/src/main/resources/cache-config.xsd new file mode 100644 index 000000000000..1f5eb4eb9be6 --- /dev/null +++ b/maven-core/src/main/resources/cache-config.xsd @@ -0,0 +1,620 @@ + + + + + + + + + + + + + + + + Cached build metadata + + + + + + + + + + Configuration of major cache properties + + + + + + + Configuration for source code input files participating in checksum calculation + + + + + + + Configuration for output artifacts, it's needed if you want to explicitly include/exclude something from caching + + + + + + + Execution rules for plugins in cached mode. Defines which plugins should run always + + + + + + + + + + + + + + + + 64 bit XX family hashing. Fast, but higher probability of collisions + + + + + + + 64 bit XX family hashing, with usage of Memory Mapped Buffer + + + + + + + + + + + + + + Validate cache config and builds metadata against xsd. + + + + + + + Specifies how to identify belonging to a cached project then submodule is being build. + + + + + + + + Any project dependency this this version will be considered cache eligible and will + be processed cache aware + + + + + + + + + + + + + address of remote cache + + + + + + + + + Save output to remote cache. Recommended to enable on CI agents only. + + + + + + + + + + + + Directory name in build output directory to attach to cached artifacts + + + + + + + + + + + + + Maximum number of cached build per artifact in local cache. First created cache (the + oldest) is + evicted if breached. + + + + + + + + + + + + + Causes file hash is saved in build metadata + + + + + + + Causes effective pom info is saved in build metadata + + + + + + + + + + + + + + + Global input calculation rules applicable to all projects and plugins in the build + + + + + + + Plugin specific input calculation rules + + + + + + + + + + + + + + Effective pom calculation rules + + + + + + + + Plugin configuration property should be excluded from effective pom + calculation + + + + + + + + + + Specifies plugin level rules of configuration processing in search of referenced source + files + + + + + + + Specifies execution specific configuration processing in search of referenced source + files + + + + + + + + + + + + + + + Specifies rules of configuration processing in search of referenced source files + + + + + + + + ignore parent config or inherit/merge + + + + + + + + + Common attributes for scanning paths + + + + + + Should walk directory specified in property recursively or not + + + + + + + Glob to apply when scanning dir denoted by this property + + + + + + + + + + + + + + + + + + + Forces cache to treat property value as input and include in calculation. If set, only included + properties will be takein in calculation (whitelist) + + + + + + + + Tag to exclude when scanning plugin configuration for input files (blacklist) + + + + + + + Additional processing rules for non-blacklisted tags + + + + + + + + + + Ignore parent settings or inherit and merge + + + + + + + + + + + Scan directory accordingly to cache implementation + + + + + + + Skip directory + + + + + + + + + + + + + + Patterns to exclude output artifacts applicable to all projects in the build + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Specify property which should be reconciled against running build + + + + + + + Specify property which should be logged to build metadata for exploration + + + + + + + Specify property which should not be logged + + + + + + + + Controls if all plugin properties to be logged (true is default). All the properties logged + with respect to log/nolog children: + * true: logged all if no blacklists () and whitelists () specified on plugin + level + * false: logged only tracked and included by whitelists () on plugin level + + + + + + + + + + + + + Specify which plugin should run always if present in build regardless of cached status + + + + + + + Specify which executions/plugins/goals do not affect generated artifacts and do not affect build correctness. + If cached build lacks of ignorable executions only, it still could be reused. + Typically case is then cached build is produced with 'verify' and you locally you run 'install'. + Strictly speaking these are different builds but in most of cases you want this difference to be ignored + + + + + + + Specify which plugin should run always if present in build regardless of cached status + + + + + + + + Reconciliation rules for plugin properties which might be affected by command line + flags, etc + + + + + + + + Controls if all plugin properties to be logged (true is default). All the properties + logged with respect to children: + * logAll on plugin level overrides global value + * true: logged all if no blacklists () and whitelists () specified on + plugin level + * false: logged only tracked and included by whitelists () on plugin level + + + + + + + + + + + + + + + Specify which plugin should run always if present in build regardless of cached status + + + + + + + Specify which executions should run always if present in build regardless of cached status + + + + + + + Specify which goals should run always if present in build regardless of cached status + + + + + + + + + + + + + + Goals identification + + + + + + + + + + + + + + + + + + + + + + + + + Executions ids list with plugin identifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Specify which value denotes skipped execution in plugin config. + If active build skips execution (property set to skipValue) cache will allow such + discrepancy. + + + + + + + Manual value for reconciliation. Required to reconcile runtime only properties + + + + + + + + diff --git a/maven-core/src/main/resources/cache-domain-instance.xml b/maven-core/src/main/resources/cache-domain-instance.xml new file mode 100644 index 000000000000..1c859de7f831 --- /dev/null +++ b/maven-core/src/main/resources/cache-domain-instance.xml @@ -0,0 +1,44 @@ + + + v3 + 1980-03-23T10:20:15 + my-server.com + SHA-256 + + clean + verify + + + com.my.shared + my-server + jar + my-server.jar + hashvalue + 123456 + + + + com.my.shared + my-server + java-source + sources + my-server-sources.jar + + + + + default:check:verify:duplicate-finder-maven-plugin:org.basepom.maven:1.3.0 + org.basepom.mojo.duplicatefinder.DuplicateFinderMojo + + true + false + + + + + 0136171ac2c9d9b066805bd09904a25318cfb2037189b20c48e79b20b63b81ed + src/main/conf/my-context.xml + + diff --git a/maven-core/src/main/resources/cache-domain.xsd b/maven-core/src/main/resources/cache-domain.xsd new file mode 100644 index 000000000000..48446ca7370c --- /dev/null +++ b/maven-core/src/main/resources/cache-domain.xsd @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maven-core/src/test/java/org/apache/maven/caching/BuildInfoTest.java b/maven-core/src/test/java/org/apache/maven/caching/BuildInfoTest.java new file mode 100644 index 000000000000..6c9b14c418aa --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/caching/BuildInfoTest.java @@ -0,0 +1,113 @@ +package org.apache.maven.caching; + +/* + * 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 com.google.common.collect.Lists; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; +import org.apache.maven.caching.hash.HashFactory; +import org.apache.maven.caching.jaxb.ArtifactType; +import org.apache.maven.caching.jaxb.BuildInfoType; +import org.apache.maven.caching.jaxb.CompletedExecutionType; +import org.apache.maven.caching.jaxb.DigestItemType; +import org.apache.maven.caching.jaxb.ProjectsInputInfoType; +import org.apache.maven.caching.jaxb.PropertyValueType; +import org.apache.maven.caching.xml.BuildInfo; +import org.apache.maven.caching.xml.XmlService; + +import javax.xml.datatype.DatatypeFactory; +import java.io.File; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.GregorianCalendar; + +import static org.apache.maven.caching.xml.BuildInfo.createGoals; + +public class BuildInfoTest { + + // @Test + public void name() throws Exception { + + XmlService xmlService = new XmlService(); + + ProjectsInputInfoType main = new ProjectsInputInfoType(); + main.setChecksum("dependencyChecksum"); + main.getItem().add(createItem("pom", "4.0.0", "hash1")); + main.getItem().add(createItem("file", Paths.get(".").toString(), "hash2")); + + ArtifactType artifact = new ArtifactType(); + artifact.setGroupId("g"); + artifact.setArtifactId("a"); + artifact.setType("t"); + artifact.setClassifier("c"); + artifact.setScope("s"); + artifact.setFileName("f"); + artifact.setFileSize(BigInteger.valueOf(123456)); + artifact.setFileHash("456L"); + + BuildInfoType buildInfo = new BuildInfoType(); + buildInfo.setCacheImplementationVersion("cacheImplementationVersion"); + buildInfo.setBuildServer("server"); + buildInfo.setBuildTime(DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar())); + buildInfo.setArtifact(artifact); + buildInfo.setHashFunction("SHA-256"); + buildInfo.setGoals(createGoals(Lists.newArrayList("install"))); + final Artifact attachedArtifact = new DefaultArtifact("ag", "aa", "av", "as", "at", "ac", new DefaultArtifactHandler()); + buildInfo.setAttachedArtifacts(BuildInfo.createAttachedArtifacts(Lists.newArrayList(attachedArtifact), HashFactory.XX.createAlgorithm())); + buildInfo.setProjectsInputInfo(main); + buildInfo.setExecutions(createExecutions()); + + byte[] bytes = xmlService.toBytes(buildInfo); + System.out.println(new String(bytes, StandardCharsets.UTF_8)); + Path tempFilePath = Files.createTempFile("test", "test"); + File file = tempFilePath.toFile(); + file.deleteOnExit(); + Files.write(tempFilePath, bytes); + + BuildInfoType buildInfo1 = xmlService.fromFile(BuildInfoType.class, file); + System.out.println(buildInfo1); + } + + private BuildInfoType.Executions createExecutions() { + CompletedExecutionType execution = new CompletedExecutionType(); + execution.setExecutionKey("execkey"); + execution.setConfiguration(new CompletedExecutionType.Configuration()); + PropertyValueType property = new PropertyValueType(); + property.setValue("value"); + property.setName("key"); + execution.getConfiguration().getProperty().add(property); + BuildInfoType.Executions executions = new BuildInfoType.Executions(); + executions.getExecution().add(execution); + return executions; + } + + private DigestItemType createItem(String pom, String s, String hash1) { + final DigestItemType d1 = new DigestItemType(); + d1.setType(pom); + d1.setHash(s); + d1.setValue(hash1); + return d1; + } +} \ No newline at end of file diff --git a/maven-core/src/test/java/org/apache/maven/caching/checksum/MavenProjectInputTest.java b/maven-core/src/test/java/org/apache/maven/caching/checksum/MavenProjectInputTest.java new file mode 100644 index 000000000000..d420134e6746 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/caching/checksum/MavenProjectInputTest.java @@ -0,0 +1,85 @@ +package org.apache.maven.caching.checksum; + +/* + * 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.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class MavenProjectInputTest { + + private static final String GLOB = "{*-pom.xml}"; + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testAddInputsRelativePath() { +// MavenProjectInput inputs = new MavenProjectInput(config, new ArrayList(), Paths.get("src\\test\\resources\\org"), GLOB); +// ArrayList files = new ArrayList<>(); +// inputs.listDirOrFile("../../resources", inputs.dirGlob, files, new HashSet()); +// assertEquals(4, files.size()); + } + + @Test + public void testAddInputsAbsolutePath() { +// Path baseDirPath = Paths.get("src\\test\\resources\\org"); +// MavenProjectInput inputs = new MavenProjectInput(config, new ArrayList(), baseDirPath, GLOB); +// ArrayList files = new ArrayList<>(); +// Path candidatePath = baseDirPath.resolve("../../resources").normalize().toAbsolutePath(); +// inputs.listDirOrFile(candidatePath.toString(), inputs.dirGlob, files, new HashSet()); +// assertEquals(4, files.size()); // pom is filtered out by hardcoded if + } + + @Test + public void testGlobInput() { +// Path baseDirPath = Paths.get("src\\test\\resources"); +// MavenProjectInput inputs = new MavenProjectInput(config, new ArrayList(), baseDirPath, GLOB); +// ArrayList files = new ArrayList<>(); +// inputs.tryAddInputs("*.java", files, new HashSet()); +// assertEquals(0, files.size()); // pom is filtered out by hardcoded if + } + + @Test + public void testGetDirectoryFiles() { + List directoryFiles = new ArrayList<>(); + MavenProjectInput.walkDirectoryFiles(Paths.get("src/test/resources"), directoryFiles, MavenProjectInput.DEFAULT_GLOB); + assertEquals(0, directoryFiles.size()); // pom is filtered out by hardcoded if + } + + @Test + public void testGetDirectoryFiles2() { + List directoryFiles = new ArrayList<>(); + MavenProjectInput.walkDirectoryFiles(Paths.get("src/test/resources"), directoryFiles, GLOB); + assertEquals(4, directoryFiles.size()); // pom is filtered out by hardcoded if + } +} diff --git a/maven-core/src/test/java/org/apache/maven/caching/checksum/SHAHashTest.java b/maven-core/src/test/java/org/apache/maven/caching/checksum/SHAHashTest.java new file mode 100644 index 000000000000..9cf5bb405f91 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/caching/checksum/SHAHashTest.java @@ -0,0 +1,72 @@ +package org.apache.maven.caching.checksum; + +/* + * 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.maven.caching.hash.HashAlgorithm; +import org.apache.maven.caching.hash.HashChecksum; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +import static org.apache.maven.caching.hash.HashFactory.SHA256; +import static org.junit.Assert.assertEquals; + +public class SHAHashTest { + private static final byte[] HELLO_ARRAY = "hello".getBytes(StandardCharsets.UTF_8); + private static final byte[] WORLD_ARRAY = "world".getBytes(StandardCharsets.UTF_8); + private static final String EMPTY_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + private static final String HELLO_HASH = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"; + private static final String WORLD_HASH = "486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7"; + private static final String HELLO_CHECKSUM = "9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50"; + private static final String WORLD_CHECKSUM = "63e5c163c81ee9a3ed99d365ff963ecea340cc455deeac7c4b63ac75b9cf4706"; + private static final String FULL_CHECKSUM = "7305db9b2abccd706c256db3d97e5ff48d677cfe4d3a5904afb7da0e3950e1e2"; + + private static final HashAlgorithm ALGORITHM = SHA256.createAlgorithm(); + private static final HashChecksum CHECKSUM = SHA256.createChecksum(0); + + @Test + public void testEmptyArray() { + byte[] emptyArray = new byte[0]; + String hash = ALGORITHM.hash(emptyArray); + assertEquals(EMPTY_HASH, hash); + } + + @Test + public void testSimpleHash() { + String helloHash = ALGORITHM.hash(HELLO_ARRAY); + assertEquals(HELLO_HASH, helloHash); + + String worldHash = ALGORITHM.hash(WORLD_ARRAY); + assertEquals(WORLD_HASH, worldHash); + } + + @Test + public void testSimpleChecksum() { + assertEquals(HELLO_HASH, CHECKSUM.update(HELLO_ARRAY)); + assertEquals(HELLO_CHECKSUM, CHECKSUM.digest()); + + assertEquals(WORLD_HASH, CHECKSUM.update(WORLD_ARRAY)); + assertEquals(WORLD_CHECKSUM, CHECKSUM.digest()); + + assertEquals(HELLO_HASH, CHECKSUM.update(HELLO_ARRAY)); + assertEquals(WORLD_HASH, CHECKSUM.update(WORLD_ARRAY)); + assertEquals(FULL_CHECKSUM, CHECKSUM.digest()); + } +} diff --git a/maven-core/src/test/java/org/apache/maven/caching/checksum/XXHashTest.java b/maven-core/src/test/java/org/apache/maven/caching/checksum/XXHashTest.java new file mode 100644 index 000000000000..3a1a46ecf492 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/caching/checksum/XXHashTest.java @@ -0,0 +1,132 @@ +package org.apache.maven.caching.checksum; + +/* + * 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.maven.caching.hash.HashAlgorithm; +import org.apache.maven.caching.hash.HashChecksum; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import static org.apache.maven.caching.hash.HashFactory.XX; +import static org.apache.maven.caching.hash.HashFactory.XXMM; +import static org.junit.Assert.assertEquals; + +public class XXHashTest { + private static final byte[] HELLO_ARRAY = "hello".getBytes(StandardCharsets.UTF_8); + private static final byte[] WORLD_ARRAY = "world".getBytes(StandardCharsets.UTF_8); + private static final long HELLO_LONG = 2794345569481354659L; + private static final long WORLD_LONG = -1767385783675760145L; + private static final String EMPTY_HASH = "ef46db3751d8e999"; + private static final String HELLO_HASH = "26c7827d889f6da3"; + private static final String WORLD_HASH = "e778fbfe66ee51ef"; + private static final String HELLO_CHECKSUM = "c07c10338a825a5d"; + private static final String WORLD_CHECKSUM = "cb21505d7a714523"; + private static final String FULL_CHECKSUM = "b8ca8fa824d335e9"; + + private static final HashAlgorithm ALGORITHM = XX.createAlgorithm(); + + @Test + public void testEmptyArray() { + byte[] emptyArray = new byte[0]; + String hash = ALGORITHM.hash(emptyArray); + assertEquals(EMPTY_HASH, hash); + } + + @Test + public void testSimpleHash() { + String helloHash = ALGORITHM.hash(HELLO_ARRAY); + assertEquals(HELLO_HASH, helloHash); + + String worldHash = ALGORITHM.hash(WORLD_ARRAY); + assertEquals(WORLD_HASH, worldHash); + } + + @Test + public void testSimpleChecksum() { + String helloChecksum = ALGORITHM.hash(longToBytes(1, HELLO_LONG)); + assertEquals(HELLO_CHECKSUM, helloChecksum); + + String worldChecksum = ALGORITHM.hash(longToBytes(1, WORLD_LONG)); + assertEquals(WORLD_CHECKSUM, worldChecksum); + + String checksum = ALGORITHM.hash(longToBytes(2, HELLO_LONG, WORLD_LONG)); + assertEquals(FULL_CHECKSUM, checksum); + } + + @Test + public void testEmptyBuffer() { + assertEmptyBuffer(XX.createChecksum(0)); + assertEmptyBuffer(XXMM.createChecksum(0)); + } + + @Test + public void testSingleHash() { + assertSingleHash(XX.createChecksum(1)); + assertSingleHash(XXMM.createChecksum(1)); + } + + @Test + public void testSingleChecksum() { + assertSingleChecksum(XX.createChecksum(1)); + assertSingleChecksum(XXMM.createChecksum(1)); + } + + @Test + public void testNotFullChecksum() { + assertSingleChecksum(XX.createChecksum(2)); + assertSingleChecksum(XXMM.createChecksum(2)); + } + + @Test + public void testFullChecksum() { + assertFullChecksum(XX.createChecksum(2)); + assertFullChecksum(XXMM.createChecksum(2)); + } + + private void assertEmptyBuffer(HashChecksum checksum) { + assertEquals(EMPTY_HASH, checksum.digest()); + } + + private void assertSingleHash(HashChecksum checksum) { + assertEquals(HELLO_HASH, checksum.update(HELLO_ARRAY)); + assertEquals(HELLO_CHECKSUM, checksum.digest()); + } + + private void assertSingleChecksum(HashChecksum checksum) { + assertEquals(HELLO_HASH, checksum.update(HELLO_HASH)); + assertEquals(HELLO_CHECKSUM, checksum.digest()); + } + + private void assertFullChecksum(HashChecksum checksum) { + assertEquals(HELLO_HASH, checksum.update(HELLO_HASH)); + assertEquals(WORLD_HASH, checksum.update(WORLD_HASH)); + assertEquals(FULL_CHECKSUM, checksum.digest()); + } + + private byte[] longToBytes(int size, long... values) { + final ByteBuffer buffer = ByteBuffer.allocate(size * Long.SIZE / Byte.SIZE); + for (long value : values) { + buffer.putLong(value); + } + return buffer.array(); + } +} diff --git a/maven-core/src/test/java/org/apache/maven/caching/hash/HexUtilsTest.java b/maven-core/src/test/java/org/apache/maven/caching/hash/HexUtilsTest.java new file mode 100644 index 000000000000..e9615f493e20 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/caching/hash/HexUtilsTest.java @@ -0,0 +1,50 @@ +package org.apache.maven.caching.hash; + +/* + * 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.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class HexUtilsTest { + + @Test + public void testEncodeToHex() { + //array length = 8 left padded with zeroes + assertEquals("0", HexUtils.encode(new byte[8])); + assertEquals("0", HexUtils.encode(new byte[1])); + + assertEquals("a", HexUtils.encode(new byte[]{10})); + assertEquals("a", HexUtils.encode(new byte[]{0, 0, 0, 0, 0, 0, 0, 10})); + + assertEquals("100", HexUtils.encode(new byte[]{1, 0})); + assertEquals("101", HexUtils.encode(new byte[]{0, 0, 0, 0, 0, 0, 1, 1})); + } + + @Test + public void testDecodeHex() { + assertArrayEquals(new byte[]{0}, HexUtils.decode("0")); + assertArrayEquals(new byte[]{10}, HexUtils.decode("a")); + assertArrayEquals(new byte[]{10}, HexUtils.decode("A")); + assertArrayEquals(new byte[]{1, 0}, HexUtils.decode("100")); + } + +} \ No newline at end of file diff --git a/maven-core/src/test/java/org/apache/maven/caching/xml/CacheConfigTest.java b/maven-core/src/test/java/org/apache/maven/caching/xml/CacheConfigTest.java new file mode 100644 index 000000000000..b18752fd0594 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/caching/xml/CacheConfigTest.java @@ -0,0 +1,35 @@ +package org.apache.maven.caching.xml; + +/* + * 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.junit.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class CacheConfigTest { + + @Test + public void testMU() { +// final Path path = Paths.get("W:\\dev\\abfx\\maven-cache-config.xml"); +// final CacheConfig cacheConfig = CacheConfig.fromFile(path.toFile()); +// System.out.println(cacheConfig.toString()); + } +} \ No newline at end of file diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java index 8a6580b699af..1650e24eb681 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java @@ -17,10 +17,7 @@ import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.lifecycle.internal.DependencyContext; -import org.apache.maven.lifecycle.internal.MojoExecutor; -import org.apache.maven.lifecycle.internal.PhaseRecorder; -import org.apache.maven.lifecycle.internal.ProjectIndex; +import org.apache.maven.lifecycle.internal.*; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptor; @@ -39,8 +36,8 @@ public class MojoExecutorStub public List executions = Collections.synchronizedList( new ArrayList() ); @Override - public void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex, - DependencyContext dependencyContext, PhaseRecorder phaseRecorder ) + public void execute(MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex, + IDependencyContext dependencyContext, PhaseRecorder phaseRecorder ) throws LifecycleExecutionException { executions.add( mojoExecution ); diff --git a/pom.xml b/pom.xml index c770f0d33c5c..75e04d8204bd 100644 --- a/pom.xml +++ b/pom.xml @@ -47,8 +47,8 @@ under the License. 3.0.5 - 1.7 - 1.7 + 1.8 + 1.8 2.6.0 1.4 3.8.1 @@ -565,6 +565,11 @@ under the License. --> src/main/appended-resources/licenses/CDDL-1.0.txt src/main/appended-resources/licenses/EPL-1.0.txt + src/main/appended-resources/licenses/EDL-1.0.txt + src/main/appended-resources/licenses/MPL-1.1.txt + src/main/appended-resources/licenses/LGPL-3.0.txt + .editorconfig + **/*.iml @@ -601,6 +606,7 @@ under the License. animal-sniffer-maven-plugin 1.17 + false org.codehaus.mojo.signature java17