├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── scriptjar.groovy └── src ├── AetherUtil.groovy ├── ConsoleRepositoryListener.groovy ├── ConsoleTransferListener.groovy ├── artifactResolver.groovy └── simplelogger.properties /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | artifactResolver.jar 3 | script.args 4 | 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea/workspace.xml 10 | .idea/tasks.xml 11 | 12 | # Sensitive or high-churn files: 13 | .idea/dataSources/ 14 | .idea/dataSources.ids 15 | .idea/dataSources.xml 16 | .idea/dataSources.local.xml 17 | .idea/sqlDataSources.xml 18 | .idea/dynamic.xml 19 | .idea/uiDesigner.xml 20 | 21 | # Gradle: 22 | .idea/gradle.xml 23 | .idea/libraries 24 | 25 | # Mongo Explorer plugin: 26 | .idea/mongoSettings.xml 27 | 28 | ## File-based project format: 29 | *.iws 30 | 31 | ## Plugin-specific files: 32 | 33 | # IntelliJ 34 | /out/ 35 | 36 | # mpeltonen/sbt-idea plugin 37 | .idea_modules/ 38 | 39 | # JIRA plugin 40 | atlassian-ide-plugin.xml 41 | 42 | # Crashlytics plugin (for Android Studio and IntelliJ) 43 | com_crashlytics_export_strings.xml 44 | crashlytics.properties 45 | crashlytics-build.properties 46 | fabric.properties 47 | 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' from 19 | a Contributor if it was added to the Program by such Contributor itself or 20 | anyone acting on such Contributor's behalf. Contributions do not include 21 | additions to the Program which: (i) are separate modules of software 22 | distributed in conjunction with the Program under their own license 23 | agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement, 34 | including all Contributors. 35 | 36 | 2. GRANT OF RIGHTS 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly perform, 40 | distribute and sublicense the Contribution of such Contributor, if any, and 41 | such derivative works, in source code and object code form. 42 | b) Subject to the terms of this Agreement, each Contributor hereby grants 43 | Recipient a non-exclusive, worldwide, royalty-free patent license under 44 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 45 | transfer the Contribution of such Contributor, if any, in source code and 46 | object code form. This patent license shall apply to the combination of the 47 | Contribution and the Program if, at the time the Contribution is added by 48 | the Contributor, such addition of the Contribution causes such combination 49 | to be covered by the Licensed Patents. The patent license shall not apply 50 | to any other combinations which include the Contribution. No hardware per 51 | se is licensed hereunder. 52 | c) Recipient understands that although each Contributor grants the licenses to 53 | its Contributions set forth herein, no assurances are provided by any 54 | Contributor that the Program does not infringe the patent or other 55 | intellectual property rights of any other entity. Each Contributor 56 | disclaims any liability to Recipient for claims brought by any other entity 57 | based on infringement of intellectual property rights or otherwise. As a 58 | condition to exercising the rights and licenses granted hereunder, each 59 | Recipient hereby assumes sole responsibility to secure any other 60 | intellectual property rights needed, if any. For example, if a third party 61 | patent license is required to allow Recipient to distribute the Program, it 62 | is Recipient's responsibility to acquire that license before distributing 63 | the Program. 64 | d) Each Contributor represents that to its knowledge it has sufficient 65 | copyright rights in its Contribution, if any, to grant the copyright 66 | license set forth in this Agreement. 67 | 68 | 3. REQUIREMENTS 69 | 70 | A Contributor may choose to distribute the Program in object code form under its 71 | own license agreement, provided that: 72 | 73 | a) it complies with the terms and conditions of this Agreement; and 74 | b) its license agreement: 75 | i) effectively disclaims on behalf of all Contributors all warranties and 76 | conditions, express and implied, including warranties or conditions of 77 | title and non-infringement, and implied warranties or conditions of 78 | merchantability and fitness for a particular purpose; 79 | ii) effectively excludes on behalf of all Contributors all liability for 80 | damages, including direct, indirect, special, incidental and 81 | consequential damages, such as lost profits; 82 | iii) states that any provisions which differ from this Agreement are offered 83 | by that Contributor alone and not by any other party; and 84 | iv) states that source code for the Program is available from such 85 | Contributor, and informs licensees how to obtain it in a reasonable 86 | manner on or through a medium customarily used for software exchange. 87 | 88 | When the Program is made available in source code form: 89 | 90 | a) it must be made available under this Agreement; and 91 | b) a copy of this Agreement must be included with each copy of the Program. 92 | Contributors may not remove or alter any copyright notices contained within 93 | the Program. 94 | 95 | Each Contributor must identify itself as the originator of its Contribution, if 96 | any, in a manner that reasonably allows subsequent Recipients to identify the 97 | originator of the Contribution. 98 | 99 | 4. COMMERCIAL DISTRIBUTION 100 | 101 | Commercial distributors of software may accept certain responsibilities with 102 | respect to end users, business partners and the like. While this license is 103 | intended to facilitate the commercial use of the Program, the Contributor who 104 | includes the Program in a commercial product offering should do so in a manner 105 | which does not create potential liability for other Contributors. Therefore, if 106 | a Contributor includes the Program in a commercial product offering, such 107 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 108 | every other Contributor ("Indemnified Contributor") against any losses, damages 109 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 110 | actions brought by a third party against the Indemnified Contributor to the 111 | extent caused by the acts or omissions of such Commercial Contributor in 112 | connection with its distribution of the Program in a commercial product 113 | offering. The obligations in this section do not apply to any claims or Losses 114 | relating to any actual or alleged intellectual property infringement. In order 115 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 116 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 117 | control, and cooperate with the Commercial Contributor in, the defense and any 118 | related settlement negotiations. The Indemnified Contributor may participate in 119 | any such claim at its own expense. 120 | 121 | For example, a Contributor might include the Program in a commercial product 122 | offering, Product X. That Contributor is then a Commercial Contributor. If that 123 | Commercial Contributor then makes performance claims, or offers warranties 124 | related to Product X, those performance claims and warranties are such 125 | Commercial Contributor's responsibility alone. Under this section, the 126 | Commercial Contributor would have to defend claims against the other 127 | Contributors related to those performance claims and warranties, and if a court 128 | requires any other Contributor to pay any damages as a result, the Commercial 129 | Contributor must pay those damages. 130 | 131 | 5. NO WARRANTY 132 | 133 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 134 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 135 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 136 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 137 | Recipient is solely responsible for determining the appropriateness of using and 138 | distributing the Program and assumes all risks associated with its exercise of 139 | rights under this Agreement , including but not limited to the risks and costs 140 | of program errors, compliance with applicable laws, damage to or loss of data, 141 | programs or equipment, and unavailability or interruption of operations. 142 | 143 | 6. DISCLAIMER OF LIABILITY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 146 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 147 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 148 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 149 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 150 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 151 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 152 | 153 | 7. GENERAL 154 | 155 | If any provision of this Agreement is invalid or unenforceable under applicable 156 | law, it shall not affect the validity or enforceability of the remainder of the 157 | terms of this Agreement, and without further action by the parties hereto, such 158 | provision shall be reformed to the minimum extent necessary to make such 159 | provision valid and enforceable. 160 | 161 | If Recipient institutes patent litigation against any entity (including a 162 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 163 | (excluding combinations of the Program with other software or hardware) 164 | infringes such Recipient's patent(s), then such Recipient's rights granted under 165 | Section 2(b) shall terminate as of the date such litigation is filed. 166 | 167 | All Recipient's rights under this Agreement shall terminate if it fails to 168 | comply with any of the material terms or conditions of this Agreement and does 169 | not cure such failure in a reasonable period of time after becoming aware of 170 | such noncompliance. If all Recipient's rights under this Agreement terminate, 171 | Recipient agrees to cease use and distribution of the Program as soon as 172 | reasonably practicable. However, Recipient's obligations under this Agreement 173 | and any licenses granted by Recipient relating to the Program shall continue and 174 | survive. 175 | 176 | Everyone is permitted to copy and distribute copies of this Agreement, but in 177 | order to avoid inconsistency the Agreement is copyrighted and may only be 178 | modified in the following manner. The Agreement Steward reserves the right to 179 | publish new versions (including revisions) of this Agreement from time to time. 180 | No one other than the Agreement Steward has the right to modify this Agreement. 181 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation 182 | may assign the responsibility to serve as the Agreement Steward to a suitable 183 | separate entity. Each new version of the Agreement will be given a 184 | distinguishing version number. The Program (including Contributions) may always 185 | be distributed subject to the version of the Agreement under which it was 186 | received. In addition, after a new version of the Agreement is published, 187 | Contributor may elect to distribute the Program (including its Contributions) 188 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 189 | above, Recipient receives no rights or licenses to the intellectual property of 190 | any Contributor under this Agreement, whether expressly, by implication, 191 | estoppel or otherwise. All rights in the Program not expressly granted under 192 | this Agreement are reserved. 193 | 194 | This Agreement is governed by the laws of the State of New York and the 195 | intellectual property laws of the United States of America. No party to this 196 | Agreement will bring a legal action under this Agreement more than one year 197 | after the cause of action arose. Each party waives its rights to a jury trial in 198 | any resulting litigation. 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resolve a Maven 2 Artifact Using Only A Client Side Tool 2 | 3 | ## What is it? 4 | 5 | A standalone executable jar which uses Eclipse Aether libraries to query and optionally download via HTTP(S) an artifact from a 6 | remote Maven 2 format repository. 7 | 8 | The program resolves artifacts with: 9 | 10 | - the highest versioned within a specified Maven 2 version range ie. LATEST 11 | - the highest timestamped SNAPSHOT within a specified base SNAPSHOT version ie. 1.0-SNAPSHOT 12 | - the highest release ( non-temporary, non-snapshot) version ie. RELEASE 13 | 14 | ## How to build the stand alone jar? 15 | 16 | The build uses a modified https://github.com/dmitart/scriptjar to compile and generate a single uber jar executable. 17 | 18 | So you need Groovy to build it. 19 | 20 | The scriptjar modification I made was to optionally include resources in the jar, in this case some log configuration. 21 | 22 | A simple bash script wraps the build process. 23 | 24 | ``` 25 | ./build.sh 26 | ``` 27 | 28 | ## Is this a Sonatype supported product? 29 | 30 | No. The program is provided as-is, as an example. Feel free to fork it and adjust to your own tooling environment. 31 | 32 | ## Is this a replacement for the Nexus 2 REST APIs missing in Nexus 3? 33 | 34 | This program is meant as an example alternative client side only replacement for the following 35 | [Nexus Repository Manager 2.x REST resources](https://support.sonatype.com/hc/en-us/articles/213465488): 36 | 37 | ``` 38 | /service/local/artifact/maven/content 39 | /service/local/artifact/maven/redirect 40 | /service/local/artifact/maven/resolve 41 | ``` 42 | 43 | It demonstrates how you can use a standalone client to accomplish a similar result as the Nexus 2.x REST APIs against 44 | any remote Maven 2 format repository accessible over HTTP including Nexus 2 or 3. 45 | 46 | ## Will Sonatype ever implement a REST API in Nexus 3? 47 | 48 | Yes. It is expected a search and component REST API will eventually be available. 49 | 50 | ## What are the limitations of this program? 51 | 52 | The main limitation of this is it only gets the highest version by comparing all known versions from the remote - which is not 53 | necessarily the often wanted 'latest deployed' version from your CI tool. 54 | 55 | This only works if the remote is properly implementing a Maven 2 repository format with proper maven-metadata.xml files. 56 | 57 | If the remote repository has Maven metadata that is not giving you results you expect, this tool cannot fix that. 58 | 59 | ## Can I get latest SNAPSHOT with this program? 60 | 61 | Sure, the program doesn't mind. As long as you understand simple stuff like `1.7-SNAPSHOT` is higher than `1.6` release for example. 62 | 63 | Example, to get the latest timestamped snapshot of all `1.7-SNAPSHOT` versions, just specify `1.7-SNAPSHOT` as the actual version. 64 | 65 | ## Can I specify LATEST or RELEASE as the version? 66 | 67 | Does the script accept these? Grudgingly yes...but I feel dirty. 68 | 69 | First, some short history. 70 | 71 | These version names are pseudo values, no longer directly supported in Maven 3 or Nexus 3 - their use encourages non-repeatable builds. 72 | 73 | The terms were defined as: 74 | 75 | LATEST: get the highest version, snapshot or release. Some people expect this to match the value in the `` element inside maven-metadata.xml. [THIS IS BAD!](https://support.sonatype.com/hc/en-us/articles/213464638-Why-are-the-latest-and-release-tags-in-maven-metadata-xml-not-being-updated-after-deploying-artifacts-). 76 | 77 | RELEASE ( using Apache Maven 2.x ): get the version inside the `` element in the GA level maven-metadata.xml file on the remote. [THIS IS BAD!](https://support.sonatype.com/hc/en-us/articles/213464638-Why-are-the-latest-and-release-tags-in-maven-metadata-xml-not-being-updated-after-deploying-artifacts-). 78 | 79 | RELEASE ( using Nexus 2 REST API and this script ): get the highest release version. 80 | 81 | LATEST is easy to simulate - the program converts this to an open ended version range. 82 | 83 | RELEASE is implemented as get the highest non-snapshot/non-temporary version from the entire list of available versions. 84 | 85 | For a longer history, maybe [this helps](http://stackoverflow.com/questions/30571/how-do-i-tell-maven-to-use-the-latest-version-of-a-dependency). 86 | 87 | ## How are artifact versions compared? 88 | 89 | Versions are compared lexicographically with [some special cases](https://github.com/eclipse/aether-core/blob/master/aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersion.java#L183). 90 | 91 | A good resource to understand Apache Maven style versions and ranges in general is this doc: 92 | 93 | https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN8855 94 | 95 | For specifics, understand the [GenericVersionScheme](https://github.com/eclipse/aether-core/blob/master/aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersionScheme.java) 96 | and the [GenericVersion](https://github.com/eclipse/aether-core/blob/master/aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersion.java) 97 | 98 | Also become familiar with getting the highest version using a [VersionRange](https://github.com/eclipse/aether-core/blob/master/aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersionRange.java) with this tool. 99 | 100 | ## How do I specify the artifact to get? 101 | 102 | Unqualified program arguments are treated as long form artifact coordinates. You can specify more than one if you like. Surround those in quotes for the best results. 103 | 104 | The expected artifact coordinate format is: 105 | 106 | ``` 107 | :[:[:]]:[] 108 | ``` 109 | 110 | If you type an invalid artifact coordinate, you will get a message telling you the expected format. 111 | 112 | Example: 113 | 114 | ``` 115 | java -jar artifactResolver.jar @script.args commons-dbutils 116 | ERROR: Bad artifact coordinates commons-dbutils, expected format is :[:[:]]: 117 | ``` 118 | 119 | Note: There is one _special_ variation that does not require version - see next section. 120 | 121 | ## How do I ask for the highest version of a group and artifact id combination, without adding a version or range? 122 | 123 | The program will accept a special artifact coordinate variation consisting of only groupid and artifactid without a version, extension or classifer value. 124 | 125 | In that case, the program treats the request as if you specified an unbounded upper range on the version. In other words a Maven 2 version range 126 | equal to `[0,)`, which means a version no lower than zero and as high a version as is available? 127 | 128 | Also the extension and classifier are defaults in this case of jar and empty value respectively. 129 | 130 | ``` 131 | java -jar artifactResolver.jar @script.args commons-dbutils:commons-dbutils 132 | ``` 133 | 134 | ## Can I get the highest version in a bounded range of versions? 135 | 136 | Sure. All calculations of the range are done entirely on inside this client. The version value of the artifact coordinate can be any valid Maven 2 version range. 137 | 138 | ## How do I specify authentication for the remote repo? 139 | 140 | Specify a username and password with the -u,--user argument. Example: 141 | 142 |
143 | java -jar artifactResolver.jar --user admin:admin123 --remote.repo "http://localhost:8081/nexus/content/groups/public/" commons-dbutils:commons:dbutils
144 | 
145 | 146 | ## How do I avoid typing my password in the clear? 147 | 148 | The script uses [Groovy CliBuilder](http://docs.groovy-lang.org/next/html/gapi/groovy/util/CliBuilder.html) to process arguments. You can create a file and put arguments, like auth, in that file instead. 149 | 150 | Example: 151 | 152 | 1. Create a file named `script.args` in the same directory as the jar 153 | 2. The contents of the file can contain an argument on each line, like this: 154 |
155 |      --user
156 |      admin:admin123
157 |      --repo.remote
158 |      "http://localhost:8081/nexus/content/groups/public/"
159 |      --repo.local
160 |      "./target/other-repo"
161 |      
162 | 3. Use the special `@` prefix and pass the file name as an argument to the script. Each line of the file will be read as if 163 | it was passed on the command line. Example: 164 |
165 |     java -jar artifactResolver.jar @script.args commons-dbutils:commons-dbutils
166 |     
167 | 168 | ## Well I tried it and something went horribly wrong! 169 | 170 | This program is perfect. It's your fault. 171 | 172 | At least you can turn on verbose logging using Java system properties. 173 | 174 | The Jar responds to log levels set according to system properties a la SimpleLogger. 175 | 176 | See http://www.slf4j.org/api/org/slf4j/impl/SimpleLogger.html 177 | 178 | Example - turn on http client logging: 179 | 180 | ``` 181 | java -Dorg.slf4j.simpleLogger.log.org.apache.http=DEBUG -jar artifactResolver.jar @script.args commons-dbutils:commons-dbutils 182 | ``` 183 | 184 | ## Well it worked. Once. Now it won't send anymore requests to the remote. What up? 185 | 186 | The script caches metadata in the local repo, much like Apache Maven itself would, and it likely hasn't expired yet. Just delete the local repo and try again. 187 | 188 | ## Dude! I recognize some of this code!! 189 | 190 | Sure - its almost a copy-pasta of bits inside Aether Demo project. 191 | 192 | https://github.com/eclipse/aether-demo/tree/master/aether-demo-snippets 193 | 194 | ## Dude! This is awesome!!! 195 | 196 | Well... keep in mind this program can help solve only one use case of getting the 'latest' 197 | thing. 198 | 199 | There are many other client side tools out there that do similar things ie. ivy cli, grape cli 200 | 201 | Apparently though the burden of learning them or the overhead of installing them is too much. 202 | 203 | And besides Nexus 2 has a REST API that does what you want. 204 | 205 | ..and Nexus 3 should get around to having a REST API that does this magic for you eventually. 206 | 207 | ## Dude! This is lame!!! 208 | 209 | Sure. It's free though. Free as in Free Range Turkey...the rest is up to you. 210 | 211 | ## Dude! I've read this far. What do I win? 212 | 213 | Wow. You could have downloaded your favorite artifact by now. 214 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | cp LICENSE src/artifact-resolver-LICENSE.txt 4 | cp scriptjar.groovy src 5 | cd src 6 | groovy scriptjar.groovy artifactResolver.groovy artifactResolver.jar "simplelogger.properties" "artifact-resolver-LICENSE.txt" 7 | mv artifactResolver.jar ../ 8 | rm artifact-resolver-LICENSE.txt 9 | rm scriptjar.groovy 10 | cd - 11 | -------------------------------------------------------------------------------- /scriptjar.groovy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | /* 4 | MIT License 5 | 6 | Copyright (c) 2016 Dmitrijs Artjomenko 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | // modified version of https://github.com/dmitart/scriptjar 28 | 29 | import groovy.grape.Grape 30 | import org.codehaus.groovy.control.CompilationUnit 31 | import org.codehaus.groovy.control.CompilerConfiguration 32 | import org.codehaus.groovy.control.SourceUnit 33 | import org.codehaus.groovy.tools.GroovyClass 34 | 35 | import java.util.jar.JarEntry 36 | import java.util.jar.JarFile 37 | import java.util.jar.JarOutputStream 38 | 39 | import groovy.io.FileType 40 | 41 | import static org.codehaus.groovy.control.Phases.CLASS_GENERATION 42 | 43 | List compile(String prefix, File file) { 44 | List classes = [] as List 45 | 46 | // set up classloader with all the grapes loaded 47 | final GroovyClassLoader classLoader = new GroovyClassLoader() 48 | classLoader.parseClass(file.text) 49 | getSiblingGroovyFiles(file).each { 50 | classLoader.parseClass(it.text) 51 | } 52 | 53 | // disable groovy grapes - we're resolving these ahead of time 54 | CompilerConfiguration compilerConfig = new CompilerConfiguration() 55 | Set disabledTransforms = ['groovy.grape.GrabAnnotationTransformation'] as Set 56 | compilerConfig.setDisabledGlobalASTTransformations(disabledTransforms) 57 | 58 | // compile main class 59 | CompilationUnit unit = new CompilationUnit(compilerConfig, null, classLoader) 60 | unit.addSource(SourceUnit.create(prefix, file.text)) 61 | println "${file.name} => ${prefix} (main class)" 62 | unit.compile(CLASS_GENERATION) 63 | classes += unit.getClasses() 64 | 65 | // compile groovy files in same folder 66 | getSiblingGroovyFiles(file).each { 67 | CompilationUnit dependentUnit = new CompilationUnit(compilerConfig, null, classLoader) 68 | def className = it.name.replaceAll(/\.groovy$/, '') 69 | println "${it.name} => ${className} (lib)" 70 | dependentUnit.addSource(SourceUnit.create(className, it.text)) 71 | dependentUnit.compile(CLASS_GENERATION) 72 | 73 | classes += dependentUnit.getClasses() 74 | } 75 | 76 | return classes 77 | } 78 | 79 | List getSiblingGroovyFiles(File mainGroovyFile) { 80 | def groovyFileRe = /.*\.groovy$/ 81 | List files = [] as List 82 | mainGroovyFile.getAbsoluteFile().getParentFile().eachFile(FileType.FILES) { 83 | if (!it.hidden && it.name =~ groovyFileRe && it.name != mainGroovyFile.name && it.name != 'scriptjar.groovy') { 84 | files << it 85 | } 86 | } 87 | 88 | return files 89 | } 90 | 91 | List getGroovyLibs(List neededJars) { 92 | def libs = new File('.') 93 | if (System.getenv('GROOVY_HOME')) { 94 | libs = new File(System.getenv('GROOVY_HOME'), 'lib') 95 | }else if( System.getProperty("user.home") && 96 | new File( System.getProperty("user.home"), '.groovy/grapes' ).exists() ) { 97 | libs = new File( System.getProperty("user.home"), '.groovy/grapes' ) 98 | } else { 99 | println "Cann't find GROOVY_HOME" 100 | System.exit(1) 101 | } 102 | def groovylibs = libs.listFiles().findAll{jar -> 103 | neededJars.any{needed -> jar.name =~ needed } 104 | } 105 | if (groovylibs) { 106 | return groovylibs 107 | } else { 108 | println "Can't find Groovy lib in ${libs.absolutePath}, specify it manually as Grab dependency" 109 | System.exit(1) 110 | } 111 | } 112 | 113 | List dependencies(File source) { 114 | final GroovyClassLoader classLoader = new GroovyClassLoader() 115 | classLoader.parseClass(source.text) 116 | getSiblingGroovyFiles(source).each { 117 | classLoader.parseClass(it.text) 118 | } 119 | def files = Grape.resolve([:], Grape.listDependencies(classLoader)).collect{ new JarFile(it.path) } 120 | files.addAll(getGroovyLibs([/groovy-\d+.\d+.\d+.jar/]).collect{ new JarFile(it) }) 121 | 122 | return files 123 | } 124 | 125 | void writeJarEntry(JarOutputStream jos, JarEntry entry, byte[] data) { 126 | entry.setSize(data.length) 127 | jos.putNextEntry(entry) 128 | jos.write(data) 129 | } 130 | 131 | byte[] createJar(String prefix, List jars, List compiled, File resourcesBase, List resources) { 132 | ByteArrayOutputStream output = new ByteArrayOutputStream() 133 | JarOutputStream jos = new JarOutputStream(output) 134 | 135 | jos.putNextEntry(new JarEntry('META-INF/')) 136 | writeJarEntry(jos, new JarEntry('META-INF/MANIFEST.MF'), "Manifest-Version: 1.0\nMain-Class: ${prefix}\n".getBytes()) 137 | compiled.each { 138 | writeJarEntry(jos, new JarEntry("${it.name}.class"), it.bytes) 139 | } 140 | 141 | def directories = ['META-INF/', 'META-INF/MANIFEST.MF'] 142 | 143 | jars.each {file -> 144 | println "Merging ${file.name}" 145 | file.entries().each { JarEntry entry -> 146 | if (!directories.contains(entry.name)) { 147 | writeJarEntry(jos, entry, file.getInputStream(entry).getBytes()) 148 | directories << entry.name 149 | } 150 | } 151 | } 152 | resources.each { resource -> 153 | println "Matching pattern ${resource}" 154 | List matchedResources = new FileNameFinder().getFileNames(resourcesBase.getAbsolutePath(), resource ) 155 | println "Matched resources $matchedResources" 156 | 157 | matchedResources.each { matchedResource -> 158 | def entryName = matchedResource.replaceFirst(resourcesBase.getAbsolutePath() + '/', '') 159 | if (!directories.contains(entryName)) { 160 | println "Merging $matchedResource as jar entry $entryName" 161 | writeJarEntry(jos, new JarEntry(entryName), new File(matchedResource).newInputStream().getBytes()) 162 | directories << entryName 163 | } 164 | } 165 | 166 | } 167 | 168 | jos.close() 169 | return output.toByteArray() 170 | } 171 | 172 | byte[] createUberjar(File file, String prefix, List resources) { 173 | List compiled = compile(prefix, file) 174 | def jars = dependencies(file) 175 | return createJar(prefix, jars, compiled, file.getAbsoluteFile().getParentFile(), resources) 176 | } 177 | 178 | 179 | if (args.size() < 2) { 180 | println "Usage: ./scriptjar.groovy input.groovy output.jar [additional resources]" 181 | System.exit(1) 182 | } 183 | 184 | File file = new File(args[0]) 185 | String prefix = file.name.substring(0, file.name.indexOf('.')) 186 | 187 | List resourcePatterns = Arrays.asList(args).subList(2, args.size()) 188 | println "additional args $resourcePatterns" 189 | new File(args[1]).withOutputStream { 190 | it << createUberjar(file, prefix, resourcePatterns) 191 | } 192 | -------------------------------------------------------------------------------- /src/AetherUtil.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present Sonatype, Inc. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, 5 | * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. 6 | */ 7 | 8 | import org.apache.maven.repository.internal.MavenRepositorySystemUtils 9 | import org.eclipse.aether.DefaultRepositorySystemSession 10 | import org.eclipse.aether.RepositorySystem 11 | import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory 12 | import org.eclipse.aether.impl.DefaultServiceLocator 13 | import org.eclipse.aether.repository.Authentication 14 | import org.eclipse.aether.repository.LocalRepository 15 | import org.eclipse.aether.repository.RemoteRepository 16 | import org.eclipse.aether.spi.connector.RepositoryConnectorFactory 17 | import org.eclipse.aether.spi.connector.transport.TransporterFactory 18 | import org.eclipse.aether.transport.file.FileTransporterFactory 19 | import org.eclipse.aether.transport.http.HttpTransporterFactory 20 | import org.eclipse.aether.util.repository.AuthenticationBuilder 21 | 22 | 23 | class AetherUtil 24 | { 25 | 26 | static DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system, LocalRepository localRepo ) 27 | { 28 | DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession() 29 | 30 | session.setLocalRepositoryManager( system.newLocalRepositoryManager( session, localRepo ) ) 31 | 32 | session.setTransferListener( new ConsoleTransferListener() ) 33 | session.setRepositoryListener( new ConsoleRepositoryListener() ) 34 | 35 | // uncomment to generate dirty trees 36 | // session.setDependencyGraphTransformer( null ); 37 | 38 | return session 39 | } 40 | 41 | static List newRepositories(RemoteRepository remoteRepository) 42 | { 43 | return new ArrayList( Arrays.asList( remoteRepository != null ? remoteRepository : newCentralRepository() ) ) 44 | } 45 | 46 | static RemoteRepository newCentralRepository() 47 | { 48 | return new RemoteRepository.Builder( "central", "default", "https://repo1.maven.org/maven2/" ).build() 49 | } 50 | 51 | static RemoteRepository newRemoteRepository(String url, String user, String pass){ 52 | 53 | RemoteRepository.Builder builder = new RemoteRepository.Builder( "custom", "default", url ) 54 | 55 | if(user != null && pass != null){ 56 | Authentication auth = new AuthenticationBuilder().addUsername(user).addPassword(pass).build() 57 | builder.setAuthentication(auth) 58 | } 59 | 60 | return builder.build() 61 | 62 | } 63 | 64 | static RepositorySystem newRepositorySystem() 65 | { 66 | /* 67 | * Aether's components implement org.eclipse.aether.spi.locator.Service to ease manual wiring and using the 68 | * prepopulated DefaultServiceLocator, we only need to register the repository connector and transporter 69 | * factories. 70 | */ 71 | DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator() 72 | locator.addService( RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class ) 73 | locator.addService( TransporterFactory.class, FileTransporterFactory.class ) 74 | locator.addService( TransporterFactory.class, HttpTransporterFactory.class ) 75 | 76 | locator.setErrorHandler( new DefaultServiceLocator.ErrorHandler() 77 | { 78 | @Override 79 | void serviceCreationFailed(Class type, Class impl, Throwable exception ) 80 | { 81 | exception.printStackTrace() 82 | } 83 | } ) 84 | 85 | return locator.getService( RepositorySystem.class ) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/ConsoleRepositoryListener.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present Sonatype, Inc. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, 5 | * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. 6 | */ 7 | 8 | import org.eclipse.aether.AbstractRepositoryListener 9 | import org.eclipse.aether.RepositoryEvent 10 | 11 | /** 12 | * A simplistic repository listener that logs events to the console. 13 | */ 14 | class ConsoleRepositoryListener 15 | extends AbstractRepositoryListener 16 | { 17 | 18 | private PrintStream out 19 | 20 | ConsoleRepositoryListener() 21 | { 22 | this( null ) 23 | } 24 | 25 | ConsoleRepositoryListener(PrintStream out ) 26 | { 27 | this.out = ( out != null ) ? out : System.out 28 | } 29 | 30 | void artifactDeployed(RepositoryEvent event ) 31 | { 32 | out.println( "Deployed " + event.getArtifact() + " to " + event.getRepository() ) 33 | } 34 | 35 | void artifactDeploying(RepositoryEvent event ) 36 | { 37 | out.println( "Deploying " + event.getArtifact() + " to " + event.getRepository() ) 38 | } 39 | 40 | void artifactDescriptorInvalid(RepositoryEvent event ) 41 | { 42 | out.println( "Invalid artifact descriptor for " + event.getArtifact() + ": " 43 | + event.getException().getMessage() ) 44 | } 45 | 46 | void artifactDescriptorMissing(RepositoryEvent event ) 47 | { 48 | out.println( "Missing artifact descriptor for " + event.getArtifact() ) 49 | } 50 | 51 | void artifactInstalled(RepositoryEvent event ) 52 | { 53 | out.println( "Installed " + event.getArtifact() + " to " + event.getFile() ) 54 | } 55 | 56 | void artifactInstalling(RepositoryEvent event ) 57 | { 58 | out.println( "Installing " + event.getArtifact() + " to " + event.getFile() ) 59 | } 60 | 61 | void artifactResolved(RepositoryEvent event ) 62 | { 63 | out.println( "Resolved artifact " + event.getArtifact() + " from " + event.getRepository() ) 64 | } 65 | 66 | void artifactDownloading(RepositoryEvent event ) 67 | { 68 | out.println( "Downloading artifact " + event.getArtifact() + " from " + event.getRepository() ) 69 | } 70 | 71 | void artifactDownloaded(RepositoryEvent event ) 72 | { 73 | out.println( "Downloaded artifact " + event.getArtifact() + " from " + event.getRepository() ) 74 | } 75 | 76 | void artifactResolving(RepositoryEvent event ) 77 | { 78 | out.println( "Resolving artifact " + event.getArtifact() ) 79 | } 80 | 81 | void metadataDeployed(RepositoryEvent event ) 82 | { 83 | out.println( "Deployed " + event.getMetadata() + " to " + event.getRepository() ) 84 | } 85 | 86 | void metadataDeploying(RepositoryEvent event ) 87 | { 88 | out.println( "Deploying " + event.getMetadata() + " to " + event.getRepository() ) 89 | } 90 | 91 | void metadataInstalled(RepositoryEvent event ) 92 | { 93 | out.println( "Installed " + event.getMetadata() + " to " + event.getFile() ) 94 | } 95 | 96 | void metadataInstalling(RepositoryEvent event ) 97 | { 98 | out.println( "Installing " + event.getMetadata() + " to " + event.getFile() ) 99 | } 100 | 101 | void metadataInvalid(RepositoryEvent event ) 102 | { 103 | out.println( "Invalid metadata " + event.getMetadata() ) 104 | } 105 | 106 | void metadataResolved(RepositoryEvent event ) 107 | { 108 | out.println( "Resolved metadata " + event.getMetadata() + " from " + event.getRepository() ) 109 | } 110 | 111 | void metadataResolving(RepositoryEvent event ) 112 | { 113 | out.println( "Resolving metadata " + event.getMetadata() + " from " + event.getRepository() ) 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/ConsoleTransferListener.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present Sonatype, Inc. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, 5 | * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. 6 | */ 7 | 8 | import org.eclipse.aether.transfer.AbstractTransferListener 9 | import org.eclipse.aether.transfer.MetadataNotFoundException 10 | import org.eclipse.aether.transfer.TransferEvent 11 | import org.eclipse.aether.transfer.TransferResource 12 | 13 | import java.text.DecimalFormat 14 | import java.text.DecimalFormatSymbols 15 | import java.util.concurrent.ConcurrentHashMap 16 | 17 | class ConsoleTransferListener 18 | extends AbstractTransferListener 19 | { 20 | 21 | private PrintStream out 22 | 23 | private Map downloads = new ConcurrentHashMap() 24 | 25 | private int lastLength 26 | 27 | ConsoleTransferListener() 28 | { 29 | this( null ) 30 | } 31 | 32 | ConsoleTransferListener(PrintStream out ) 33 | { 34 | this.out = ( out != null ) ? out : System.out 35 | } 36 | 37 | @Override 38 | void transferInitiated(TransferEvent event ) 39 | { 40 | String message = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading" 41 | 42 | out.println( message + ": " + event.getResource().getRepositoryUrl() + event.getResource().getResourceName() ) 43 | } 44 | 45 | @Override 46 | void transferProgressed(TransferEvent event ) 47 | { 48 | TransferResource resource = event.getResource() 49 | downloads.put( resource, Long.valueOf( event.getTransferredBytes() ) ) 50 | 51 | StringBuilder buffer = new StringBuilder( 64 ) 52 | 53 | for ( Map.Entry entry : downloads.entrySet() ) 54 | { 55 | long total = entry.getKey().getContentLength() 56 | long complete = entry.getValue().longValue() 57 | 58 | buffer.append( getStatus( complete, total ) ).append( " " ) 59 | } 60 | 61 | int pad = lastLength - buffer.length() 62 | lastLength = buffer.length() 63 | this.pad( buffer, pad ) 64 | buffer.append( '\r' ) 65 | 66 | out.print( buffer ) 67 | } 68 | 69 | private String getStatus( long complete, long total ) 70 | { 71 | if ( total >= 1024 ) 72 | { 73 | return toKB( complete ) + "/" + toKB( total ) + " KB " 74 | } 75 | else if ( total >= 0 ) 76 | { 77 | return complete + "/" + total + " B " 78 | } 79 | else if ( complete >= 1024 ) 80 | { 81 | return toKB( complete ) + " KB " 82 | } 83 | else 84 | { 85 | return complete + " B " 86 | } 87 | } 88 | 89 | private void pad( StringBuilder buffer, int spaces ) 90 | { 91 | String block = " " 92 | while ( spaces > 0 ) 93 | { 94 | int n = Math.min( spaces, block.length() ) 95 | buffer.append( block, 0, n ) 96 | spaces -= n 97 | } 98 | } 99 | 100 | @Override 101 | void transferSucceeded(TransferEvent event ) 102 | { 103 | transferCompleted( event ) 104 | 105 | TransferResource resource = event.getResource() 106 | long contentLength = event.getTransferredBytes() 107 | if ( contentLength >= 0 ) 108 | { 109 | String type = ( event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded" ) 110 | String len = contentLength >= 1024 ? toKB( contentLength ) + " KB" : contentLength + " B" 111 | 112 | String throughput = "" 113 | long duration = System.currentTimeMillis() - resource.getTransferStartTime() 114 | if ( duration > 0 ) 115 | { 116 | long bytes = contentLength - resource.getResumeOffset() 117 | DecimalFormat format = new DecimalFormat( "0.0", new DecimalFormatSymbols( Locale.ENGLISH ) ) 118 | double kbPerSec = ( bytes / 1024.0 ) / ( duration / 1000.0 ) 119 | throughput = " at " + format.format( kbPerSec ) + " KB/sec" 120 | } 121 | 122 | out.println( type + ": " + resource.getRepositoryUrl() + resource.getResourceName() + " (" + len 123 | + throughput + ")" ) 124 | } 125 | } 126 | 127 | @Override 128 | void transferFailed(TransferEvent event ) 129 | { 130 | transferCompleted( event ) 131 | 132 | if ( !( event.getException() instanceof MetadataNotFoundException ) ) 133 | { 134 | event.getException().printStackTrace( out ) 135 | } 136 | } 137 | 138 | private void transferCompleted( TransferEvent event ) 139 | { 140 | downloads.remove( event.getResource() ) 141 | 142 | StringBuilder buffer = new StringBuilder( 64 ) 143 | pad( buffer, lastLength ) 144 | buffer.append( '\r' ) 145 | out.print( buffer ) 146 | } 147 | 148 | void transferCorrupted(TransferEvent event ) 149 | { 150 | event.getException().printStackTrace( out ) 151 | } 152 | 153 | protected long toKB( long bytes ) 154 | { 155 | return ( bytes + 1023 ) / 1024 156 | } 157 | 158 | } -------------------------------------------------------------------------------- /src/artifactResolver.groovy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | /* 4 | * Copyright (c) 2017-present Sonatype, Inc. All rights reserved. 5 | * 6 | * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, 7 | * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. 8 | */ 9 | 10 | @Grapes([ 11 | @Grab('org.slf4j:jcl-over-slf4j:1.7.22'), 12 | @Grab('org.slf4j:slf4j-api:1.7.22'), 13 | @Grab('org.slf4j:slf4j-simple:1.7.22'), 14 | @GrabConfig(systemClassLoader = true) 15 | ]) 16 | 17 | @Grab('org.apache.maven:maven-aether-provider:3.3.9') 18 | @Grab('org.eclipse.aether:aether-impl:1.1.0;force=true') 19 | @Grab('org.eclipse.aether:aether-transport-http:1.1.0') 20 | @Grab('org.eclipse.aether:aether-transport-file:1.1.0') 21 | @Grab('org.eclipse.aether:aether-connector-basic:1.1.0') 22 | // commons cli is Grabbed to help scriptjar.groovy to bundle it, otherwise normally picked up from 23 | @Grab('commons-cli:commons-cli:1.2') 24 | 25 | import groovy.util.logging.Slf4j 26 | import org.eclipse.aether.RepositorySystem 27 | import org.eclipse.aether.RepositorySystemSession 28 | import org.eclipse.aether.artifact.Artifact 29 | import org.eclipse.aether.artifact.DefaultArtifact 30 | import org.eclipse.aether.repository.LocalRepository 31 | import org.eclipse.aether.repository.RemoteRepository 32 | import org.eclipse.aether.resolution.ArtifactRequest 33 | import org.eclipse.aether.resolution.ArtifactResult 34 | import org.eclipse.aether.resolution.VersionRangeRequest 35 | import org.eclipse.aether.resolution.VersionRangeResult 36 | import org.eclipse.aether.version.Version 37 | 38 | import static AetherUtil.* 39 | 40 | @Slf4j 41 | class ArtifactResolver 42 | { 43 | 44 | private String artifactName 45 | 46 | private RemoteRepository remote 47 | 48 | private LocalRepository local 49 | 50 | final static String OPEN_RANGE = '[0,)' 51 | 52 | ArtifactResolver(String artifactName, RemoteRepository remote, LocalRepository local) { 53 | this.artifactName = artifactName 54 | this.remote = remote 55 | this.local = local 56 | } 57 | 58 | void resolve(boolean download) { 59 | 60 | // missing version == highest version, default classifier, default extension 61 | if (artifactName.split(':').length == 2) { 62 | artifactName = "$artifactName:$OPEN_RANGE" 63 | } 64 | 65 | // create artifact first to verify artifact coordinates 66 | Artifact artifact = new DefaultArtifact(artifactName) 67 | 68 | boolean releaseWanted = 'RELEASE' == artifact.getVersion() 69 | if (['LATEST', 'RELEASE'].contains(artifact.getVersion())) { 70 | artifact.setVersion(OPEN_RANGE) 71 | } 72 | 73 | RepositorySystem system = newRepositorySystem() 74 | RepositorySystemSession session = newRepositorySystemSession(system, local) 75 | List remotes = newRepositories(remote) 76 | 77 | VersionRangeRequest rangeRequest = new VersionRangeRequest() 78 | rangeRequest.setArtifact(artifact) 79 | rangeRequest.setRepositories(remotes) 80 | 81 | VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest) 82 | 83 | 84 | Version highestVersion 85 | if (releaseWanted) { 86 | // a 'release' means anything but SNAPSHOT - a version that is not temporary 87 | // this may include versions such as -alpha, -beta, etc, which some people may find is not semantically a release 88 | highestVersion = rangeResult.getVersions().reverse().find { v -> !v.toString().endsWith('-SNAPSHOT') } 89 | } 90 | else { 91 | highestVersion = rangeResult.getHighestVersion() 92 | } 93 | 94 | println("Highest version " + highestVersion + " from repository " 95 | + rangeResult.getRepository(highestVersion)) 96 | 97 | 98 | if (download) { 99 | ArtifactRequest artifactRequest = new ArtifactRequest() 100 | 101 | Artifact toDownload = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), 102 | artifact.getClassifier(), artifact.getExtension(), highestVersion.toString()) 103 | artifactRequest.setArtifact(toDownload) 104 | artifactRequest.setRepositories(remotes) 105 | 106 | ArtifactResult artifactResult = system.resolveArtifact(session, artifactRequest) 107 | 108 | artifact = artifactResult.getArtifact() 109 | 110 | println("$artifact resolved to ${artifact.getFile()}") 111 | } 112 | 113 | } 114 | } 115 | 116 | 117 | // =========== ARGS ============== 118 | // args processing 119 | def cli = new CliBuilder(usage: "java -jar ${artifactResolver.class.name}.jar [options] ") 120 | cli.with { 121 | u longOpt: 'user', args: 1, required: false, 'remote repo username:password' 122 | r longOpt: 'repo.remote', args: 1, required: false, 'remote repo url (default: central)' 123 | l longOpt: 'repo.local', args: 1, required: false, 'local repo directory (default ./target/local-repo)' 124 | n longOpt: 'no-download', required: false, 'resolve version only, do not download artifact ( default: false )' 125 | } 126 | cli.setFooter(''' 127 | You can specify one or more artifact coordinates of the form: 128 | 129 | :[:[:]][:] 130 | 131 | The default repo.remote url is: https://repo1.maven.org/maven2/ 132 | 133 | ''') 134 | 135 | 136 | def options = cli.parse(args) 137 | if (options == null || options.arguments().size() == 0) { 138 | cli.usage() 139 | System.exit(1) 140 | } 141 | 142 | RemoteRepository remoteRepo = null 143 | String username = null 144 | String password = null 145 | if (options.r) { 146 | if (options.u) { 147 | String[] up = options.u.split(':') 148 | if (up.size() < 2) { 149 | System.err.println('colon missing from user option. Could not determine password') 150 | System.exit(1) 151 | } 152 | else if (up.size() > 2) { 153 | System.err.println('more than one colon detected.') 154 | System.exit(1) 155 | } 156 | username = up[0] 157 | password = up[1] 158 | } 159 | 160 | remoteRepo = newRemoteRepository(options.r as String, username, password) 161 | } 162 | else { 163 | remoteRepo = newCentralRepository() 164 | } 165 | 166 | String localRepoPath = options.l ? options.l as String : 'target/local-repo' 167 | LocalRepository localRepo = new LocalRepository(localRepoPath) 168 | 169 | println "Local Repo: $localRepo" 170 | println "Remote Repo: $remoteRepo" 171 | if (remoteRepo.authentication) { 172 | println "Auth: $remoteRepo.authentication" 173 | } 174 | println "Download?: ${!options.n}" 175 | options.arguments().each { 176 | println "Artifact: $it" 177 | new ArtifactResolver(it, remoteRepo, localRepo).resolve(!options.n as boolean) 178 | } 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /src/simplelogger.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017-present Sonatype, Inc. All rights reserved. 3 | # 4 | # This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, 5 | # which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. 6 | # 7 | 8 | # Default Overrides 9 | org.slf4j.simpleLogger.defaultLogLevel=INFO 10 | org.slf4j.simpleLogger.showDateTime=true 11 | org.slf4j.simpleLogger.showThreadName=false 12 | org.slf4j.simpleLogger.showLogName=true 13 | org.slf4j.simpleLogger.logFile=System.err 14 | org.slf4j.simpleLogger.levelInBrackets=true 15 | org.slf4j.simpleLogger.warnLevelString=WARN 16 | 17 | # HTTP logging overrides 18 | org.slf4j.simpleLogger.log.org.apache.http=INFO 19 | org.slf4j.simpleLogger.log.org.apache.http.wire=ERROR --------------------------------------------------------------------------------