├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── LICENSE.txt ├── README.md ├── RELEASE.md ├── app.yml ├── jreleaser.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── renovate.json └── src └── main ├── java └── org │ └── codejive │ └── jpm │ ├── Jpm.java │ ├── Main.java │ ├── json │ └── AppInfo.java │ └── util │ ├── FileUtils.java │ ├── ResolverUtils.java │ ├── SearchResult.java │ ├── SearchUtils.java │ ├── SyncStats.java │ └── Version.java └── resources └── META-INF └── native-image └── maven-model └── resource-config.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | workflow_dispatch: 13 | push: 14 | branches: [ "main" ] 15 | pull_request: 16 | branches: [ "main" ] 17 | 18 | jobs: 19 | build: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up JDK 11 26 | uses: actions/setup-java@v4 27 | with: 28 | java-version: '11' 29 | distribution: 'temurin' 30 | cache: maven 31 | - name: Build with Maven 32 | run: mvn -B verify jreleaser:assemble -Prelease --file pom.xml 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | deps/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/ 9 | .idea/modules.xml 10 | .idea/jarRepositories.xml 11 | .idea/compiler.xml 12 | .idea/libraries/ 13 | *.iws 14 | *.iml 15 | *.ipr 16 | 17 | ### Eclipse ### 18 | .apt_generated 19 | .classpath 20 | .factorypath 21 | .project 22 | .settings 23 | .springBeans 24 | .sts4-cache 25 | 26 | ### NetBeans ### 27 | /nbproject/private/ 28 | /nbbuild/ 29 | /dist/ 30 | /nbdist/ 31 | /.nb-gradle/ 32 | build/ 33 | !**/src/main/**/build/ 34 | !**/src/test/**/build/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Mac OS ### 40 | .DS_Store 41 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jpm - Java Package Manager 2 | 3 | A simple command line tool to manage Maven dependencies for Java projects that are not using build systems like Maven 4 | or Gradle. 5 | 6 | It takes inspiration from Node's npm but is more focused on managing dependencies and 7 | is _not_ a build tool. Keep using Maven and Gradle for that. This tool is ideal for those who want to compile and 8 | run Java code directly without making their lives difficult the moment they want to start using dependencies. 9 | 10 | [![asciicast](https://asciinema.org/a/ZqmYDG93jSJxQH8zaFRe7ilG0.svg)](https://asciinema.org/a/ZqmYDG93jSJxQH8zaFRe7ilG0) 11 | 12 | ## Example 13 | 14 | **TL;DR** 15 | 16 | ```shell 17 | $ jpm install com.github.lalyos:jfiglet:0.0.9 18 | Artifacts new: 1, updated: 0, deleted: 0 19 | $ java -cp deps/* HelloWorld.java 20 | _ _ _ _ __ __ _ _ _ 21 | | | | | ___| | | ___ \ \ / /__ _ __| | __| | | 22 | | |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | | 23 | | _ | __/ | | (_) | \ V V / (_) | | | | (_| |_| 24 | |_| |_|\___|_|_|\___( ) \_/\_/ \___/|_| |_|\__,_(_) 25 | ``` 26 | 27 | **Slightly longer explanation:** 28 | 29 | Imagine you're writing a simple Java console command, and you want to use JFigletFont for some more 30 | impactful visuals. You've written the following code: 31 | 32 | ```java 33 | import com.github.lalyos.jfiglet.FigletFont; 34 | 35 | public class HelloWorld { 36 | public static void main(String[] args) throws Exception { 37 | System.out.println(FigletFont.convertOneLine("Hello, World!")); 38 | } 39 | } 40 | ``` 41 | 42 | But now you get to the point that you need to add the JFigletFont library to your project. You could start 43 | using Maven or Gradle, but that seems overkill for such a simple project. Instead, you can use `jpm`. 44 | First you can search for the library if you can't remember the exact name and version: 45 | 46 | ```shell 47 | $ jpm search jfiglet 48 | com.github.dtmo.jfiglet:jfiglet:1.0.1 49 | com.github.lalyos:jfiglet:0.0.9 50 | ``` 51 | 52 | So let's install the second library from that list: 53 | 54 | ```shell 55 | $ jpm install com.github.lalyos:jfiglet:0.0.9 56 | Artifacts new: 1, updated: 0, deleted: 0 57 | ``` 58 | 59 | Let's see what that did: 60 | 61 | ```shell 62 | $ tree 63 | . 64 | ├── app.yml 65 | ├── deps 66 | │   └── jfiglet-0.0.9.jar -> /home/user/.m2/repository/com/github/lalyos/jfiglet/0.0.9/jfiglet-0.0.9.jar 67 | └── HelloWorld.java 68 | ``` 69 | 70 | As you can see `jpm` has created a `deps` directory and copied the JFigletFont library there 71 | (_although in fact it didn't actually copy the library itself, but instead it created a symbolic link to save space_). 72 | 73 | We can now simply run the program like this (using Java 11 or newer): 74 | 75 | ```shell 76 | $ java -cp deps/* HelloWorld.java 77 | _ _ _ _ __ __ _ _ _ 78 | | | | | ___| | | ___ \ \ / /__ _ __| | __| | | 79 | | |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | | 80 | | _ | __/ | | (_) | \ V V / (_) | | | | (_| |_| 81 | |_| |_|\___|_|_|\___( ) \_/\_/ \___/|_| |_|\__,_(_) 82 | ``` 83 | 84 | But if we look again at the above output of `tree`, we also see an `app.yml` file. 85 | This file is used by `jpm` to keep track of the dependencies of your project. If you want to share your project 86 | with someone else, you can simply share the `app.yml` file along with the code, and they can run `jpm install` 87 | to get the required dependencies to run the code. 88 | 89 | _NB: We could have used `jpm copy` instead of `jpm install` to copy the dependencies but that would not have created 90 | the `app.yml` file._ 91 | 92 | ## Installation 93 | 94 | For now the simplest way to install `jpm` is to use [JBang](https://www.jbang.dev/download/): 95 | 96 | ```shell 97 | jbang app install jpm@codejive 98 | ``` 99 | 100 | But you can also simply download and unzip the [release package](https://github.com/codejive/java-jpm/releases/latest) and run the `bin/jpm` script. 101 | 102 | ## Usage 103 | 104 | See: 105 | 106 | ```shell 107 | $ jpm --help 108 | Usage: jpm [-hV] [COMMAND] 109 | Simple command line tool for managing Maven artifacts 110 | -h, --help Show this help message and exit. 111 | -V, --version Print version information and exit. 112 | Commands: 113 | copy, c Resolves one or more artifacts and copies them and all their 114 | dependencies to a target directory. By default jpm will try to 115 | create symbolic links to conserve space. 116 | 117 | Example: 118 | jpm copy org.apache.httpcomponents:httpclient:4.5.14 119 | 120 | search, s Without arguments this command will start an interactive search 121 | asking the user to provide details of the artifact to look for 122 | and the actions to take. When provided with an argument this 123 | command finds and returns the names of those artifacts that 124 | match the given (partial) name. 125 | 126 | Example: 127 | jpm search httpclient 128 | 129 | install, i This adds the given artifacts to the list of dependencies 130 | available in the app.yml file. It then behaves just like 'copy 131 | --sync' and copies all artifacts in that list and all their 132 | dependencies to the target directory while at the same time 133 | removing any artifacts that are no longer needed (ie the ones 134 | that are not mentioned in the app.yml file). If no artifacts 135 | are passed the app.yml file will be left untouched and only 136 | the existing dependencies in the file will be copied. 137 | 138 | Example: 139 | jpm install org.apache.httpcomponents:httpclient:4.5.14 140 | 141 | path, p Resolves one or more artifacts and prints the full classpath to 142 | standard output. If no artifacts are passed the classpath for 143 | the dependencies defined in the app.yml file will be printed 144 | instead. 145 | 146 | Example: 147 | jpm path org.apache.httpcomponents:httpclient:4.5.14 148 | ``` 149 | 150 | ## Development 151 | 152 | To build the project simply run: 153 | 154 | ```shell 155 | ./mvnw spotless:apply clean install 156 | ``` 157 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | 2 | # Release instructions 3 | 4 | Use the following to create a release and deploy it: 5 | 6 | ## Step 1 - Set release versions 7 | 8 | ```bash 9 | mvn build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion} versions:commit 10 | ``` 11 | 12 | ## Step 2 - Building, staging and assembling 13 | 14 | ```bash 15 | ./mvnw clean deploy jreleaser:assemble -Prelease 16 | ``` 17 | 18 | ## Step 3 - Do a dry-run of the full release 19 | 20 | ```bash 21 | ./mvnw jreleaser:full-release -Djreleaser.dry.run 22 | ``` 23 | 24 | ## Step 4 - Commit and push the version changes 25 | 26 | ```bash 27 | git commit -am "Prepare for release" 28 | git push 29 | ``` 30 | 31 | ## Step 5 - Perform the full release 32 | 33 | ```bash 34 | ./mvnw jreleaser:full-release 35 | ``` 36 | 37 | ## Step 6 - Bump versions for next SNAPSHOT, commit and push 38 | 39 | ```bash 40 | mvn build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion}-SNAPSHOT versions:commit 41 | git commit -am "Prepare for next development iteration" 42 | git push 43 | ``` 44 | -------------------------------------------------------------------------------- /app.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | eu.maveniverse.maven.mima:context: "2.4.15" 3 | eu.maveniverse.maven.mima.runtime:standalone-static: "2.4.15" 4 | info.picocli:picocli: "4.7.6" 5 | org.yaml:snakeyaml: "2.3" 6 | org.jline:jline-console-ui: "3.29.0" 7 | org.jline:jline-terminal-jni: "3.29.0" 8 | org.slf4j:slf4j-api: "2.0.13" 9 | org.slf4j:slf4j-log4j12: "2.0.13" 10 | org.slf4j:slf4j-simple: "2.0.13" 11 | -------------------------------------------------------------------------------- /jreleaser.yml: -------------------------------------------------------------------------------- 1 | # Generated with JReleaser 1.14.0-SNAPSHOT at 2024-07-02T18:57:13.31908+02:00 2 | project: 3 | name: jpm 4 | description: Java Package Manager 5 | longDescription: | 6 | A simple command line tool, taking inspiration from Node's npm, to manage Maven dependencies for Java projects 7 | that are not using build systems like Maven or Gradle. 8 | authors: 9 | - Tako Schotanus 10 | tags: 11 | - java 12 | - cli 13 | - dependencies 14 | - packages 15 | - artifacts 16 | - maven 17 | license: Apache-2.0 18 | links: 19 | homepage: https://github.com/codejive/java-jpm 20 | languages: 21 | java: 22 | groupId: org.codejive.jpm 23 | version: '8' 24 | mainClass: org.codejive.jpm.Main 25 | inceptionYear: '2024' 26 | stereotype: CLI 27 | 28 | assemble: 29 | javaArchive: 30 | jpm: 31 | active: ALWAYS 32 | formats: 33 | - ZIP 34 | - TGZ 35 | mainJar: 36 | path: 'target/{{distributionName}}-{{projectVersion}}.jar' 37 | jars: 38 | - pattern: 'target/binary/lib/*.jar' 39 | 40 | nativeImage: 41 | jpm-native: 42 | active: NEVER 43 | java: 44 | version: '22' 45 | imageName: '{{distributionName}}-{{projectEffectiveVersion}}' 46 | executable: jpm 47 | mainJar: 48 | path: 'target/jpm-{{projectVersion}}-cli.jar' 49 | graalJdks: 50 | - path: 'C:\Users\tako\.jbang\cache\jdks\22' 51 | platform: 'windows-x86_64' 52 | upx: 53 | active: NEVER 54 | version: '4.2.4' 55 | args: 56 | - '-Duser.language=en' 57 | - '-H:IncludeLocales=en' 58 | - '--no-fallback' 59 | - '--enable-http' 60 | - '--enable-https' 61 | 62 | deploy: 63 | maven: 64 | mavenCentral: 65 | jpm: 66 | active: RELEASE 67 | url: https://central.sonatype.com/api/v1/publisher 68 | stagingRepositories: 69 | - target/staging-deploy 70 | 71 | release: 72 | github: 73 | owner: codejive 74 | name: java-jpm 75 | overwrite: true 76 | changelog: 77 | formatted: ALWAYS 78 | preset: conventional-commits 79 | contributors: 80 | format: '- {{contributorName}}{{#contributorUsernameAsLink}} ({{.}}){{/contributorUsernameAsLink}}' 81 | 82 | checksum: 83 | individual: true 84 | 85 | signing: 86 | active: ALWAYS 87 | armored: true 88 | 89 | distributions: 90 | jpm: 91 | artifacts: 92 | - path: target/jreleaser/assemble/{{distributionName}}/java-archive/{{distributionName}}-{{projectVersion}}.zip 93 | - path: target/jreleaser/assemble/{{distributionName}}/java-archive/{{distributionName}}-{{projectVersion}}.zip 94 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | org.codejive.jpm 7 | jpm 8 | 0.4.1 9 | 10 | Java Package Manager 11 | A simple command line tool for managing Maven dependencies for Java tools like javac and java 12 | https://github.com/codejive/java-jpm 13 | 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | 23 | Tako Schotanus 24 | tako@codejive.org 25 | Codejive 26 | https://github.com/codejive 27 | 28 | 29 | 30 | 31 | UTF-8 32 | 11 33 | 11 34 | org.codejive.jpm.Main 35 | 2.4.25 36 | 4.7.7 37 | 2.4 38 | 3.30.3 39 | 2.0.17 40 | 2.44.4 41 | 1.22.0 42 | 43 | 44 | 45 | 46 | eu.maveniverse.maven.mima 47 | context 48 | ${version.mima} 49 | 50 | 51 | eu.maveniverse.maven.mima.runtime 52 | standalone-static 53 | ${version.mima} 54 | 55 | 56 | info.picocli 57 | picocli 58 | ${version.picocli} 59 | 60 | 61 | org.yaml 62 | snakeyaml 63 | ${version.snakeyaml} 64 | 65 | 66 | org.jline 67 | jline-console-ui 68 | ${version.jline} 69 | 70 | 71 | org.jline 72 | jline-terminal-jni 73 | ${version.jline} 74 | 75 | 76 | org.slf4j 77 | slf4j-api 78 | ${version.slf4j} 79 | 80 | 81 | org.slf4j 82 | slf4j-simple 83 | ${version.slf4j} 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | com.diffplug.spotless 92 | spotless-maven-plugin 93 | ${version.spotless} 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-compiler-plugin 98 | 3.14.0 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-jar-plugin 103 | 3.4.2 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-shade-plugin 108 | 3.6.0 109 | 110 | 111 | org.codehaus.mojo 112 | appassembler-maven-plugin 113 | 2.1.0 114 | 115 | 116 | org.apache.maven.plugins 117 | maven-source-plugin 118 | 3.3.1 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-javadoc-plugin 123 | 3.11.2 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-gpg-plugin 128 | 3.2.7 129 | 130 | 131 | org.jreleaser 132 | jreleaser-maven-plugin 133 | 1.18.0 134 | false 135 | 136 | 137 | 138 | 139 | 140 | com.diffplug.spotless 141 | spotless-maven-plugin 142 | 143 | 144 | 145 | 146 | **/*.md 147 | **/*.txt 148 | .gitignore 149 | .gitattributes 150 | 151 | 152 | **/target/** 153 | 154 | 155 | 156 | 157 | true 158 | 4 159 | 160 | 161 | 162 | 163 | 164 | ${version.google-java-format} 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | verify 173 | 174 | check 175 | 176 | 177 | 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-compiler-plugin 182 | 183 | 184 | default-proc 185 | 186 | compile 187 | 188 | generate-sources 189 | 190 | 191 | 192 | info.picocli 193 | picocli-codegen 194 | ${version.picocli} 195 | 196 | 197 | 198 | -Aproject=${project.groupId}/${project.artifactId} 199 | 200 | only 201 | 202 | 203 | 204 | 205 | 206 | org.apache.maven.plugins 207 | maven-jar-plugin 208 | 209 | 210 | 211 | ${mainClass} 212 | true 213 | 214 | 215 | 216 | 217 | 218 | org.apache.maven.plugins 219 | maven-shade-plugin 220 | 221 | 222 | cli 223 | 224 | shade 225 | 226 | package 227 | 228 | false 229 | true 230 | cli 231 | 232 | 233 | 234 | 235 | 236 | ${mainClass} 237 | ${project.artifactId} 238 | ${project.version} 239 | ${project.artifactId} 240 | ${project.version} 241 | ${project.groupId} 242 | 243 | 244 | 245 | 246 | 247 | *:* 248 | 249 | META-INF/MANIFEST.MF 250 | META-INF/LICENSE 251 | META-INF/LICENSE.txt 252 | META-INF/DEPENDENCIES 253 | META-INF/NOTICE 254 | META-INF/NOTICE.txt 255 | **/module-info.class 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | org.codehaus.mojo 265 | appassembler-maven-plugin 266 | 267 | ${project.build.directory}/binary 268 | flat 269 | lib 270 | 271 | 272 | ${mainClass} 273 | jpm 274 | 275 | 276 | 277 | 278 | 279 | make-distribution 280 | package 281 | 282 | assemble 283 | 284 | 285 | 286 | 287 | 288 | org.jreleaser 289 | jreleaser-maven-plugin 290 | false 291 | 292 | ${project.basedir}/jreleaser.yml 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | release 301 | 302 | local::file:./target/staging-deploy 303 | 304 | 305 | deploy 306 | 307 | 308 | org.apache.maven.plugins 309 | maven-source-plugin 310 | 311 | 312 | attach-sources 313 | 314 | jar 315 | 316 | 317 | true 318 | 319 | 320 | 321 | 322 | 323 | org.apache.maven.plugins 324 | maven-javadoc-plugin 325 | 326 | 327 | attach-javadocs 328 | 329 | jar 330 | 331 | 332 | true 333 | 334 | 335 | 336 | 337 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | scm:git:git://github.com/codejive/java-jpm.git 357 | scm:git:git@github.com:codejive/java-jpm.git 358 | http://github.com/codejive/java-jpm 359 | 360 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/Jpm.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Path; 5 | import java.util.*; 6 | import org.codejive.jpm.json.AppInfo; 7 | import org.codejive.jpm.util.*; 8 | import org.eclipse.aether.artifact.Artifact; 9 | import org.eclipse.aether.resolution.DependencyResolutionException; 10 | 11 | /** The class implementing all the jpm command actions. */ 12 | public class Jpm { 13 | private final Path directory; 14 | private final boolean noLinks; 15 | 16 | private Jpm(Path directory, boolean noLinks) { 17 | this.directory = directory; 18 | this.noLinks = noLinks; 19 | } 20 | 21 | /** 22 | * Create a new {@link Builder} instance for the {@link Jpm} class. 23 | * 24 | * @return A new {@link Builder} instance. 25 | */ 26 | public static Builder builder() { 27 | return new Builder(); 28 | } 29 | 30 | /** Builder class for the {@link Jpm} class. */ 31 | public static class Builder { 32 | private Path directory; 33 | private boolean noLinks; 34 | 35 | private Builder() {} 36 | 37 | /** 38 | * Set the target directory to use for the jpm commands. 39 | * 40 | * @param directory The target directory. 41 | * @return The builder instance for chaining. 42 | */ 43 | public Builder directory(Path directory) { 44 | this.directory = directory; 45 | return this; 46 | } 47 | 48 | /** 49 | * Set whether to create symbolic links or not. 50 | * 51 | * @param noLinks Whether to create symbolic links or not. 52 | * @return The builder instance for chaining. 53 | */ 54 | public Builder noLinks(boolean noLinks) { 55 | this.noLinks = noLinks; 56 | return this; 57 | } 58 | 59 | /** 60 | * Builds the {@link Jpm} instance. 61 | * 62 | * @return A {@link Jpm} instance. 63 | */ 64 | public Jpm build() { 65 | return new Jpm(directory, noLinks); 66 | } 67 | } 68 | 69 | /** 70 | * Copies the given artifacts to the target directory. 71 | * 72 | * @param artifactNames The artifacts to copy. 73 | * @param sync Whether to sync the target directory or not. 74 | * @return An instance of {@link SyncStats} containing the statistics of the copy operation. 75 | * @throws IOException If an error occurred during the copy operation. 76 | * @throws DependencyResolutionException If an error occurred during the dependency resolution. 77 | */ 78 | public SyncStats copy(String[] artifactNames, boolean sync) 79 | throws IOException, DependencyResolutionException { 80 | List files = ResolverUtils.resolveArtifactPaths(artifactNames); 81 | return FileUtils.syncArtifacts(files, directory, noLinks, !sync); 82 | } 83 | 84 | /** 85 | * Searches for artifacts matching the given pattern. 86 | * 87 | * @param artifactPattern The pattern to search for. 88 | * @param count The maximum number of results to return. 89 | * @return An array of artifact names matching the given pattern. 90 | * @throws IOException If an error occurred during the search. 91 | */ 92 | public String[] search(String artifactPattern, int count) throws IOException { 93 | List artifacts = new ArrayList<>(); 94 | int max = count <= 0 || count > 200 ? 200 : count; 95 | SearchResult result = SearchUtils.findArtifacts(artifactPattern, max); 96 | while (result != null) { 97 | artifacts.addAll(result.artifacts); 98 | result = count <= 0 ? SearchUtils.findNextArtifacts(result) : null; 99 | } 100 | return artifacts.stream().map(Jpm::artifactGav).toArray(String[]::new); 101 | } 102 | 103 | private static String artifactGav(Artifact artifact) { 104 | return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(); 105 | } 106 | 107 | /** 108 | * Installs the given artifacts to the target directory while also registering them as 109 | * dependencies in the app.yml file in the current directory. If no artifacts are given, all 110 | * dependencies in the app.yml file will be installed. NB: "installation" in this context 111 | * basically means sync-copying the artifacts to the target directory. 112 | * 113 | * @param artifactNames The artifacts to install. 114 | * @return An instance of {@link SyncStats} containing the statistics of the install operation. 115 | * @throws IOException If an error occurred during the install operation. 116 | * @throws DependencyResolutionException If an error occurred during the dependency resolution. 117 | */ 118 | public SyncStats install(String[] artifactNames) 119 | throws IOException, DependencyResolutionException { 120 | AppInfo appInfo = AppInfo.read(); 121 | String[] artifacts = getArtifacts(artifactNames, appInfo); 122 | if (artifacts.length > 0) { 123 | List files = ResolverUtils.resolveArtifactPaths(artifacts); 124 | SyncStats stats = FileUtils.syncArtifacts(files, directory, noLinks, true); 125 | if (artifactNames.length > 0) { 126 | for (String dep : artifactNames) { 127 | int p = dep.lastIndexOf(':'); 128 | String name = dep.substring(0, p); 129 | String version = dep.substring(p + 1); 130 | appInfo.dependencies.put(name, version); 131 | } 132 | AppInfo.write(appInfo); 133 | } 134 | return stats; 135 | } else { 136 | return new SyncStats(); 137 | } 138 | } 139 | 140 | /** 141 | * Returns the paths of the given artifacts. If no artifacts are given, the paths for all 142 | * dependencies in the app.yml file will be returned instead. 143 | * 144 | * @param artifactNames The artifacts to get the paths for. 145 | * @return A list of paths. 146 | * @throws DependencyResolutionException If an error occurred during the dependency resolution. 147 | * @throws IOException If an error occurred during the operation. 148 | */ 149 | public List path(String[] artifactNames) 150 | throws DependencyResolutionException, IOException { 151 | AppInfo appInfo = AppInfo.read(); 152 | String[] deps = getArtifacts(artifactNames, appInfo); 153 | if (deps.length > 0) { 154 | return ResolverUtils.resolveArtifactPaths(deps); 155 | } else { 156 | return Collections.emptyList(); 157 | } 158 | } 159 | 160 | private static String[] getArtifacts(String[] artifactNames, AppInfo appInfo) { 161 | String[] deps; 162 | if (artifactNames.length > 0) { 163 | deps = artifactNames; 164 | } else { 165 | deps = appInfo.getDependencyGAVs(); 166 | } 167 | return deps; 168 | } 169 | 170 | private static boolean isWindows() { 171 | String os = 172 | System.getProperty("os.name") 173 | .toLowerCase(Locale.ENGLISH) 174 | .replaceAll("[^a-z0-9]+", ""); 175 | return os.startsWith("win"); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/Main.java: -------------------------------------------------------------------------------- 1 | // spotless:off Dependencies for JBang 2 | //DEPS eu.maveniverse.maven.mima:context:2.4.15 eu.maveniverse.maven.mima.runtime:standalone-static:2.4.15 3 | //DEPS info.picocli:picocli:4.7.6 4 | //DEPS org.yaml:snakeyaml:2.3 5 | //DEPS org.jline:jline-console-ui:3.29.0 org.jline:jline-terminal-jni:3.29.0 6 | //DEPS org.slf4j:slf4j-api:2.0.13 org.slf4j:slf4j-simple:2.0.13 7 | //SOURCES Jpm.java json/AppInfo.java util/FileUtils.java util/ResolverUtils.java util/SearchUtils.java 8 | //SOURCES util/SearchResult.java util/SyncStats.java util/Version.java 9 | // spotless:on 10 | 11 | package org.codejive.jpm; 12 | 13 | import java.io.*; 14 | import java.nio.file.Path; 15 | import java.util.*; 16 | import java.util.concurrent.Callable; 17 | import java.util.stream.Collectors; 18 | import org.codejive.jpm.util.SyncStats; 19 | import org.codejive.jpm.util.Version; 20 | import org.jline.consoleui.elements.InputValue; 21 | import org.jline.consoleui.elements.ListChoice; 22 | import org.jline.consoleui.elements.PageSizeType; 23 | import org.jline.consoleui.elements.PromptableElementIF; 24 | import org.jline.consoleui.elements.items.ListItemIF; 25 | import org.jline.consoleui.elements.items.impl.ListItem; 26 | import org.jline.consoleui.prompt.ConsolePrompt; 27 | import org.jline.consoleui.prompt.ListResult; 28 | import org.jline.consoleui.prompt.PromptResultItemIF; 29 | import org.jline.consoleui.prompt.builder.PromptBuilder; 30 | import org.jline.terminal.Terminal; 31 | import org.jline.terminal.TerminalBuilder; 32 | import picocli.CommandLine; 33 | import picocli.CommandLine.Command; 34 | import picocli.CommandLine.Mixin; 35 | import picocli.CommandLine.Option; 36 | import picocli.CommandLine.Parameters; 37 | 38 | /** Main class for the jpm command line tool. */ 39 | @Command( 40 | name = "jpm", 41 | mixinStandardHelpOptions = true, 42 | versionProvider = Version.class, 43 | description = "Simple command line tool for managing Maven artifacts", 44 | subcommands = { 45 | Main.Copy.class, 46 | Main.Search.class, 47 | Main.Install.class, 48 | Main.PrintPath.class 49 | }) 50 | public class Main { 51 | 52 | @Command( 53 | name = "copy", 54 | aliases = {"c"}, 55 | description = 56 | "Resolves one or more artifacts and copies them and all their dependencies to a target directory. " 57 | + "By default jpm will try to create symbolic links to conserve space.\n\n" 58 | + "Example:\n jpm copy org.apache.httpcomponents:httpclient:4.5.14\n") 59 | static class Copy implements Callable { 60 | @Mixin QuietMixin quietMixin; 61 | @Mixin ArtifactsMixin artifactsMixin; 62 | 63 | @Option( 64 | names = {"-s", "--sync"}, 65 | description = 66 | "Makes sure the target directory will only contain the mentioned artifacts and their dependencies, possibly removing other files present in the directory", 67 | defaultValue = "false") 68 | private boolean sync; 69 | 70 | @Override 71 | public Integer call() throws Exception { 72 | SyncStats stats = 73 | Jpm.builder() 74 | .directory(artifactsMixin.copyMixin.directory) 75 | .noLinks(artifactsMixin.copyMixin.noLinks) 76 | .build() 77 | .copy(artifactsMixin.artifactNames, sync); 78 | if (!quietMixin.quiet) { 79 | printStats(stats); 80 | } 81 | return (Integer) 0; 82 | } 83 | } 84 | 85 | @Command( 86 | name = "search", 87 | aliases = {"s"}, 88 | description = 89 | "Without arguments this command will start an interactive search asking the user to " 90 | + "provide details of the artifact to look for and the actions to take. When provided " 91 | + "with an argument this command finds and returns the names of those artifacts that " 92 | + "match the given (partial) name.\n\n" 93 | + "Example:\n jpm search httpclient\n") 94 | static class Search implements Callable { 95 | @Mixin QuietMixin quietMixin; 96 | @Mixin CopyMixin copyMixin; 97 | 98 | @Option( 99 | names = {"-i", "--interactive"}, 100 | description = "Interactively search and select artifacts to install", 101 | defaultValue = "false") 102 | private boolean interactive; 103 | 104 | @Option( 105 | names = {"-m", "--max"}, 106 | description = "Maximum number of results to return") 107 | private Integer max; 108 | 109 | @Parameters( 110 | paramLabel = "artifactPattern", 111 | description = "Partial or full artifact name to search for.", 112 | defaultValue = "") 113 | private String artifactPattern; 114 | 115 | @Override 116 | public Integer call() throws Exception { 117 | if (interactive || artifactPattern == null || artifactPattern.isEmpty()) { 118 | if (max == null) { 119 | max = (Integer) 100; 120 | } 121 | try (Terminal terminal = TerminalBuilder.builder().build()) { 122 | while (true) { 123 | ConsolePrompt.UiConfig cfg = new ConsolePrompt.UiConfig(); 124 | cfg.setCancellableFirstPrompt(true); 125 | ConsolePrompt prompt = new ConsolePrompt(null, terminal, cfg); 126 | Map result = prompt.prompt(this::nextQuestion); 127 | if (result.isEmpty()) { 128 | break; 129 | } 130 | String selectedArtifact = getSelectedId(result, "item"); 131 | String artifactAction = getSelectedId(result, "action"); 132 | if ("install".equals(artifactAction)) { 133 | SyncStats stats = 134 | Jpm.builder() 135 | .directory(copyMixin.directory) 136 | .noLinks(copyMixin.noLinks) 137 | .build() 138 | .install(new String[] {selectedArtifact}); 139 | if (!quietMixin.quiet) { 140 | printStats(stats); 141 | } 142 | } else if ("copy".equals(artifactAction)) { 143 | SyncStats stats = 144 | Jpm.builder() 145 | .directory(copyMixin.directory) 146 | .noLinks(copyMixin.noLinks) 147 | .build() 148 | .copy(new String[] {selectedArtifact}, false); 149 | if (!quietMixin.quiet) { 150 | printStats(stats); 151 | } 152 | } else { // quit 153 | break; 154 | } 155 | String finalAction = selectFinalAction(prompt); 156 | if (!"again".equals(finalAction)) { 157 | break; 158 | } 159 | artifactPattern = null; 160 | } 161 | } 162 | } else { 163 | if (max == null) { 164 | max = (Integer) 20; 165 | } 166 | String[] artifactNames = search(artifactPattern); 167 | if (artifactNames.length > 0) { 168 | Arrays.stream(artifactNames).forEach(System.out::println); 169 | } 170 | } 171 | return (Integer) 0; 172 | } 173 | 174 | String[] search(String artifactPattern) { 175 | try { 176 | return Jpm.builder() 177 | .directory(copyMixin.directory) 178 | .noLinks(copyMixin.noLinks) 179 | .build() 180 | .search(artifactPattern, Math.min(max, 200)); 181 | } catch (IOException e) { 182 | throw new UncheckedIOException(e); 183 | } 184 | } 185 | 186 | List nextQuestion(Map results) { 187 | String pattern; 188 | if (artifactPattern == null || artifactPattern.isEmpty()) { 189 | if (!results.containsKey("input")) { 190 | return List.of(stringElement("Search for:")); 191 | } 192 | pattern = results.get("input").getResult(); 193 | } else { 194 | pattern = artifactPattern; 195 | } 196 | 197 | if (!results.containsKey("item")) { 198 | String[] artifactNames = search(pattern); 199 | return List.of(selectElement("Select artifact:", artifactNames)); 200 | } 201 | 202 | if (!results.containsKey("action")) { 203 | return List.of(selectArtifactActionElement()); 204 | } else if ("version".equals(getSelectedId(results, "action"))) { 205 | results.remove("action"); 206 | pattern = getSelectedId(results, "item"); 207 | String[] artifactNames = search(pattern); 208 | return List.of(selectElement("Select version:", artifactNames)); 209 | } 210 | 211 | return null; 212 | } 213 | 214 | InputValue stringElement(String message) { 215 | return new InputValue("input", message); 216 | } 217 | 218 | ListChoice selectElement(String message, String[] items) { 219 | List itemList = 220 | Arrays.stream(items) 221 | .map(it -> new ListItem(it, it)) 222 | .collect(Collectors.toList()); 223 | return new ListChoice(message, "item", 10, PageSizeType.ABSOLUTE, itemList); 224 | } 225 | 226 | ListChoice selectArtifactActionElement() { 227 | List itemList = new ArrayList<>(); 228 | itemList.add(new ListItem("Download & Install artifact", "install")); 229 | itemList.add(new ListItem("Download & Copy artifact", "copy")); 230 | itemList.add(new ListItem("Select different version", "version")); 231 | itemList.add(new ListItem("Quit", "quit")); 232 | return new ListChoice("What to do:", "action", 10, PageSizeType.ABSOLUTE, itemList); 233 | } 234 | 235 | String selectFinalAction(ConsolePrompt prompt) throws IOException { 236 | PromptBuilder promptBuilder = prompt.getPromptBuilder(); 237 | promptBuilder 238 | .createListPrompt() 239 | .name("action") 240 | .message("Next step:") 241 | .newItem("quit") 242 | .text("Quit") 243 | .add() 244 | .newItem("again") 245 | .text("Search again") 246 | .add() 247 | .addPrompt(); 248 | Map result = prompt.prompt(promptBuilder.build()); 249 | return getSelectedId(result, "action"); 250 | } 251 | 252 | private static String getSelectedId( 253 | Map result, String itemName) { 254 | return ((ListResult) result.get(itemName)).getSelectedId(); 255 | } 256 | } 257 | 258 | @Command( 259 | name = "install", 260 | aliases = {"i"}, 261 | description = 262 | "This adds the given artifacts to the list of dependencies available in the app.yml file. " 263 | + "It then behaves just like 'copy --sync' and copies all artifacts in that list and all their dependencies to the target directory while at the same time removing any artifacts that are no longer needed (ie the ones that are not mentioned in the app.yml file). " 264 | + "If no artifacts are passed the app.yml file will be left untouched and only the existing dependencies in the file will be copied.\n\n" 265 | + "Example:\n jpm install org.apache.httpcomponents:httpclient:4.5.14\n") 266 | static class Install implements Callable { 267 | @Mixin QuietMixin quietMixin; 268 | @Mixin OptionalArtifactsMixin optionalArtifactsMixin; 269 | 270 | @Override 271 | public Integer call() throws Exception { 272 | SyncStats stats = 273 | Jpm.builder() 274 | .directory(optionalArtifactsMixin.copyMixin.directory) 275 | .noLinks(optionalArtifactsMixin.copyMixin.noLinks) 276 | .build() 277 | .install(optionalArtifactsMixin.artifactNames); 278 | if (!quietMixin.quiet) { 279 | printStats(stats); 280 | } 281 | return (Integer) 0; 282 | } 283 | } 284 | 285 | @Command( 286 | name = "path", 287 | aliases = {"p"}, 288 | description = 289 | "Resolves one or more artifacts and prints the full classpath to standard output. " 290 | + "If no artifacts are passed the classpath for the dependencies defined in the app.yml file will be printed instead.\n\n" 291 | + "Example:\n jpm path org.apache.httpcomponents:httpclient:4.5.14\n") 292 | static class PrintPath implements Callable { 293 | @Mixin OptionalArtifactsMixin optionalArtifactsMixin; 294 | 295 | @Override 296 | public Integer call() throws Exception { 297 | List files = 298 | Jpm.builder() 299 | .directory(optionalArtifactsMixin.copyMixin.directory) 300 | .noLinks(optionalArtifactsMixin.copyMixin.noLinks) 301 | .build() 302 | .path(optionalArtifactsMixin.artifactNames); 303 | if (!files.isEmpty()) { 304 | String classpath = 305 | files.stream() 306 | .map(Path::toString) 307 | .collect(Collectors.joining(File.pathSeparator)); 308 | System.out.print(classpath); 309 | } 310 | return (Integer) 0; 311 | } 312 | } 313 | 314 | static class CopyMixin { 315 | @Option( 316 | names = {"-d", "--directory"}, 317 | description = "Directory to copy artifacts to", 318 | defaultValue = "deps") 319 | Path directory; 320 | 321 | @Option( 322 | names = {"-L", "--no-links"}, 323 | description = "Always copy artifacts, don't try to create symlinks", 324 | defaultValue = "false") 325 | boolean noLinks; 326 | } 327 | 328 | static class ArtifactsMixin { 329 | @Mixin CopyMixin copyMixin; 330 | 331 | @Parameters( 332 | paramLabel = "artifacts", 333 | description = 334 | "One or more artifacts to resolve. Artifacts have the format :[:[:]]:", 335 | arity = "1..*") 336 | private String[] artifactNames = {}; 337 | } 338 | 339 | static class OptionalArtifactsMixin { 340 | @Mixin CopyMixin copyMixin; 341 | 342 | @Parameters( 343 | paramLabel = "artifacts", 344 | description = 345 | "One or more artifacts to resolve. Artifacts have the format :[:[:]]:", 346 | arity = "0..*") 347 | private String[] artifactNames = {}; 348 | } 349 | 350 | static class QuietMixin { 351 | @Option( 352 | names = {"-q", "--quiet"}, 353 | description = "Don't output non-essential information", 354 | defaultValue = "false") 355 | private boolean quiet; 356 | } 357 | 358 | private static void printStats(SyncStats stats) { 359 | System.err.printf( 360 | "Artifacts new: %d, updated: %d, deleted: %d%n", 361 | (Integer) stats.copied, (Integer) stats.updated, (Integer) stats.deleted); 362 | } 363 | 364 | /** 365 | * Main entry point for the jpm command line tool. 366 | * 367 | * @param args The command line arguments. 368 | */ 369 | public static void main(String... args) { 370 | if (args.length == 0) { 371 | System.err.println( 372 | "Running 'jpm search --interactive', try 'jpm --help' for more options"); 373 | args = new String[] {"search", "--interactive"}; 374 | } 375 | new CommandLine(new Main()).execute(args); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/json/AppInfo.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm.json; 2 | 3 | import java.io.IOException; 4 | import java.io.Reader; 5 | import java.io.Writer; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.Map; 10 | import java.util.TreeMap; 11 | import org.yaml.snakeyaml.DumperOptions; 12 | import org.yaml.snakeyaml.Yaml; 13 | 14 | /** 15 | * Represents the contents of an app.yml file. There are methods for reading and writing instances 16 | * from/to files. 17 | */ 18 | public class AppInfo { 19 | private Map yaml = new TreeMap<>(); 20 | public Map dependencies = new TreeMap<>(); 21 | 22 | /** The official name of the app.yml file. */ 23 | public static final String APP_INFO_FILE = "app.yml"; 24 | 25 | /** 26 | * Returns the dependencies as an array of strings in the format "groupId:artifactId:version". 27 | * 28 | * @return An array of strings 29 | */ 30 | public String[] getDependencyGAVs() { 31 | return dependencies.entrySet().stream() 32 | .map(e -> e.getKey() + ":" + e.getValue()) 33 | .toArray(String[]::new); 34 | } 35 | 36 | /** 37 | * Reads the app.yml file in the current directory and returns its content as an AppInfo object. 38 | * 39 | * @return An instance of AppInfo 40 | * @throws IOException if an error occurred while reading or parsing the file 41 | */ 42 | public static AppInfo read() throws IOException { 43 | Path prjJson = Paths.get(APP_INFO_FILE); 44 | AppInfo appInfo = new AppInfo(); 45 | if (Files.isRegularFile(prjJson)) { 46 | try (Reader in = Files.newBufferedReader(prjJson)) { 47 | Yaml yaml = new Yaml(); 48 | appInfo.yaml = yaml.load(in); 49 | } 50 | } else { 51 | appInfo = new AppInfo(); 52 | } 53 | // WARNING awful code ahead 54 | if (appInfo.yaml.containsKey("dependencies") 55 | && appInfo.yaml.get("dependencies") instanceof Map) { 56 | Map deps = (Map) appInfo.yaml.get("dependencies"); 57 | for (Map.Entry entry : deps.entrySet()) { 58 | appInfo.dependencies.put(entry.getKey(), entry.getValue().toString()); 59 | } 60 | } 61 | return appInfo; 62 | } 63 | 64 | /** 65 | * Writes the AppInfo object to the app.yml file in the current directory. 66 | * 67 | * @param appInfo The AppInfo object to write 68 | * @throws IOException if an error occurred while writing the file 69 | */ 70 | public static void write(AppInfo appInfo) throws IOException { 71 | Path prjJson = Paths.get(APP_INFO_FILE); 72 | try (Writer out = Files.newBufferedWriter(prjJson)) { 73 | DumperOptions dopts = new DumperOptions(); 74 | dopts.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); 75 | dopts.setPrettyFlow(true); 76 | Yaml yaml = new Yaml(dopts); 77 | // WARNING awful code ahead 78 | appInfo.yaml.put("dependencies", (Map) (Map) appInfo.dependencies); 79 | yaml.dump(appInfo.yaml, out); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.StandardCopyOption; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | /** Utility class for file operations. */ 13 | public class FileUtils { 14 | 15 | /** 16 | * Synchronizes a list of artifacts with a target directory. 17 | * 18 | * @param artifacts list of artifacts to synchronize 19 | * @param directory target directory 20 | * @param noLinks if true, copy artifacts instead of creating symbolic links 21 | * @param noDelete if true, do not delete artifacts that are no longer needed 22 | * @return An instance of {@link SyncStats} with statistics about the synchronization 23 | * @throws IOException if an error occurred during the synchronization 24 | */ 25 | public static SyncStats syncArtifacts( 26 | List artifacts, Path directory, boolean noLinks, boolean noDelete) 27 | throws IOException { 28 | SyncStats stats = new SyncStats(); 29 | 30 | // Make sure the target directory exists 31 | Files.createDirectories(directory); 32 | 33 | // Remember current artifact names in target directory (if any) 34 | Set artifactsToDelete = new HashSet<>(); 35 | if (!noDelete) { 36 | File[] files = directory.toFile().listFiles(File::isFile); 37 | if (files != null) { 38 | for (File file : files) { 39 | artifactsToDelete.add(file.getName()); 40 | } 41 | } 42 | } 43 | 44 | // Copy artifacts 45 | for (Path artifact : artifacts) { 46 | String artifactName = artifact.getFileName().toString(); 47 | Path target = directory.resolve(artifactName); 48 | if (!Files.exists(target)) { 49 | copyDependency(artifact, directory, noLinks); 50 | artifactsToDelete.remove(artifactName); 51 | stats.copied++; 52 | } else if (Files.isSymbolicLink(target) == noLinks) { 53 | copyDependency(artifact, directory, noLinks); 54 | stats.updated++; 55 | } 56 | } 57 | 58 | // Now remove any artifacts that are no longer needed 59 | if (!noDelete) { 60 | for (String existingArtifact : artifactsToDelete) { 61 | Path target = directory.resolve(existingArtifact); 62 | Files.delete(target); 63 | stats.deleted++; 64 | } 65 | } 66 | 67 | return stats; 68 | } 69 | 70 | private static void copyDependency(Path artifact, Path directory, boolean noLinks) 71 | throws IOException { 72 | Path target = directory.resolve(artifact.getFileName().toString()); 73 | if (!noLinks) { 74 | Files.deleteIfExists(target); 75 | try { 76 | Files.createSymbolicLink(target, artifact); 77 | return; 78 | } catch (IOException e) { 79 | // Creating a symlink might fail (eg on Windows) so we 80 | // fall through and try again by simply copying the file 81 | } 82 | } 83 | Files.copy(artifact, target, StandardCopyOption.REPLACE_EXISTING); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/util/ResolverUtils.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm.util; 2 | 3 | import eu.maveniverse.maven.mima.context.Context; 4 | import eu.maveniverse.maven.mima.context.ContextOverrides; 5 | import eu.maveniverse.maven.mima.context.Runtime; 6 | import eu.maveniverse.maven.mima.context.Runtimes; 7 | import java.nio.file.Path; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | import org.eclipse.aether.artifact.Artifact; 12 | import org.eclipse.aether.artifact.DefaultArtifact; 13 | import org.eclipse.aether.collection.CollectRequest; 14 | import org.eclipse.aether.graph.Dependency; 15 | import org.eclipse.aether.resolution.ArtifactResult; 16 | import org.eclipse.aether.resolution.DependencyRequest; 17 | import org.eclipse.aether.resolution.DependencyResolutionException; 18 | import org.eclipse.aether.resolution.DependencyResult; 19 | import org.eclipse.aether.util.artifact.JavaScopes; 20 | 21 | /** Utility class for resolving Maven artifacts. */ 22 | public class ResolverUtils { 23 | /** 24 | * Resolves the paths of the given artifacts. Handles parsing and resolving of artifacts and 25 | * extracts their paths. 26 | * 27 | * @param artifactNames the artifacts to resolve as an array of strings in the format 28 | * "groupId:artifactId:version" 29 | * @return the paths of the resolved artifacts 30 | * @throws DependencyResolutionException if an error occurs while resolving the artifacts 31 | */ 32 | public static List resolveArtifactPaths(String[] artifactNames) 33 | throws DependencyResolutionException { 34 | List artifacts = parseArtifacts(artifactNames); 35 | List resolvedArtifacts = resolveArtifacts(artifacts); 36 | return resolvedArtifacts.stream() 37 | .map(ar -> ar.getArtifact().getFile().toPath()) 38 | .collect(Collectors.toList()); 39 | } 40 | 41 | /** 42 | * Parses the given artifact names into a list of {@link Artifact} instances. 43 | * 44 | * @param artifactNames the artifact names to parse as an array of strings in the format 45 | * "groupId:artifactId:version" 46 | * @return a list of {@link Artifact} instances 47 | */ 48 | public static List parseArtifacts(String[] artifactNames) { 49 | return Arrays.stream(artifactNames).map(DefaultArtifact::new).collect(Collectors.toList()); 50 | } 51 | 52 | /** 53 | * Resolves the given artifacts. 54 | * 55 | * @param artifacts the artifacts to resolve as a list of {@link Artifact} instances 56 | * @return the resolved artifacts as a list of {@link ArtifactResult} instances 57 | * @throws DependencyResolutionException if an error occurs while resolving the artifacts 58 | */ 59 | public static List resolveArtifacts(List artifacts) 60 | throws DependencyResolutionException { 61 | List dependencies = 62 | artifacts.stream() 63 | .map(a -> new Dependency(a, JavaScopes.RUNTIME)) 64 | .collect(Collectors.toList()); 65 | ContextOverrides overrides = ContextOverrides.create().build(); 66 | Runtime runtime = Runtimes.INSTANCE.getRuntime(); 67 | try (Context context = runtime.create(overrides)) { 68 | CollectRequest collectRequest = 69 | new CollectRequest() 70 | .setDependencies(dependencies) 71 | .setRepositories(context.remoteRepositories()); 72 | DependencyRequest dependencyRequest = 73 | new DependencyRequest().setCollectRequest(collectRequest); 74 | 75 | DependencyResult dependencyResult = 76 | context.repositorySystem() 77 | .resolveDependencies( 78 | context.repositorySystemSession(), dependencyRequest); 79 | return dependencyResult.getArtifactResults(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/util/SearchResult.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm.util; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import org.eclipse.aether.artifact.Artifact; 6 | 7 | /** Hold the result of a search while also functioning as a kind of bookmark for paging purposes. */ 8 | public class SearchResult { 9 | /** The artifacts that matched the search query. */ 10 | public final List artifacts; 11 | 12 | /** The search query that produced this result. */ 13 | public final String query; 14 | 15 | /** The index of the first artifact in this result relative to the total result set. */ 16 | public final int start; 17 | 18 | /** The maximum number of results to return */ 19 | public final int count; 20 | 21 | /** The total number of artifacts that matched the search query. */ 22 | public final int total; 23 | 24 | /** 25 | * Create a new search result. 26 | * 27 | * @param artifacts The artifacts that matched the search query. 28 | * @param query The search query that produced this result. 29 | * @param start The index of the first artifact in this result relative to the total result set. 30 | * @param count The maximum number of results to return. 31 | * @param total The total number of artifacts that matched the search query. 32 | */ 33 | public SearchResult( 34 | List artifacts, String query, int start, int count, int total) { 35 | this.artifacts = Collections.unmodifiableList(artifacts); 36 | this.query = query; 37 | this.start = start; 38 | this.count = count; 39 | this.total = total; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/util/SearchUtils.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.net.URLEncoder; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import org.apache.http.client.methods.CloseableHttpResponse; 10 | import org.apache.http.client.methods.HttpGet; 11 | import org.apache.http.impl.client.CloseableHttpClient; 12 | import org.apache.http.impl.client.HttpClients; 13 | import org.eclipse.aether.artifact.DefaultArtifact; 14 | import org.yaml.snakeyaml.DumperOptions; 15 | import org.yaml.snakeyaml.LoaderOptions; 16 | import org.yaml.snakeyaml.Yaml; 17 | import org.yaml.snakeyaml.constructor.Constructor; 18 | import org.yaml.snakeyaml.representer.Representer; 19 | 20 | /** Utility class for searching Maven artifacts. */ 21 | public class SearchUtils { 22 | 23 | /** 24 | * Find artifacts matching the given pattern. This will return the first page of results. If the 25 | * pattern to search for is a simple name (there are no colons in the string), the search will 26 | * match any part of an artifact's group or name. If there's a single colon, the search will 27 | * match any part of the group id and artifact id separately. If there are two colons, the 28 | * search will match the group id and artifact id exactly, and will return the artifact's 29 | * versions. 30 | * 31 | * @param artifactPattern The pattern to search for. 32 | * @param count The maximum number of results to return. 33 | * @return The search result as an instance of {@link SearchResult}. 34 | * @throws IOException If an error occurred during the search. 35 | */ 36 | public static SearchResult findArtifacts(String artifactPattern, int count) throws IOException { 37 | return select(artifactPattern, 0, count); 38 | } 39 | 40 | /** 41 | * Find the next page of artifacts. This takes a {@link SearchResult} returned by a previous 42 | * call to {@link #findArtifacts(String, int)} and returns the next page of results. 43 | * 44 | * @param prevResult The previous search result. 45 | * @return The next search result as an instance of {@link SearchResult}. 46 | * @throws IOException If an error occurred during the search. 47 | */ 48 | public static SearchResult findNextArtifacts(SearchResult prevResult) throws IOException { 49 | if (prevResult.start + prevResult.count >= prevResult.total) { 50 | return null; 51 | } 52 | SearchResult result = 53 | select(prevResult.query, prevResult.start + prevResult.count, prevResult.count); 54 | return result.artifacts.isEmpty() ? null : result; 55 | } 56 | 57 | private static SearchResult select(String query, int start, int count) throws IOException { 58 | String[] parts = query.split(":", -1); 59 | String finalQuery; 60 | if (parts.length >= 3) { 61 | // Exact group/artifact match for retrieving versions 62 | finalQuery = String.format("g:%s AND a:%s", parts[0], parts[1]); 63 | } else if (parts.length == 2) { 64 | // Partial group/artifact match, we will filter the results 65 | // to remove those that match an inverted artifact/group 66 | finalQuery = String.format("%s AND %s", parts[0], parts[1]); 67 | } else { 68 | // Simple partial match 69 | finalQuery = query; 70 | } 71 | String searchUrl = 72 | String.format( 73 | "https://search.maven.org/solrsearch/select?start=%d&rows=%d&q=%s", 74 | start, count, URLEncoder.encode(finalQuery, "UTF-8")); 75 | if (parts.length >= 3) { 76 | searchUrl += "&core=gav"; 77 | } 78 | String agent = 79 | String.format( 80 | "jpm/%s (%s %s)", 81 | Version.get(), 82 | System.getProperty("os.name"), 83 | System.getProperty("os.arch")); 84 | try (CloseableHttpClient httpClient = HttpClients.custom().setUserAgent(agent).build()) { 85 | HttpGet request = new HttpGet(searchUrl); 86 | try (CloseableHttpResponse response = httpClient.execute(request)) { 87 | DumperOptions dopts = new DumperOptions(); 88 | Constructor cons = new Constructor(MvnSearchResult.class, new LoaderOptions()); 89 | Representer representer = new Representer(dopts); 90 | representer.getPropertyUtils().setSkipMissingProperties(true); 91 | Yaml yaml = new Yaml(cons, representer, dopts); 92 | InputStream ins = response.getEntity().getContent(); 93 | InputStreamReader rdr = new InputStreamReader(ins); 94 | MvnSearchResult result = yaml.load(rdr); 95 | if (result.responseHeader.status != 0) { 96 | throw new IOException("Search failed"); 97 | } 98 | List artifacts = 99 | result.response.docs.stream() 100 | .filter(d -> acceptDoc(d, parts)) 101 | .map(SearchUtils::toArtifact) 102 | .collect(Collectors.toList()); 103 | return new SearchResult(artifacts, query, start, count, result.response.numFound); 104 | } 105 | } 106 | } 107 | 108 | private static boolean acceptDoc(MsrDoc d, String[] parts) { 109 | return d.ec != null 110 | && d.ec.contains(".jar") 111 | && (parts.length != 2 || d.g.contains(parts[0]) && d.a.contains(parts[1])); 112 | } 113 | 114 | private static DefaultArtifact toArtifact(MsrDoc d) { 115 | return new DefaultArtifact(d.g, d.a, "", d.v != null ? d.v : d.latestVersion); 116 | } 117 | } 118 | 119 | class MvnSearchResult { 120 | public MsrHeader responseHeader; 121 | public MsrResponse response; 122 | } 123 | 124 | class MsrHeader { 125 | public int status; 126 | } 127 | 128 | class MsrResponse { 129 | public List docs; 130 | public int numFound; 131 | public int start; 132 | } 133 | 134 | class MsrDoc { 135 | public String g; 136 | public String a; 137 | public String v; 138 | public String latestVersion; 139 | public String p; 140 | public List ec; 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/util/SyncStats.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm.util; 2 | 3 | /** Utility class for keeping track of synchronization statistics. */ 4 | public class SyncStats { 5 | /** The number of new artifacts that were copied. */ 6 | public int copied; 7 | 8 | /** The number of existing artifacts that were updated. */ 9 | public int updated; 10 | 11 | /** The number of existing artifacts that were deleted. */ 12 | public int deleted; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/codejive/jpm/util/Version.java: -------------------------------------------------------------------------------- 1 | package org.codejive.jpm.util; 2 | 3 | import picocli.CommandLine; 4 | 5 | /** Utility class for retrieving the version of the application. */ 6 | public class Version implements CommandLine.IVersionProvider { 7 | 8 | /** 9 | * Get the version of the application. 10 | * 11 | * @return The version of the application. 12 | */ 13 | public static String get() { 14 | String version = Version.class.getPackage().getImplementationVersion(); 15 | return version != null ? version : "0.0.0"; 16 | } 17 | 18 | @Override 19 | public String[] getVersion() throws Exception { 20 | return new String[] {Version.get()}; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/maven-model/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundles" : [ 3 | ], 4 | "resources": { 5 | "includes": [ 6 | {"pattern": "org/apache/maven/model/pom-4\\.0\\.0\\.xml"} 7 | ] 8 | } 9 | } 10 | --------------------------------------------------------------------------------