├── LICENSE ├── pom.xml ├── src └── main │ └── java │ └── com │ └── github │ └── chrisdchristo │ └── capsule │ ├── Mojo.java │ └── CapsuleMojo.java └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.github.chrisdchristo 5 | capsule-maven-plugin 6 | 1.5.2-SNAPSHOT 7 | maven-plugin 8 | 9 | 10 | org.sonatype.oss 11 | oss-parent 12 | 9 13 | 14 | 15 | 16 | 17 | chrisdchristo 18 | https://github.com/chrisdchristo 19 | 20 | 21 | 22 | 23 | https://github.com/chrisdchristo/capsule-maven-plugin 24 | scm:git:https://github.com/chrisdchristo/capsule-maven-plugin.git 25 | scm:git:https://github.com/chrisdchristo/capsule-maven-plugin.git 26 | 27 | 28 | Capsule Maven Plugin 29 | The maven plugin to build capsules of your jars. 30 | https://github.com/chrisdchristo/capsule-maven-plugin 31 | 32 | 33 | 34 | MIT License 35 | http://www.opensource.org/licenses/mit-license.php 36 | repo 37 | 38 | 39 | 40 | 41 | 42 | ossrh 43 | https://oss.sonatype.org/content/repositories/snapshots/ 44 | 45 | 46 | ossrh 47 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 48 | 49 | 50 | 51 | 52 | UTF-8 53 | 1.7 54 | 55 | 3.0.1 56 | 3.6.1 57 | 3.0-alpha-2 58 | 3.5 59 | 3.3.9 60 | 3.5 61 | 1.6 62 | 1.1.0 63 | 64 | 65 | 66 | 67 | org.apache.maven 68 | maven-plugin-api 69 | ${maven.plugin.api.version} 70 | 71 | 72 | org.apache.maven.plugin-tools 73 | maven-plugin-annotations 74 | ${maven.plugin.annotations.version} 75 | provided 76 | 77 | 78 | org.apache.maven 79 | maven-project 80 | ${maven.project.version} 81 | 82 | 83 | org.eclipse.aether 84 | aether-api 85 | ${aether.api.version} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-source-plugin 96 | ${maven.source.plugin.version} 97 | 98 | 99 | attach-sources 100 | 101 | jar 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-compiler-plugin 111 | ${maven.compiler.plugin.version} 112 | 113 | ${java.version} 114 | ${java.version} 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-plugin-plugin 122 | ${maven.plugin.plugin.version} 123 | 124 | capsule 125 | true 126 | 127 | 128 | 129 | mojo-descriptor 130 | 131 | descriptor 132 | 133 | 134 | 135 | help-goal 136 | 137 | helpmojo 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | release-sign-artifacts 149 | 150 | 151 | performRelease 152 | true 153 | 154 | 155 | 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-gpg-plugin 161 | ${maven.gpg.plugin.version} 162 | 163 | 164 | sign-artifacts 165 | verify 166 | 167 | sign 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /src/main/java/com/github/chrisdchristo/capsule/Mojo.java: -------------------------------------------------------------------------------- 1 | package com.github.chrisdchristo.capsule; 2 | 3 | import org.apache.maven.artifact.Artifact; 4 | import org.apache.maven.model.Dependency; 5 | import org.apache.maven.model.Exclusion; 6 | import org.apache.maven.model.Plugin; 7 | import org.apache.maven.plugin.AbstractMojo; 8 | import org.apache.maven.plugins.annotations.Component; 9 | import org.apache.maven.plugins.annotations.Parameter; 10 | import org.apache.maven.project.DefaultMavenProjectHelper; 11 | import org.apache.maven.project.MavenProject; 12 | import org.apache.maven.project.MavenProjectHelper; 13 | import org.codehaus.plexus.util.IOUtil; 14 | import org.eclipse.aether.RepositorySystem; 15 | import org.eclipse.aether.RepositorySystemSession; 16 | import org.eclipse.aether.artifact.DefaultArtifact; 17 | import org.eclipse.aether.collection.CollectRequest; 18 | import org.eclipse.aether.repository.RemoteRepository; 19 | import org.eclipse.aether.resolution.*; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.util.*; 25 | import java.util.jar.Attributes; 26 | import java.util.jar.JarOutputStream; 27 | import java.util.jar.Manifest; 28 | import java.util.zip.ZipEntry; 29 | import java.util.zip.ZipException; 30 | 31 | /** 32 | * Super class with generic methods 33 | */ 34 | public abstract class Mojo extends AbstractMojo { 35 | 36 | /** 37 | * AETHER REPO LINK 38 | */ 39 | @Component 40 | RepositorySystem repoSystem = null; 41 | @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) 42 | RepositorySystemSession repoSession = null; 43 | @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true) 44 | List remoteRepos = null; 45 | @Parameter(defaultValue = "${project.build.finalName}", readonly = true) 46 | String finalName = null; 47 | @Parameter(defaultValue = "${project.build.directory}") 48 | File buildDir = null; 49 | @Parameter(defaultValue = "${project.basedir}") 50 | File baseDir = null; 51 | 52 | 53 | /** 54 | * Project 55 | */ 56 | @Parameter(defaultValue = "${project}", readonly = true) 57 | MavenProject project = null; 58 | 59 | final MavenProjectHelper helper = new DefaultMavenProjectHelper(); 60 | 61 | abstract String pluginKey(); 62 | abstract String logPrefix(); 63 | 64 | // DEPENDENCIES 65 | 66 | Set appDependencies() { return set(project.getTestDependencies()); } 67 | Set appDirectDependencies() { return set(project.getDependencies()); } 68 | Set appDependencyArtifacts() { return project.getArtifacts(); } 69 | Set appDirectDependencyArtifacts() { return project.getDependencyArtifacts(); } 70 | 71 | Set pluginDependencies() { return getDependenciesOf(set(plugin().getDependencies()), true); } 72 | Set pluginDirectDependencies() { return set(plugin().getDependencies()); } 73 | Set pluginDependencyArtifacts() { return getDependencyArtifactsOf(set(plugin().getDependencies()), true); } 74 | Set pluginDirectDependencyArtifacts() { return toArtifacts(set(plugin().getDependencies())); } 75 | 76 | 77 | // RESOLVERS 78 | 79 | private Plugin plugin() { 80 | return project.getPlugin(pluginKey()); 81 | } 82 | 83 | private ArtifactResult resolve(final Dependency dependency) { 84 | return resolve(dependency.getGroupId(), dependency.getArtifactId(), dependency.getClassifier(), dependency.getVersion()); 85 | } 86 | 87 | ArtifactResult resolve(final String groupId, final String artifactId, final String classifier, final String version) { 88 | return resolve(coords(groupId, artifactId, classifier, version)); 89 | } 90 | 91 | ArtifactResult resolve(final String coords) { 92 | try { 93 | return repoSystem.resolveArtifact(repoSession, new ArtifactRequest(new DefaultArtifact(coords), remoteRepos, null)); 94 | } catch (final ArtifactResolutionException e) { 95 | warn("\t\t[Resolve] Failed to resolve: [" + coords + "]"); 96 | return null; 97 | } 98 | } 99 | 100 | private Set resolveDependencies(final Dependency dependency) { 101 | try { 102 | final CollectRequest collectRequest = new CollectRequest(new org.eclipse.aether.graph.Dependency(resolve(dependency).getArtifact(), ""), remoteRepos); 103 | return set(repoSystem.resolveDependencies(repoSession, new DependencyRequest(collectRequest, null)).getArtifactResults()); 104 | } catch (final DependencyResolutionException e) { 105 | warn("\t\t[Resolve] Failed to resolve: [" + coords(dependency) + "]"); 106 | return new HashSet<>(); 107 | } 108 | } 109 | 110 | Artifact toArtifact(final ArtifactResult ar) { 111 | if (ar == null) return null; 112 | final Artifact artifact = new org.apache.maven.artifact.DefaultArtifact( 113 | ar.getArtifact().getGroupId(), 114 | ar.getArtifact().getArtifactId(), 115 | ar.getArtifact().getVersion(), 116 | null, 117 | "jar", 118 | ar.getArtifact().getClassifier(), 119 | null); 120 | if (ar.getRequest().getDependencyNode() != null && ar.getRequest().getDependencyNode().getDependency() != null) { 121 | artifact.setScope(ar.getRequest().getDependencyNode().getDependency().getScope()); 122 | artifact.setOptional(ar.getRequest().getDependencyNode().getDependency().isOptional()); 123 | } 124 | if (artifact.getScope() == null || artifact.getScope().isEmpty()) artifact.setScope("compile"); 125 | artifact.setFile(ar.getArtifact().getFile()); 126 | return artifact; 127 | } 128 | 129 | private Artifact toArtifact(final Dependency dependency) { 130 | if (dependency == null) return null; 131 | final Artifact artifact = toArtifact(resolve(dependency)); 132 | artifact.setScope(dependency.getScope()); 133 | if (artifact.getScope() == null || artifact.getScope().isEmpty()) artifact.setScope("compile"); 134 | return artifact; 135 | } 136 | 137 | private Dependency toDependency(final Artifact artifact) { 138 | if (artifact == null) return null; 139 | final Dependency dependency = new Dependency(); 140 | dependency.setGroupId(artifact.getGroupId()); 141 | dependency.setArtifactId(artifact.getArtifactId()); 142 | dependency.setVersion(artifact.getVersion()); 143 | dependency.setScope(artifact.getScope()); 144 | dependency.setClassifier(artifact.getClassifier()); 145 | dependency.setOptional(artifact.isOptional()); 146 | if (dependency.getScope() == null || dependency.getScope().isEmpty()) dependency.setScope("compile"); 147 | return dependency; 148 | } 149 | 150 | private Dependency toDependency(final ArtifactResult ar) { 151 | return toDependency(toArtifact(ar)); 152 | } 153 | 154 | private Set getDependencyArtifactsOf(final Dependency dependency, final boolean includeRoot) { 155 | final Set artifacts = new HashSet<>(); 156 | if (includeRoot) artifacts.add(toArtifact(dependency)); 157 | for (final ArtifactResult ar : resolveDependencies(dependency)) { 158 | final Artifact artifact = toArtifact(ar); 159 | 160 | // if null set to default compile 161 | if (artifact.getScope() == null || artifact.getScope().isEmpty()) artifact.setScope("compile"); 162 | 163 | // skip any deps that aren't compile or runtime 164 | if (!artifact.getScope().equals("compile") && !artifact.getScope().equals("runtime")) continue; 165 | 166 | // set direct-scope on transitive deps 167 | if (dependency.getScope().equals("provided")) artifact.setScope("provided"); 168 | if (dependency.getScope().equals("system")) artifact.setScope("system"); 169 | if (dependency.getScope().equals("test")) artifact.setScope("test"); 170 | 171 | artifacts.add(toArtifact(ar)); 172 | } 173 | return cleanArtifacts(artifacts); 174 | } 175 | 176 | private Set getDependencyArtifactsOf(final Set dependencies, final boolean includeRoot) { 177 | final Set artifacts = new HashSet<>(); 178 | for (final Dependency dependency : dependencies) { 179 | artifacts.addAll(getDependencyArtifactsOf(dependency, includeRoot)); 180 | } 181 | return cleanArtifacts(artifacts); 182 | } 183 | 184 | private Set getDependenciesOf(final Dependency dependency, final boolean includeRoot) { 185 | final Set dependencies = new HashSet<>(); 186 | if (includeRoot) dependencies.add(dependency); 187 | for (final ArtifactResult ar : resolveDependencies(dependency)) { 188 | dependencies.add(toDependency(ar)); 189 | } 190 | return cleanDependencies(dependencies); 191 | } 192 | 193 | private Set getDependenciesOf(final Set dependencies, final boolean includeRoot) { 194 | final Set dependenciesAll = new HashSet<>(); 195 | for (final Dependency dependency : dependencies) { 196 | dependenciesAll.addAll(getDependenciesOf(dependency, includeRoot)); 197 | } 198 | return cleanDependencies(dependenciesAll); 199 | } 200 | 201 | private Set toArtifacts(final Set dependencies) { 202 | final Set artifacts = new HashSet<>(); 203 | for (final Dependency dependency : dependencies) { 204 | artifacts.add(toArtifact(dependency)); 205 | } 206 | return cleanArtifacts(artifacts); 207 | } 208 | 209 | // JAR & FILE HELPERS 210 | 211 | String addDirectoryToJar(final JarOutputStream jar, final String outputDirectory) throws IOException { 212 | 213 | // format the output directory 214 | String formattedOutputDirectory = ""; 215 | if (outputDirectory != null && !outputDirectory.isEmpty()) { 216 | if (!outputDirectory.endsWith("/")) { 217 | formattedOutputDirectory = outputDirectory + File.separatorChar; 218 | } else { 219 | formattedOutputDirectory = outputDirectory; 220 | } 221 | } 222 | 223 | if (!formattedOutputDirectory.isEmpty()) { 224 | try { 225 | jar.putNextEntry(new ZipEntry(formattedOutputDirectory)); 226 | jar.closeEntry(); 227 | } catch (final ZipException ignore) {} // ignore duplicate entries and other errors 228 | } 229 | return formattedOutputDirectory; 230 | } 231 | 232 | JarOutputStream addToJar(final String name, final InputStream input, final JarOutputStream jar) throws IOException { 233 | try { 234 | debug("\t[Added to Jar]: " + name); 235 | jar.putNextEntry(new ZipEntry(name)); 236 | IOUtil.copy(input, jar); 237 | jar.closeEntry(); 238 | } catch (final ZipException ignore) {} // ignore duplicate entries and other errors 239 | IOUtil.close(input); 240 | return jar; 241 | } 242 | 243 | 244 | // LOG 245 | 246 | void debug(final String message) { getLog().debug(logPrefix() + message); } 247 | void info(final String message) { getLog().info(logPrefix() + message); } 248 | void warn(final String message) { getLog().warn(logPrefix() + message); } 249 | void printManifest(final Manifest manifest) { 250 | info("\t[Manifest]:"); 251 | for (final Map.Entry attr : manifest.getMainAttributes().entrySet()) { 252 | info("\t\t" + attr.getKey().toString() + ": " + attr.getValue().toString()); 253 | } 254 | for (final Map.Entry entry : manifest.getEntries().entrySet()) { 255 | info(""); 256 | info("\t\tName:" + entry.getKey()); 257 | for (final Map.Entry attr : entry.getValue().entrySet()) { 258 | info("\t\t" + attr.getKey().toString() + ": " + attr.getValue().toString()); 259 | } 260 | } 261 | } 262 | 263 | 264 | 265 | 266 | // HELPERS 267 | 268 | public static class Pair { 269 | public K key; 270 | public V value; 271 | public Pair() {} 272 | public Pair(final K key, final V value) { 273 | this.key = key; 274 | this.value = value; 275 | } 276 | } 277 | 278 | static Set set(final List list) { 279 | return new HashSet<>(list); 280 | } 281 | 282 | static Set set(final Enumeration enumeration) { 283 | final Set set = new HashSet<>(); 284 | while (enumeration.hasMoreElements()) 285 | set.add(enumeration.nextElement()); 286 | return set; 287 | } 288 | 289 | 290 | // COORDS 291 | 292 | static String coords(final String groupId, final String artifactId, final String classifier, final String version) { 293 | final StringBuilder coords = new StringBuilder(); 294 | coords.append(groupId).append(":").append(artifactId); 295 | if (classifier != null && !classifier.isEmpty()) 296 | coords.append(":").append(classifier); 297 | if (version != null && !version.isEmpty()) 298 | coords.append(":").append(version); 299 | return coords.toString(); 300 | } 301 | 302 | static String coords(final Artifact artifact) { 303 | return coords(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getVersion()); 304 | } 305 | 306 | static String coords(final Dependency dependency) { 307 | return coords(dependency.getGroupId(), dependency.getArtifactId(), dependency.getClassifier(), dependency.getVersion()); 308 | } 309 | 310 | static String coordsWithExclusions(final Dependency dependency) { 311 | final StringBuilder coords = new StringBuilder(coords(dependency)); 312 | if (dependency.getExclusions().size() > 0) { 313 | final StringBuilder exclusionsList = new StringBuilder(); 314 | int i = 0; 315 | for (final Exclusion exclusion : dependency.getExclusions()) { 316 | if (i > 0) exclusionsList.append(","); 317 | exclusionsList.append(exclusion.getGroupId()).append(":").append(exclusion.getArtifactId()); 318 | i++; 319 | } 320 | coords.append("(").append(exclusionsList.toString()).append(")"); 321 | } 322 | return coords.toString(); 323 | } 324 | 325 | 326 | // CLEANERS 327 | 328 | // clean any duplicates 329 | static Set cleanDependencies(final Set dependencies) { 330 | final Set dependenciesClean = new HashSet<>(); 331 | for (final Dependency dependencyA : dependencies) { 332 | boolean found = false; 333 | for (final Dependency dependencyB : dependenciesClean) { 334 | if (coords(dependencyA).equals(coords(dependencyB))) { 335 | found = true; 336 | break; 337 | } 338 | } 339 | if (!found) dependenciesClean.add(dependencyA); 340 | } 341 | return dependenciesClean; 342 | } 343 | 344 | // clean any duplicates 345 | static Set cleanArtifacts(final Set artifacts) { 346 | final Set artifactsClean = new HashSet<>(); 347 | for (final Artifact artifactA : artifacts) { 348 | boolean found = false; 349 | for (final Artifact artifactB : artifactsClean) { 350 | if (coords(artifactA).equals(coords(artifactB))){ 351 | found = true; 352 | break; 353 | } 354 | } 355 | if (!found) artifactsClean.add(artifactA); 356 | } 357 | return artifactsClean; 358 | } 359 | 360 | static Set cleanDependencies(final Set setA, final boolean includeA, final Set setB, final boolean includeB) { 361 | final Set set = new HashSet<>(); 362 | if (includeA) set.addAll(setA); 363 | if (includeB) set.addAll(setB); 364 | return cleanDependencies(set); 365 | } 366 | 367 | static Set cleanArtifacts(final Set setA, final boolean includeA, final Set setB, final boolean includeB) { 368 | final Set set = new HashSet<>(); 369 | if (includeA) set.addAll(setA); 370 | if (includeB) set.addAll(setB); 371 | return cleanArtifacts(set); 372 | } 373 | 374 | } 375 | -------------------------------------------------------------------------------- /src/main/java/com/github/chrisdchristo/capsule/CapsuleMojo.java: -------------------------------------------------------------------------------- 1 | package com.github.chrisdchristo.capsule; 2 | 3 | import org.apache.maven.artifact.Artifact; 4 | import org.apache.maven.model.Dependency; 5 | import org.apache.maven.model.Plugin; 6 | import org.apache.maven.model.PluginExecution; 7 | import org.apache.maven.plugin.MojoExecutionException; 8 | import org.apache.maven.plugin.MojoFailureException; 9 | import org.apache.maven.plugins.annotations.LifecyclePhase; 10 | import org.apache.maven.plugins.annotations.Parameter; 11 | import org.apache.maven.plugins.annotations.ResolutionScope; 12 | import org.codehaus.plexus.util.IOUtil; 13 | import org.codehaus.plexus.util.xml.Xpp3Dom; 14 | import org.eclipse.aether.artifact.DefaultArtifact; 15 | import org.eclipse.aether.repository.RemoteRepository; 16 | import org.eclipse.aether.resolution.ArtifactResult; 17 | import org.eclipse.aether.resolution.VersionRangeRequest; 18 | import org.eclipse.aether.resolution.VersionRangeResolutionException; 19 | import org.eclipse.aether.resolution.VersionRangeResult; 20 | 21 | import java.io.*; 22 | import java.nio.file.FileVisitResult; 23 | import java.nio.file.Files; 24 | import java.nio.file.Path; 25 | import java.nio.file.SimpleFileVisitor; 26 | import java.nio.file.attribute.BasicFileAttributes; 27 | import java.util.*; 28 | import java.util.jar.*; 29 | import java.util.zip.ZipEntry; 30 | 31 | /** 32 | * Mojo to generate a Capsule jar 33 | */ 34 | @org.apache.maven.plugins.annotations.Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyCollection = ResolutionScope.TEST, requiresDependencyResolution 35 | = ResolutionScope.RUNTIME_PLUS_SYSTEM) 36 | public class CapsuleMojo extends Mojo { 37 | 38 | public final String pluginKey() { 39 | return "com.github.chrisdchristo:capsule-maven-plugin"; 40 | } 41 | 42 | public final String logPrefix() { 43 | return "[CapsuleMavenPlugin] "; 44 | } 45 | 46 | private static final String DEFAULT_CAPSULE_VERSION = "1.0.3"; 47 | private static final String DEFAULT_CAPSULE_MAVEN_VERSION = "1.0.3"; 48 | 49 | private static final String CAPSULE_GROUP = "co.paralleluniverse"; 50 | private static final String DEFAULT_CAPSULE_NAME = "Capsule"; 51 | private static final String DEFAULT_CAPSULE_CLASS = DEFAULT_CAPSULE_NAME + ".class"; 52 | private static final String DEFAULT_CAPSULE_MAVEN_NAME = "MavenCapsule"; 53 | private static final String DEFAULT_CAPSULE_MAVEN_CLASS = "MavenCapsule.class"; 54 | 55 | private static final String EXEC_PREFIX = "#!/bin/sh\n\nexec java -jar \"$0\" \"$@\"\n\n"; 56 | private static final String EXEC_TRAMPOLINE_PREFIX = "#!/bin/sh\n\nexec java -Dcapsule.trampoline -jar \"$0\" \"$@\"\n\n"; 57 | 58 | private static final String EXEC_PLUGIN_KEY = "org.codehaus.mojo:exec-maven-plugin"; 59 | 60 | /** 61 | * OPTIONAL VARIABLES 62 | */ 63 | @Parameter(property = "capsule.outputDir", defaultValue = "${project.build.directory}") 64 | private File outputDir = null; 65 | @Parameter(property = "capsule.version") 66 | private String capsuleVersion = DEFAULT_CAPSULE_VERSION; 67 | @Parameter(property = "capsule.maven.version") 68 | private String capsuleMavenVersion = DEFAULT_CAPSULE_MAVEN_VERSION; 69 | @Parameter(property = "capsule.appClass") 70 | private String appClass = null; 71 | @Parameter(property = "capsule.caplets") 72 | private String caplets; 73 | @Parameter(property = "capsule.type") 74 | private Type type = null; 75 | @Parameter(property = "capsule.chmod") 76 | private boolean chmod = false; 77 | @Parameter(property = "capsule.trampoline") 78 | private boolean trampoline = false; 79 | @Parameter(property = "capsule.setManifestRepos") 80 | private boolean setManifestRepos = false; 81 | 82 | @Parameter(property = "capsule.includeApp") 83 | private boolean includeApp = true; 84 | @Parameter(property = "capsule.includeAppDep") 85 | private boolean includeAppDep = false; 86 | @Parameter(property = "capsule.includePluginDep") 87 | private boolean includePluginDep = false; 88 | @Parameter(property = "capsule.includeTransitiveDep") 89 | private boolean includeTransitiveDep = false; 90 | @Parameter(property = "capsule.includeCompileDep") 91 | private boolean includeCompileDep = false; 92 | @Parameter(property = "capsule.includeRuntimeDep") 93 | private boolean includeRuntimeDep = false; 94 | @Parameter(property = "capsule.includeProvidedDep") 95 | private boolean includeProvidedDep = false; 96 | @Parameter(property = "capsule.includeSystemDep") 97 | private boolean includeSystemDep = false; 98 | @Parameter(property = "capsule.includeTestDep") 99 | private boolean includeTestDep = false; 100 | @Parameter(property = "capsule.includeOptionalDep") 101 | private boolean includeOptionalDep = false; 102 | 103 | @Parameter(property = "capsule.resolveApp") 104 | private boolean resolveApp = false; 105 | @Parameter(property = "capsule.resolveAppDep") 106 | private boolean resolveAppDep = false; 107 | @Parameter(property = "capsule.resolvePluginDep") 108 | private boolean resolvePluginDep = false; 109 | @Parameter(property = "capsule.resolveTransitiveDep") 110 | private boolean resolveTransitiveDep = false; 111 | @Parameter(property = "capsule.resolveCompileDep") 112 | private boolean resolveCompileDep = false; 113 | @Parameter(property = "capsule.resolveRuntimeDep") 114 | private boolean resolveRuntimeDep = false; 115 | @Parameter(property = "capsule.resolveProvidedDep") 116 | private boolean resolveProvidedDep = false; 117 | @Parameter(property = "capsule.resolveSystemDep") 118 | private boolean resolveSystemDep = false; 119 | @Parameter(property = "capsule.resolveTestDep") 120 | private boolean resolveTestDep = false; 121 | @Parameter(property = "capsule.resolveOptionalDep") 122 | private boolean resolveOptionalDep = false; 123 | 124 | @Parameter(property = "capsule.execPluginConfig") 125 | private String execPluginConfig = null; 126 | @Parameter(property = "capsule.fileName") 127 | private String fileName = null; 128 | @Parameter(property = "capsule.fileDesc") 129 | private String fileDesc = "-capsule"; 130 | @Parameter 131 | private Pair[] properties = null; // System-Properties for the app 132 | @Parameter 133 | private Pair[] manifest = null; // additional manifest entries 134 | @Parameter 135 | private Mode[] modes = null; // modes for specific properties and manifest entries 136 | @Parameter 137 | private FileSet[] fileSets = null; // assembly style filesets to add to the capsule 138 | @Parameter 139 | private DependencySet[] dependencySets = null; // assembly style dependency sets to add to the capsule 140 | 141 | // will be loaded when run 142 | private Map capletFiles = new HashMap<>(); 143 | private Xpp3Dom execConfig = null; 144 | private File resolvedCapsuleProjectFile = null; 145 | private File resolvedCapsuleMavenProjectFile = null; 146 | private String outputName; 147 | 148 | @Override 149 | public void execute() throws MojoExecutionException, MojoFailureException { 150 | 151 | // check for type (this overrides custom behaviour) 152 | if (type == Type.empty) { 153 | includeApp = false; 154 | includeAppDep = false; 155 | includePluginDep = false; 156 | includeTransitiveDep = false; 157 | includeCompileDep = false; 158 | includeRuntimeDep = false; 159 | includeProvidedDep = false; 160 | includeSystemDep = false; 161 | includeTestDep = false; 162 | includeOptionalDep = false; 163 | resolveApp = true; 164 | resolveAppDep = true; 165 | resolvePluginDep = true; 166 | resolveTransitiveDep = true; 167 | resolveCompileDep = true; 168 | resolveRuntimeDep = true; 169 | resolveProvidedDep = false; 170 | resolveSystemDep = false; 171 | resolveTestDep = false; 172 | resolveOptionalDep = false; 173 | } else if (type == Type.thin) { 174 | includeApp = true; 175 | includeAppDep = false; 176 | includePluginDep = false; 177 | includeTransitiveDep = false; 178 | includeCompileDep = false; 179 | includeRuntimeDep = false; 180 | includeProvidedDep = false; 181 | includeSystemDep = false; 182 | includeTestDep = false; 183 | includeOptionalDep = false; 184 | resolveApp = false; 185 | resolveAppDep = true; 186 | resolvePluginDep = true; 187 | resolveTransitiveDep = true; 188 | resolveCompileDep = true; 189 | resolveRuntimeDep = true; 190 | resolveProvidedDep = false; 191 | resolveSystemDep = false; 192 | resolveTestDep = false; 193 | resolveOptionalDep = false; 194 | } else if (type == Type.fat) { 195 | includeApp = true; 196 | includeAppDep = true; 197 | includePluginDep = true; 198 | includeTransitiveDep = true; 199 | includeCompileDep = true; 200 | includeRuntimeDep = true; 201 | includeProvidedDep = false; 202 | includeSystemDep = false; 203 | includeTestDep = false; 204 | includeOptionalDep = false; 205 | resolveApp = false; 206 | resolveAppDep = false; 207 | resolvePluginDep = false; 208 | resolveTransitiveDep = false; 209 | resolveCompileDep = false; 210 | resolveRuntimeDep = false; 211 | resolveProvidedDep = false; 212 | resolveSystemDep = false; 213 | resolveTestDep = false; 214 | resolveOptionalDep = false; 215 | } 216 | 217 | // check for exec plugin 218 | if (execPluginConfig != null && project.getPlugin(EXEC_PLUGIN_KEY) != null) { 219 | final Plugin plugin = project.getPlugin(EXEC_PLUGIN_KEY); 220 | if (execPluginConfig.equals("root")) { 221 | execConfig = (Xpp3Dom) plugin.getConfiguration(); 222 | } else { 223 | final List executions = plugin.getExecutions(); 224 | for (final PluginExecution execution : executions) { 225 | if (execution.getId().equals(execPluginConfig)) { 226 | execConfig = (Xpp3Dom) execution.getConfiguration(); 227 | break; 228 | } 229 | } 230 | } 231 | } 232 | 233 | // get app class from exec config (but only if app class is not set) 234 | if (appClass == null && execConfig != null) { 235 | final Xpp3Dom mainClassElement = execConfig.getChild("mainClass"); 236 | if (mainClassElement != null) appClass = mainClassElement.getValue(); 237 | } 238 | 239 | // fail if no app class 240 | if (appClass == null) 241 | throw new MojoFailureException(logPrefix() + " appClass not set (or could not be obtained from the exec plugin mainClass)"); 242 | 243 | // resolve outputDir name (the file name of the capsule jar) 244 | this.outputName = this.fileName != null ? this.fileName : this.finalName; 245 | if (this.fileDesc != null) outputName += this.fileDesc; 246 | 247 | // check for caplets existence 248 | if (this.caplets == null) this.caplets = ""; 249 | if (!caplets.isEmpty()) { 250 | final StringBuilder capletString = new StringBuilder(); 251 | final File classesDir = new File(this.buildDir, "classes"); 252 | for (final String caplet : this.caplets.split(" ")) { 253 | try { 254 | Files.walkFileTree(classesDir.toPath(), new SimpleFileVisitor() { 255 | @Override 256 | public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) { 257 | if (!attrs.isDirectory() && path.toString().contains(caplet)) { 258 | capletFiles.put(caplet, path.toFile()); 259 | return FileVisitResult.TERMINATE; 260 | } 261 | return FileVisitResult.CONTINUE; 262 | } 263 | }); 264 | } catch (final IOException e) { 265 | e.printStackTrace(); 266 | } 267 | 268 | if (!capletFiles.containsKey(caplet)) 269 | if (!caplet.contains(":")) // not from repo 270 | warn("Could not find caplet " + caplet + " class, skipping."); 271 | 272 | if (capletString.length() > 0) capletString.append(" "); 273 | capletString.append(caplet); 274 | } 275 | caplets = capletString.toString(); 276 | } 277 | 278 | // if no capsule ver specified, find the latest one 279 | if (capsuleVersion == null) { 280 | final DefaultArtifact artifact = new DefaultArtifact(CAPSULE_GROUP, "capsule", null, null, "[0,)"); 281 | final VersionRangeRequest request = new VersionRangeRequest().setRepositories(remoteRepos).setArtifact(artifact); 282 | try { 283 | final VersionRangeResult result = repoSystem.resolveVersionRange(repoSession, request); 284 | // get the latest version that is not a snapshot 285 | for (int i = result.getVersions().size() - 1; i >= 0; i--) { 286 | final String currentVersion = result.getVersions().get(i).toString(); 287 | if (!currentVersion.contains("SNAPSHOT")) { 288 | capsuleVersion = result.getVersions().get(i).toString(); 289 | break; 290 | } 291 | } 292 | } catch (VersionRangeResolutionException e) { 293 | throw new MojoFailureException(e.getMessage()); 294 | } 295 | } 296 | 297 | // double check outputDir is not in some undesired locations 298 | final List illegalOutputPaths = Arrays.asList( 299 | this.buildDir.getPath() + File.separatorChar + "classes", 300 | this.buildDir.getPath() + File.separatorChar + "classes/" 301 | ); 302 | if (illegalOutputPaths.contains(this.outputDir.getPath())) { 303 | this.outputDir = this.buildDir; 304 | debug("Output was an illegal path, resorting to default build directory."); 305 | } 306 | 307 | // build path if doesn't exist 308 | if (!outputDir.exists()) { 309 | boolean success = outputDir.mkdirs(); 310 | if (!success) throw new MojoFailureException("Failed to build outputDir path"); 311 | } 312 | 313 | info("[Capsule Version]: " + capsuleVersion); 314 | info("[Output Directory]: " + outputDir.toString()); 315 | info("[Build Info]: " + buildInfoString()); 316 | 317 | try { 318 | build(); 319 | } catch (final IOException e) { 320 | e.printStackTrace(); 321 | throw new MojoFailureException(e.getMessage()); 322 | } 323 | } 324 | 325 | /** 326 | * Build the capsule jar based on the parameters 327 | */ 328 | public void build() throws IOException { 329 | final File jarFile = new File(this.outputDir, this.outputName + ".jar"); 330 | 331 | if (jarFile.exists()) { 332 | info("EXISTS - " + jarFile.getName() + " (WILL OVERWRITE)"); 333 | final boolean deleteResult = jarFile.delete(); 334 | if (!deleteResult) { 335 | warn("FAILED TO DELETE - " + jarFile.getName()); 336 | } 337 | } 338 | 339 | final JarOutputStream jarStream = new JarOutputStream(new FileOutputStream(jarFile)); 340 | info("[Capsule Jar File]: " + jarFile.getName()); 341 | 342 | // add manifest entries 343 | addManifest(jarStream); 344 | 345 | // add Capsule.class 346 | addCapsuleClass(jarStream); 347 | 348 | // add caplets - i.e custom capsule classes (if exists) 349 | addCapletClasses(jarStream); 350 | 351 | // add CapsuleMaven classes (if we need to do any resolving on launch) 352 | addMavenCapletClasses(jarStream); 353 | 354 | // add the app jar 355 | addApp(jarStream); 356 | 357 | // add the dependencies as embedded jars 358 | addDependencies(jarStream); 359 | 360 | // add some files and folders to the capsule from filesets and dependencysets 361 | addFileSets(jarStream); 362 | addDependencySets(jarStream); 363 | 364 | IOUtil.close(jarStream); 365 | 366 | // build the chmod version of the capsule 367 | addChmodCopy(jarFile); 368 | 369 | // build the trampoline version of the capsule 370 | addTrampolineCopy(jarFile); 371 | 372 | // attach the capsule as a maven artifact 373 | info("[Maven Artifact]: Attached capsule artifact to maven (" + jarFile.getName() + ")."); 374 | helper.attachArtifact(project, jarFile, "capsule"); 375 | } 376 | 377 | // BUILD PROCESS 378 | 379 | private void addManifest(final JarOutputStream jar) throws IOException { 380 | final Manifest manifestBuild = new Manifest(); 381 | final Attributes mainAttributes = manifestBuild.getMainAttributes(); 382 | mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 383 | mainAttributes.put(Attributes.Name.MAIN_CLASS, DEFAULT_CAPSULE_NAME); 384 | mainAttributes.put(new Attributes.Name("Application-Class"), this.appClass); 385 | mainAttributes.put(new Attributes.Name("Application-Name"), this.outputName); 386 | mainAttributes.put(new Attributes.Name("Premain-Class"), DEFAULT_CAPSULE_NAME); 387 | mainAttributes.put(new Attributes.Name("Build-Info"), buildInfoString()); 388 | final String artifactsString = artifactString(); 389 | if (!artifactsString.isEmpty()) 390 | mainAttributes.put(new Attributes.Name("Embedded-Artifacts"), artifactsString); 391 | final String dependencyString = dependencyString(); 392 | if (!dependencyString.isEmpty()) 393 | mainAttributes.put(new Attributes.Name("Dependencies"), dependencyString); 394 | 395 | final String repoString = repoString().trim(); 396 | if (!repoString.isEmpty() && setManifestRepos) 397 | mainAttributes.put(new Attributes.Name("Repositories"), repoString); 398 | 399 | // add MavenCapsule caplet (if needed) & others specified by user 400 | if (resolveApp || resolveCompileDep || resolveRuntimeDep || resolveProvidedDep || resolveSystemDep || resolveTestDep) 401 | mainAttributes.put(new Attributes.Name("Caplets"), (DEFAULT_CAPSULE_MAVEN_NAME + " " + this.caplets).trim()); 402 | else if (this.caplets != null && !this.caplets.isEmpty()) 403 | mainAttributes.put(new Attributes.Name("Caplets"), this.caplets.trim()); 404 | 405 | // add properties 406 | final String propertiesString = systemPropertiesString(); 407 | if (propertiesString != null) mainAttributes.put(new Attributes.Name("System-Properties"), propertiesString); 408 | 409 | // get arguments from exec plugin (if exist) 410 | if (execConfig != null) { 411 | final Xpp3Dom argsElement = execConfig.getChild("arguments"); 412 | if (argsElement != null) { 413 | final Xpp3Dom[] argsElements = argsElement.getChildren(); 414 | if (argsElements != null && argsElements.length > 0) { 415 | final StringBuilder argsList = new StringBuilder(); 416 | for (final Xpp3Dom arg : argsElements) { 417 | if (arg != null && arg.getValue() != null) 418 | argsList.append(arg.getValue().replace(" ", "")).append(" "); 419 | } 420 | mainAttributes.put(new Attributes.Name("Args"), argsList.toString()); 421 | } 422 | } 423 | } 424 | 425 | // custom user defined manifest entries (will override any before) 426 | if (this.manifest != null) 427 | for (final Pair entry : this.manifest) 428 | mainAttributes.put(new Attributes.Name(entry.key), entry.value); 429 | 430 | // mode sections 431 | if (this.modes != null) { 432 | for (final Mode mode : this.modes) { 433 | if (mode.name == null) warn("Mode defined without name, ignoring."); 434 | else { 435 | final Attributes modeAttributes = new Attributes(); 436 | // add manifest entries to the mode section (these entries will override the manifests' main entries if mode is selected at runtime) 437 | if (mode.manifest != null) { 438 | for (final Pair entry : mode.manifest) 439 | modeAttributes.put(new Attributes.Name(entry.key), entry.value); 440 | } 441 | // add properties to the mode, this set will override all properties of the previous set. 442 | if (mode.properties != null) { 443 | final StringBuilder modePropertiesList = new StringBuilder(); 444 | for (final Pair property : mode.properties) 445 | if (property.key != null && property.value != null) { 446 | modePropertiesList.append(property.key).append("=").append(property.value).append(" "); 447 | } 448 | if (modePropertiesList.length() > 0) 449 | modeAttributes.put(new Attributes.Name("System-Properties"), modePropertiesList.toString()); 450 | } 451 | // finally add the mode's properties and manifest entries to its own section. 452 | if (!modeAttributes.isEmpty()) manifestBuild.getEntries().put(mode.name, modeAttributes); 453 | } 454 | } 455 | } 456 | 457 | // write to jar 458 | final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); 459 | manifestBuild.write(dataStream); 460 | final byte[] bytes = dataStream.toByteArray(); 461 | final ByteArrayInputStream manifestInputStream = new ByteArrayInputStream(bytes); 462 | 463 | printManifest(manifestBuild); 464 | 465 | addToJar(JarFile.MANIFEST_NAME, manifestInputStream, jar); 466 | } 467 | 468 | private void addCapsuleClass(final JarOutputStream jar) throws IOException { 469 | final JarInputStream capsuleJarInputStream = new JarInputStream(new FileInputStream(resolveCapsule())); 470 | 471 | JarEntry entry; 472 | while ((entry = capsuleJarInputStream.getNextJarEntry()) != null) // look for Capsule.class 473 | if (entry.getName().equals(DEFAULT_CAPSULE_CLASS)) 474 | addToJar(DEFAULT_CAPSULE_CLASS, new ByteArrayInputStream(IOUtil.toByteArray(capsuleJarInputStream)), jar); 475 | } 476 | 477 | private void addCapletClasses(final JarOutputStream jar) throws IOException { 478 | if (caplets != null && !caplets.isEmpty()) { 479 | for (final Map.Entry caplet : this.capletFiles.entrySet()) { 480 | final String path = caplet.getValue().getPath(); 481 | addToJar(path.substring(path.indexOf("classes") + 8), new FileInputStream(caplet.getValue()), jar); 482 | info("\t[Caplet] Embedded Caplet class " + caplet.getKey() + " from " + caplet.getValue()); 483 | } 484 | } 485 | } 486 | 487 | private void addMavenCapletClasses(final JarOutputStream jar) throws IOException { 488 | if (resolveApp || resolveCompileDep || resolveRuntimeDep || resolveProvidedDep || resolveSystemDep || resolveTestDep) { 489 | 490 | // get capsule maven classes 491 | final JarInputStream capsuleJarInputStream = new JarInputStream(new FileInputStream(resolveCapsuleMaven())); 492 | 493 | JarEntry entry; 494 | while ((entry = capsuleJarInputStream.getNextJarEntry()) != null) { 495 | if (entry.getName().contains("capsule") || entry.getName().equals(DEFAULT_CAPSULE_MAVEN_CLASS)) { 496 | addToJar(entry.getName(), new ByteArrayInputStream(IOUtil.toByteArray(capsuleJarInputStream)), jar); 497 | } 498 | } 499 | info("\t[Maven Caplet] Embedded Maven Caplet classes v" + capsuleMavenVersion + " (so capsule can resolve at launch)"); 500 | } 501 | } 502 | 503 | private void addApp(final JarOutputStream jar) throws IOException { 504 | if (includeApp) { 505 | try { 506 | final File mainJarFile = new File(this.buildDir, this.finalName + ".jar"); 507 | addToJar(mainJarFile.getName(), new FileInputStream(mainJarFile), jar); 508 | info("\t[App] App jar embedded (" + mainJarFile.getName() + ")"); 509 | } catch (final FileNotFoundException e) { // if project jar wasn't built (perhaps the mvn package wasn't run, and only the mvn compile was run) 510 | // add compiled project classes instead 511 | warn("\t[App] Couldn't add main jar file to fat capsule, adding the project classes directly instead."); 512 | 513 | final File classesDir = new File(this.buildDir, "classes"); 514 | Files.walkFileTree(classesDir.toPath(), new SimpleFileVisitor() { 515 | @Override 516 | public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) throws IOException { 517 | if (!attrs.isDirectory() && !path.endsWith(".DS_Store") && !path.endsWith("MANIFEST.MF")) { 518 | addToJar(path.toString().substring(path.toString().indexOf("classes") + 8), new FileInputStream(path.toFile()), jar); 519 | debug("\t\t[App] Adding Compile Project Class to Capsule: [" + path.toFile().getPath() + "]"); 520 | } 521 | return FileVisitResult.CONTINUE; 522 | } 523 | }); 524 | info("\t[App] App class files embedded."); 525 | } 526 | } else if (resolveApp) { 527 | info("\t[App] App jar NOT embedded and marked to be resolved at launch."); 528 | } else { 529 | warn("\t[App] App jar NOT embedded and NOT marked to be resolved at launch."); 530 | } 531 | } 532 | 533 | private void addDependencies(final JarOutputStream jar) throws IOException { 534 | 535 | // go through dependencies 536 | final Set artifacts = includeTransitiveDep ? includedDependencyArtifacts() : includedDirectDependencyArtifacts(); 537 | 538 | for (final Artifact artifact : artifacts) { 539 | 540 | final String scope = artifact.getScope() == null || artifact.getScope().isEmpty() ? "compile" : artifact.getScope(); 541 | 542 | boolean optionalMatch = true; 543 | if (artifact.isOptional()) optionalMatch = includeOptionalDep; 544 | 545 | // check artifact has a file 546 | if (artifact.getFile() == null) 547 | warn("\t[Dependency] " + coords(artifact) + "(" + artifact.getScope() + ") file not found, thus will not be added to capsule jar."); 548 | 549 | // ignore capsule jar 550 | if (artifact.getGroupId().equalsIgnoreCase(CAPSULE_GROUP) && artifact.getArtifactId().equalsIgnoreCase(DEFAULT_CAPSULE_NAME)) 551 | continue; 552 | 553 | // check against requested scopes 554 | if ( 555 | (includeCompileDep && scope.equals("compile") && optionalMatch) || 556 | (includeRuntimeDep && scope.equals("runtime") && optionalMatch) || 557 | (includeProvidedDep && scope.equals("provided") && optionalMatch) || 558 | (includeSystemDep && scope.equals("system") && optionalMatch) || 559 | (includeTestDep && scope.equals("test") && optionalMatch) 560 | ) { 561 | addToJar(artifact.getFile().getName(), new FileInputStream(artifact.getFile()), jar); 562 | info("\t[Embedded-Dependency] " + coords(artifact) + "(" + scope + ")"); 563 | } else 564 | debug("\t[Dependency] " + coords(artifact) + "(" + artifact.getScope() + ") skipped, as it does not match any required scope"); 565 | } 566 | } 567 | 568 | private void addFileSets(final JarOutputStream jar) throws IOException { 569 | if (fileSets == null) return; 570 | 571 | for (final FileSet fileSet : fileSets) { 572 | if (fileSet.directory != null && !fileSet.directory.isEmpty()) { 573 | final File fileSetDir = new File(fileSet.directory); 574 | final File directory; 575 | if (fileSetDir.isAbsolute()) { 576 | directory = fileSetDir; 577 | } else { 578 | directory = new File(baseDir.getPath() + File.separatorChar + fileSet.directory); 579 | } 580 | 581 | // warn & skip if not directory 582 | if (!directory.isDirectory()) { 583 | warn("[FileSet] Attempted to include file from non-directory [" + directory.getAbsolutePath() + "], skipping..."); 584 | continue; 585 | } 586 | 587 | final String outputDirectory = addDirectoryToJar(jar, fileSet.outputDirectory); 588 | 589 | final Set matchedEntries = new HashSet<>(); 590 | 591 | // get files entries based on direct files under dir (i.e ignore un sub dirs) 592 | final Set entries = new HashSet<>(); 593 | 594 | final File[] files = directory.listFiles(); 595 | if (files != null) { 596 | for (final File file : files) { 597 | if (!file.isDirectory()) 598 | entries.add(file); 599 | } 600 | } 601 | 602 | for (final File entry : entries) { 603 | System.out.println(entry.toString()); 604 | } 605 | 606 | for (final String include : fileSet.includes) { 607 | 608 | if (include.contains("*")) { // wildcard 609 | 610 | // quick hack to find number of wildcards 611 | final int starCount = include.length() - include.replace("*", "").length(); 612 | 613 | // max one wildcard allowed 614 | if (starCount > 1) { 615 | warn("\t[FileSet]: More than one asterisk (*) found in include, skipping... | " + include); 616 | continue; 617 | } 618 | 619 | // if start 620 | if (include.startsWith("*")) { 621 | final String toMatch = include.substring(1); 622 | for (final File entry : entries) { 623 | if (entry.getName().endsWith(toMatch)) { 624 | matchedEntries.add(entry); 625 | } 626 | } 627 | } 628 | 629 | // if end 630 | else if (include.endsWith("*")) { 631 | final String toMatch = include.substring(0, include.length() - 1); 632 | for (final File entry : entries) { 633 | if (entry.getName().startsWith(toMatch)) { 634 | matchedEntries.add(entry); 635 | } 636 | } 637 | } 638 | 639 | // if middle (check start and end match) 640 | else { 641 | final String[] split = include.split("\\*"); 642 | for (final File entry : entries) { 643 | if (entry.getName().startsWith(split[0]) && entry.getName().endsWith(split[1])) { 644 | matchedEntries.add(entry); 645 | } 646 | } 647 | } 648 | 649 | } else { // match exact (no wildcard) 650 | matchedEntries.add(new File(directory, include)); 651 | } 652 | 653 | 654 | // add all entries matched 655 | 656 | if (!matchedEntries.isEmpty()) { 657 | for (final File entry : matchedEntries) { 658 | addToJar(outputDirectory + entry.getName(), new FileInputStream(entry), jar); 659 | info("\t[FileSet]: Embedded " + outputDirectory + entry.getName() + " from " + directory); 660 | } 661 | } else { 662 | warn("\t[FileSet]: No matches found in " + directory); 663 | } 664 | 665 | } 666 | } 667 | } 668 | } 669 | 670 | private void addDependencySets(final JarOutputStream jar) throws IOException { 671 | if (dependencySets == null) return; 672 | 673 | for (final DependencySet dependencySet : dependencySets) { 674 | 675 | final Artifact artifact = toArtifact(resolve(dependencySet.toString())); 676 | 677 | 678 | if (artifact == null || artifact.getFile() == null) { 679 | warn("\t[DependencySet]: Resolution Fail | " + dependencySet.toString()); 680 | continue; 681 | } 682 | 683 | final JarFile jarFile = new JarFile(artifact.getFile()); 684 | 685 | final Set entries = set(jarFile.entries()); 686 | 687 | final String outputDirectory = addDirectoryToJar(jar, dependencySet.outputDirectory); 688 | 689 | // if includes is set add only specified 690 | if (dependencySet.includes != null && dependencySet.includes.length > 0) { 691 | final Set matchedEntries = new HashSet<>(); 692 | 693 | for (final String include : dependencySet.includes) { 694 | 695 | if (include.contains("*")) { // wildcard 696 | 697 | // quick hack to find number of wildcards 698 | final int starCount = include.length() - include.replace("*", "").length(); 699 | 700 | // max one wildcard allowed 701 | if (starCount > 1) { 702 | warn("\t[DependencySet]: More than one asterisk (*) found in include, skipping... | " + include); 703 | continue; 704 | } 705 | 706 | // if start 707 | if (include.startsWith("*")) { 708 | final String toMatch = include.substring(1); 709 | for (final ZipEntry entry : entries) { 710 | if (entry.getName().endsWith(toMatch)) { 711 | matchedEntries.add(entry); 712 | } 713 | } 714 | } 715 | 716 | // if end 717 | else if (include.endsWith("*")) { 718 | final String toMatch = include.substring(0, include.length() - 1); 719 | for (final ZipEntry entry : entries) { 720 | if (entry.getName().startsWith(toMatch)) { 721 | matchedEntries.add(entry); 722 | } 723 | } 724 | } 725 | 726 | // if middle (check start and end match) 727 | else { 728 | final String[] split = include.split("\\*"); 729 | for (final ZipEntry entry : entries) { 730 | if (entry.getName().startsWith(split[0]) && entry.getName().endsWith(split[1])) { 731 | matchedEntries.add(entry); 732 | } 733 | } 734 | } 735 | 736 | } else { // match exact (no wildcard) 737 | matchedEntries.add(jarFile.getEntry(include)); 738 | } 739 | 740 | } 741 | 742 | // add all entries matched 743 | 744 | if (!matchedEntries.isEmpty()) { 745 | for (final ZipEntry entry : matchedEntries) { 746 | addToJar(outputDirectory + entry.getName(), jarFile.getInputStream(entry), jar); 747 | info("\t[DependencySet]: Embedded from " + coords(artifact) + " > " + outputDirectory + entry.getName()); 748 | } 749 | } else { 750 | warn("\t[DependencySet]: No matches found in " + artifact.getFile()); 751 | } 752 | 753 | // else add whole file 754 | } else { 755 | if (!dependencySet.unpack) { 756 | info("\t[DependencySet]: Adding " + artifact.getFile().getName() + " to " + outputDirectory); 757 | addToJar(outputDirectory + artifact.getFile().getName(), new FileInputStream(artifact.getFile()), jar); 758 | } else { 759 | if (artifact.getType() != null && artifact.getType().equals("jar")) { 760 | info("\t[DependencySet]: Adding (unpacked) " + artifact.getFile().getName() + " to " + outputDirectory); 761 | for (final ZipEntry entry : entries) { 762 | debug("\t\t[DependencySet]: Adding (unpacked) " + outputDirectory + entry.getName()); 763 | addToJar(outputDirectory + entry.getName(), jarFile.getInputStream(entry), jar); 764 | } 765 | } else { 766 | warn("\t[DependencySet]: Cannot unpack " + artifact.getFile().getName() + " as it is not in jar format."); 767 | } 768 | } 769 | } 770 | 771 | } 772 | } 773 | 774 | private void addChmodCopy(final File jar) throws IOException { 775 | if (this.chmod) { 776 | final File file = createExecCopyProcess(jar, EXEC_PREFIX, ".x"); 777 | info("[Capsule CHMOD]: " + file.getName()); 778 | } 779 | } 780 | 781 | private void addTrampolineCopy(final File jar) throws IOException { 782 | if (this.trampoline) { 783 | final File file = createExecCopyProcess(jar, EXEC_TRAMPOLINE_PREFIX, ".tx"); 784 | info("[Capsule Trampoline]: " + file.getName()); 785 | } 786 | } 787 | 788 | // STRINGS 789 | 790 | private String buildInfoString() { 791 | final StringBuilder builder = new StringBuilder(); 792 | if (includeApp) builder.append("includeApp "); 793 | if (includeAppDep) builder.append("includeAppDep "); 794 | if (includePluginDep) builder.append("includePluginDep "); 795 | if (includeCompileDep) builder.append("includeCompileDep "); 796 | if (includeRuntimeDep) builder.append("includeRuntimeDep "); 797 | if (includeProvidedDep) builder.append("includeProvidedDep "); 798 | if (includeSystemDep) builder.append("includeSystemDep "); 799 | if (includeTestDep) builder.append("includeTestDep "); 800 | if (includeTransitiveDep) builder.append("includeTransitiveDep "); 801 | 802 | if (resolveApp) builder.append("resolveApp "); 803 | if (resolveAppDep) builder.append("resolveAppDep "); 804 | if (resolvePluginDep) builder.append("resolvePluginDep "); 805 | if (resolveCompileDep) builder.append("resolveCompileDep "); 806 | if (resolveRuntimeDep) builder.append("resolveRuntimeDep "); 807 | if (resolveProvidedDep) builder.append("resolveProvidedDep "); 808 | if (resolveSystemDep) builder.append("resolveSystemDep "); 809 | if (resolveTestDep) builder.append("resolveTestDep "); 810 | if (resolveTransitiveDep) builder.append("resolveTransitiveDep "); 811 | 812 | return builder.toString().trim(); 813 | } 814 | 815 | private String repoString() { 816 | final StringBuilder repoList = new StringBuilder(); 817 | for (final RemoteRepository repository : this.remoteRepos) 818 | repoList.append(repository.getId()).append("(").append(repository.getUrl()).append(") "); 819 | return repoList.toString(); 820 | } 821 | 822 | private String artifactString() throws IOException { 823 | final StringBuilder artifactList = new StringBuilder(); 824 | 825 | if (includeApp) artifactList.append(coords(project.getArtifact())).append(" "); 826 | 827 | // go through artifacts 828 | final Set dependencies = includeTransitiveDep ? includedDependencies() : includedDirectDependencies(); 829 | 830 | for (final Dependency dependency : dependencies) { 831 | 832 | final String scope = dependency.getScope() == null || dependency.getScope().isEmpty() ? "compile" : dependency.getScope(); 833 | 834 | boolean optionalMatch = true; 835 | if (dependency.isOptional()) optionalMatch = includeOptionalDep; 836 | 837 | // ignore capsule jar 838 | if (dependency.getGroupId().equalsIgnoreCase(CAPSULE_GROUP) && dependency.getArtifactId().equalsIgnoreCase(DEFAULT_CAPSULE_NAME)) 839 | continue; 840 | 841 | // check against requested scopes 842 | if ( 843 | (includeCompileDep && scope.equals("compile") && optionalMatch) || 844 | (includeRuntimeDep && scope.equals("runtime") && optionalMatch) || 845 | (includeProvidedDep && scope.equals("provided") && optionalMatch) || 846 | (includeSystemDep && scope.equals("system") && optionalMatch) || 847 | (includeTestDep && scope.equals("test") && optionalMatch) 848 | ) 849 | artifactList.append(coordsWithExclusions(dependency)).append(" "); 850 | } 851 | 852 | return artifactList.toString(); 853 | } 854 | 855 | private String dependencyString() throws IOException { 856 | final StringBuilder dependenciesList = new StringBuilder(); 857 | 858 | // add app to be resolved 859 | if (resolveApp) 860 | dependenciesList.append(coords(this.project.getArtifact())).append(" "); 861 | 862 | // go through dependencies 863 | final Set dependencies = resolveTransitiveDep ? resolvedDependencies() : resolvedDirectDependencies(); 864 | 865 | for (final Dependency dependency : dependencies) { 866 | 867 | final String scope = dependency.getScope() == null || dependency.getScope().isEmpty() ? "compile" : dependency.getScope(); 868 | 869 | boolean optionalMatch = true; 870 | if (dependency.isOptional()) optionalMatch = resolveOptionalDep; 871 | 872 | // ignore capsule jar 873 | if (dependency.getGroupId().equalsIgnoreCase(CAPSULE_GROUP) && dependency.getArtifactId().equalsIgnoreCase(DEFAULT_CAPSULE_NAME)) 874 | continue; 875 | 876 | // check against requested scopes 877 | if ( 878 | (resolveCompileDep && scope.equals("compile") && optionalMatch) || 879 | (resolveRuntimeDep && scope.equals("runtime") && optionalMatch) || 880 | (resolveProvidedDep && scope.equals("provided") && optionalMatch) || 881 | (resolveSystemDep && scope.equals("system") && optionalMatch) || 882 | (resolveTestDep && scope.equals("test") && optionalMatch) 883 | ) 884 | dependenciesList.append(coordsWithExclusions(dependency)).append(" "); 885 | } 886 | 887 | return dependenciesList.toString(); 888 | } 889 | 890 | private String systemPropertiesString() { 891 | StringBuilder propertiesList = null; 892 | if (this.properties != null) { 893 | propertiesList = new StringBuilder(); 894 | for (final Pair property : this.properties) { 895 | if (property.key != null) { 896 | propertiesList.append(property.key); 897 | if (property.value != null && (property.value instanceof String && !((String) property.value).isEmpty())) 898 | propertiesList.append("=").append(property.value); 899 | propertiesList.append(" "); 900 | } 901 | } 902 | } else if (execConfig != null) { // else try and find properties in the exec plugin 903 | propertiesList = new StringBuilder(); 904 | final Xpp3Dom propertiesElement = execConfig.getChild("systemProperties"); 905 | if (propertiesElement != null) { 906 | final Xpp3Dom[] propertiesElements = propertiesElement.getChildren(); 907 | if (propertiesElements != null && propertiesElements.length > 0) { 908 | for (final Xpp3Dom propertyElement : propertiesElements) { 909 | final Xpp3Dom key = propertyElement.getChild("key"); 910 | final Xpp3Dom value = propertyElement.getChild("value"); 911 | if (key != null && key.getValue() != null) { 912 | propertiesList.append(key.getValue()).append("="); 913 | if (value != null && value.getValue() != null && !value.getValue().isEmpty()) 914 | propertiesList.append("=").append(value.getValue()); 915 | propertiesList.append(" "); 916 | } 917 | } 918 | } 919 | } 920 | } 921 | return propertiesList == null ? null : propertiesList.toString().trim(); 922 | } 923 | 924 | private File createExecCopyProcess(final File jar, final String prefix, final String extension) throws IOException { 925 | final File x = new File(jar.getPath().replace(".jar", extension)); 926 | if (x.exists()) { 927 | debug("EXISTS - " + x.getName()); 928 | return x; 929 | } 930 | 931 | FileOutputStream out = null; 932 | FileInputStream in = null; 933 | try { 934 | out = new FileOutputStream(x); 935 | in = new FileInputStream(jar); 936 | out.write((prefix).getBytes("ASCII")); 937 | Files.copy(jar.toPath(), out); 938 | out.flush(); 939 | // Runtime.getRuntime().exec("chmod +x " + x.getAbsolutePath()); 940 | final boolean execResult = x.setExecutable(true, false); 941 | if (!execResult) 942 | warn("Failed to mark file executable - " + x.getAbsolutePath()); 943 | } finally { 944 | IOUtil.close(in); 945 | IOUtil.close(out); 946 | } 947 | return x; 948 | } 949 | 950 | // RESOLVERS 951 | 952 | private File resolveCapsule() throws IOException { 953 | if (this.resolvedCapsuleProjectFile == null) { 954 | final ArtifactResult artifactResult = this.resolve(CAPSULE_GROUP, "capsule", null, capsuleVersion); 955 | if (artifactResult == null) throw new IOException("Capsule not found from repos"); 956 | this.resolvedCapsuleProjectFile = artifactResult.getArtifact().getFile(); 957 | } 958 | return this.resolvedCapsuleProjectFile; 959 | } 960 | 961 | private File resolveCapsuleMaven() throws IOException { 962 | if (this.resolvedCapsuleMavenProjectFile == null) { 963 | final ArtifactResult artifactResult = this.resolve(CAPSULE_GROUP, "capsule-maven", null, capsuleMavenVersion); 964 | if (artifactResult == null) throw new IOException("CapsuleMaven not found from repos"); 965 | this.resolvedCapsuleMavenProjectFile = artifactResult.getArtifact().getFile(); 966 | } 967 | return this.resolvedCapsuleMavenProjectFile; 968 | } 969 | 970 | private Set includedDependencies() { 971 | return cleanDependencies(appDependencies(), this.includeAppDep, pluginDependencies(), this.includePluginDep); 972 | } 973 | 974 | private Set includedDirectDependencies() { 975 | return cleanDependencies(appDirectDependencies(), this.includeAppDep, pluginDirectDependencies(), this.includePluginDep); 976 | } 977 | 978 | private Set includedDependencyArtifacts() { 979 | return cleanArtifacts(appDependencyArtifacts(), this.includeAppDep, pluginDependencyArtifacts(), this.includePluginDep); 980 | } 981 | 982 | private Set includedDirectDependencyArtifacts() { 983 | return cleanArtifacts(appDirectDependencyArtifacts(), this.includeAppDep, pluginDirectDependencyArtifacts(), this.includePluginDep); 984 | } 985 | 986 | private Set resolvedDependencies() { 987 | return cleanDependencies(appDependencies(), this.resolveAppDep, pluginDependencies(), this.resolvePluginDep); 988 | } 989 | 990 | private Set resolvedDirectDependencies() { 991 | return cleanDependencies(appDirectDependencies(), this.resolveAppDep, pluginDirectDependencies(), this.resolvePluginDep); 992 | } 993 | 994 | // HELPER OBJECTS 995 | 996 | public enum Type { 997 | empty, thin, fat; 998 | } 999 | 1000 | public static class Mode { 1001 | private String name = null; 1002 | private Pair[] properties = null; 1003 | private Pair[] manifest = null; 1004 | } 1005 | 1006 | public static class DependencySet { 1007 | public String groupId; 1008 | public String artifactId; 1009 | public String classifier; 1010 | public String version; 1011 | public String outputDirectory = "/"; 1012 | public String[] includes; 1013 | public boolean unpack = false; // will unpack file of jar, zip, tar.gz, and tar.bz 1014 | 1015 | public String toString() { 1016 | return coords(groupId, artifactId, classifier, version); 1017 | } 1018 | } 1019 | 1020 | public static class FileSet { 1021 | public String directory; 1022 | public String outputDirectory; 1023 | public String[] includes; 1024 | } 1025 | 1026 | } 1027 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Capsule Maven Plugin 2 | ==================== 3 | 4 | [![Version](http://img.shields.io/badge/version-1.5.1-blue.svg?style=flat)](https://github.com/chrisdchristo/capsule-maven-plugin/releases) 5 | [![Maven Central](http://img.shields.io/badge/maven_central-1.5.1-blue.svg?style=flat)](http://mvnrepository.com/artifact/com.github.chrisdchristo/capsule-maven-plugin/) 6 | [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT) 7 | 8 | A maven plugin to build a [capsule](https://github.com/puniverse/capsule) out of your app. 9 | 10 | See more at [capsule](https://github.com/puniverse/capsule) and the [demo using the plugin](https://github.com/chrisdchristo/capsule-maven-plugin-demo). 11 | 12 | A pro? [Skip to the plugin reference](https://github.com/chrisdchristo/capsule-maven-plugin#reference). 13 | 14 | Requires java version 1.7+ and maven 3.1.x+ 15 | 16 | Supports [Capsule v1.0.3](https://github.com/puniverse/capsule/releases/tag/v1.0.3) & [CapsuleMaven v1.0.3](https://github.com/puniverse/capsule-maven/releases/tag/v1.0.3) and below (It may also work with new versions of Capsule, but use at your own risk). 17 | 18 | - [Building From Source](https://github.com/chrisdchristo/capsule-maven-plugin#building-from-source) 19 | - [Quick Start](https://github.com/chrisdchristo/capsule-maven-plugin#quick-start) 20 | - [Building Automatically](https://github.com/chrisdchristo/capsule-maven-plugin#building-automatically) 21 | - [Capsule Contents](https://github.com/chrisdchristo/capsule-maven-plugin#capsule-contents) 22 | - [The Simple Types](https://github.com/chrisdchristo/capsule-maven-plugin#the-simple-types) 23 | - [Custom Builds](https://github.com/chrisdchristo/capsule-maven-plugin#custom-builds) 24 | - [Including Dependencies based on source](https://github.com/chrisdchristo/capsule-maven-plugin#including-dependencies-based-on-source) 25 | - [Including Dependencies based on scope](https://github.com/chrisdchristo/capsule-maven-plugin#including-dependencies-based-on-scope) 26 | - [Include Optional Dependencies](https://github.com/chrisdchristo/capsule-maven-plugin#include-optional-dependencies) 27 | - [Include Transitive Dependencies](https://github.com/chrisdchristo/capsule-maven-plugin#include-transitive-dependencies) 28 | - [Understanding Dependency Scope](https://github.com/chrisdchristo/capsule-maven-plugin#understanding-dependency-scope) 29 | - [Runtime Resolution](https://github.com/chrisdchristo/capsule-maven-plugin#runtime-resolution) 30 | - [Really Executable Capsules](https://github.com/chrisdchristo/capsule-maven-plugin#really-executable-capsules-maclinux-only) 31 | - [Providing Your App System Properties](https://github.com/chrisdchristo/capsule-maven-plugin#providing-your-app-system-properties) 32 | - [Additional Manifest Entries](https://github.com/chrisdchristo/capsule-maven-plugin#additional-manifest-entries) 33 | - [Custom File Name](https://github.com/chrisdchristo/capsule-maven-plugin#custom-file-name) 34 | - [Modes](https://github.com/chrisdchristo/capsule-maven-plugin#modes) 35 | - [FileSets](https://github.com/chrisdchristo/capsule-maven-plugin#filesets) 36 | - [DependencySets](https://github.com/chrisdchristo/capsule-maven-plugin#dependencysets) 37 | - [Custom Capsule Version](https://github.com/chrisdchristo/capsule-maven-plugin#custom-capsule-version) 38 | - [Caplets](https://github.com/chrisdchristo/capsule-maven-plugin#caplets) 39 | - [Maven Exec Plugin Integration](https://github.com/chrisdchristo/capsule-maven-plugin#maven-exec-plugin-integration) 40 | - [Reference](https://github.com/chrisdchristo/capsule-maven-plugin#reference) 41 | 42 | ## Building From source 43 | 44 | Clone the project and run a maven install: 45 | 46 | ``` 47 | git clone https://github.com/chrisdchristo/capsule-maven-plugin.git 48 | cd capsule-maven-plugin 49 | mvn install 50 | ``` 51 | 52 | Alternatively you can let maven pick up the latest version from [maven central](http://mvnrepository.com/artifact/chrisdchristo/capsule-maven-plugin). 53 | 54 | 55 | ## Quick Start 56 | 57 | In the simplest form, you can add the following snippet in your `pom.xml`: 58 | 59 | ``` 60 | 61 | com.github.chrisdchristo 62 | capsule-maven-plugin 63 | ${capsule.maven.plugin.version} 64 | 65 | hello.HelloWorld 66 | fat 67 | 68 | 69 | ``` 70 | 71 | And then run: 72 | 73 | ``` 74 | mvn package capsule:build 75 | ``` 76 | 77 | Please note that the `package` command must have been executed before the `capsule:build` command can be run. 78 | 79 | The only requirement is to have the `` attribute in the configuration. This is the class of your app that contains the main method which will be fired on startup. You must include the package path along with the class name (`hello` is the package and `HelloWorld` is the class name above). 80 | 81 | It is recommended to have specified the `capsule.version` property in your pom so that the capsule plugin knows which version of capsule to use. 82 | If none is specified, the default version of Capsule will be used as specified at the top of the readme (which may not be the latest). 83 | 84 | You can also set the `capsule.maven.version` property to tell the plugin which version of CapsuleMaven to use. 85 | 86 | 87 | ## Building Automatically 88 | 89 | It is recommended to have an execution setup to build the capsules, thus eliminating you to run an additional maven command to build them. 90 | 91 | ``` 92 | 93 | com.github.chrisdchristo 94 | capsule-maven-plugin 95 | ${capsule.maven.plugin.version} 96 | 97 | 98 | 99 | build 100 | 101 | 102 | hello.HelloWorld 103 | fat 104 | 105 | 106 | 107 | 108 | ``` 109 | 110 | By default the `build` goal runs during the package phase. 111 | 112 | So now if you were to run simply `mvn package` then the build goal will execute which will build the capsules into your build directory. 113 | 114 | Or alternatively you could use the `maven-exec-plugin` to run your app (as you develop), and then only build the capsule(s) when you want to deploy to a server. This plugin integrates nicely with the `maven-exec-plugin`, [see here](https://github.com/chrisdchristo/capsule-maven-plugin#maven-exec-plugin-integration). 115 | 116 | ## Capsule Contents 117 | 118 | Essentially Capsule can be packaged with as much or as little as you want. 119 | 120 | Two things you need to think about to make up a capsule are, the app jar and the dependency jars. And these, as you will see, can be optionally included! 121 | 122 | The source of dependencies is taken from two places. Firstly, from the `````` tag defined under the root `````` tag, namely the app dependencies. Secondly the dependencies defined under the `````` tag within the `````` tag for this plugin, also known as the plugin dependencies. 123 | 124 | You have the option to include all, none or some of the dependencies. 125 | 126 | This can be done with various flags as you will see later, based on their source, scope, their ```optional``` flag and if they are direct (root) or indirect (transitive) dependencies. 127 | 128 | ### The Simple Types 129 | 130 | Generally, the most common types of capsules are the following three: 131 | 132 | - `fat`: This capsule jar will contain your app's jar as well as **all** its dependencies. When the fat-jar is run, then Capsule will simply setup the app and run it. 133 | - `thin`: This capsule jar will contain your app's classes but **no** dependencies. Capsule will resolve these dependencies at runtime (in the cache). 134 | - `empty`: This capsule will not even include your app, or any of its dependencies. It will only contain the name of your app declared in the jar's manifest, along with capsule's classes. Capsule will read the manifest entry ```Application``` and resolve the app and its dependencies in Capsule's own cache (default `~/.capsule`). 135 | 136 | These are just the popular types that fit neatly into a box, and thus the plugin provides a simple flag to build these. Namely the `````` field can be set to ```fat```, ```thin``` or ```empty```. 137 | 138 | To build a ```fat``` capsule simply include the following: 139 | 140 | ``` 141 | 142 | com.github.chrisdchristo 143 | capsule-maven-plugin 144 | ${capsule.maven.plugin.version} 145 | 146 | hello.HelloWorld 147 | fat 148 | 149 | 150 | ``` 151 | 152 | And similarly for ```thin``` and ```empty```: 153 | 154 | ``` 155 | 156 | com.github.chrisdchristo 157 | capsule-maven-plugin 158 | ${capsule.maven.plugin.version} 159 | 160 | hello.HelloWorld 161 | thin 162 | 163 | 164 | ``` 165 | 166 | ``` 167 | 168 | com.github.chrisdchristo 169 | capsule-maven-plugin 170 | ${capsule.maven.plugin.version} 171 | 172 | hello.HelloWorld 173 | empty 174 | 175 | 176 | ``` 177 | 178 | Note that the three simple types apply to only the ```compile``` and ```runtime``` scoped dependencies (but cover the transitive dependencies). More on this later. 179 | 180 | If none of these quite fit, then the plugin can accommodate a wide range of different setups, it is encouraged you build the capsule with your own specific requirements without being bogged down on the three specific types listed above. 181 | 182 | ### Custom Builds 183 | 184 | (Note, to make a custom build, the `````` tag must not be set!) 185 | 186 | If the types defined in the `````` don't quite fit your needs and you need something a little different, then you can easily customise the jar to a vast array of options. 187 | 188 | Essentially it comes down to the following scenarios; whether or not to include the app or resolve it at runtime; what dependencies to include based on their source, scope etc and which to resolve at runtime; and same question for the transitive dependencies. 189 | 190 | So to cover all these ideas, you have the following flags: 191 | 192 | ``` 193 | false 194 | false 195 | false 196 | false 197 | false 198 | false 199 | false 200 | false 201 | false 202 | false 203 | 204 | false 205 | false 206 | false 207 | false 208 | false 209 | false 210 | false 211 | false 212 | false 213 | false 214 | ``` 215 | 216 | All of the above settings are ```false``` by default. 217 | 218 | These ```includeXYZ``` flags essentially tell the plugin what to include/embed in the jar. Of course if there are any of these that you exclude from the capsule jar, and in turn they are needed for the launch, then runtime resolution will be needed by marking some ```resolveXYZ``` to ```true```. 219 | 220 | A ```fat``` capsule essentially is equivalent to having only the following set to ```true```: 221 | 222 | ``` 223 | true 224 | true 225 | true 226 | true 227 | true 228 | true 229 | ``` 230 | 231 | So as you can see, we include the app, and all its compile and runtime dependencies (including the transitive dependencies). 232 | 233 | So if a ```thin``` capsule is desired, it can be done like so: 234 | 235 | ``` 236 | true 237 | true 238 | true 239 | true 240 | true 241 | true 242 | ``` 243 | 244 | And similarly an ```empty``` capsule is done with the following: 245 | 246 | ``` 247 | true 248 | true 249 | true 250 | true 251 | true 252 | true 253 | ``` 254 | 255 | ### Including Dependencies based on source 256 | 257 | The source of dependencies is taken from two places. Firstly, from the `````` tag defined under the root `````` tag, namely the app dependencies. Secondly the dependencies defined under the `````` tag within the `````` tag for this plugin, also known as the plugin dependencies. 258 | 259 | You can choose to include a source by using ```true``` or ```true```. 260 | 261 | 262 | ### Including Dependencies based on scope 263 | 264 | You can include certain dependencies by setting the scope of the dependency to something that you will be including in the built capsule. 265 | 266 | All possible options for scope are ```compile```, ```runtime```, ```provided```, ```system``` or ```test```. 267 | 268 | Note that the plugin dependencies can only have scope ```compile```, ```runtime``` or ```system```. 269 | 270 | So you could set your dependency to scope ```runtime``` like so: 271 | 272 | ``` 273 | 274 | com.google.guava 275 | guava 276 | 17.0 277 | runtime 278 | 279 | ``` 280 | 281 | And then mark the necessary flags: 282 | 283 | ``` 284 | 285 | true 286 | 287 | ``` 288 | 289 | or if you want to resolve them instead: 290 | 291 | ``` 292 | 293 | true 294 | 295 | ``` 296 | 297 | So the above will not include the dependencies marked with ```runtime``` scope, however it will resolve them at launch. 298 | 299 | Just make sure you have a source also set to true for example, ```true``` or ```true```. 300 | 301 | ### Include Optional Dependencies 302 | 303 | Dependencies can be marked with the `````` tag, for example: 304 | 305 | ``` 306 | 307 | com.google.guava 308 | guava 309 | true 310 | 311 | ``` 312 | 313 | To include optional dependencies in the capsule, you simply need to turn on a flag: 314 | 315 | ``` 316 | 317 | true 318 | 319 | ``` 320 | 321 | or if you want to resolve them instead: 322 | 323 | ``` 324 | 325 | true 326 | 327 | ``` 328 | 329 | Just make sure you have a source also set to true for example, ```true``` or ```true```. 330 | 331 | ### Include Transitive Dependencies 332 | 333 | Transitive dependencies are essentially the deep dependencies or in other wors the dependencies of your dependencies. 334 | 335 | You can include transitive dependencies by setting the configuration property `includeTransitiveDep` to true: 336 | 337 | ``` 338 | 339 | true 340 | 341 | ``` 342 | 343 | or if you want to resolve them instead: 344 | 345 | ``` 346 | 347 | true 348 | 349 | ``` 350 | 351 | Just make sure you have a source also set to true for example, ```true``` or ```true```. 352 | 353 | ### Understanding Dependency Scope 354 | 355 | In maven, you can essentially define the following five scopes for your dependencies; ```compile```, ```runtime```, ```provided```, ```system``` and ```test```. 356 | 357 | You set the scope on each of the project's direct dependencies. Although transitive dependencies will have defined scope also, this only applies specifically to their own project. 358 | 359 | The scope of transitive dependencies in relation to the main project will be directly affected by the scope of its parent dependency. We call this the 'direct-scope'. 360 | 361 | Also note, that transitive dependencies with scope other than ```compile``` or ```runtime``` are not applicable to the main project and thus are **excluded always**. 362 | 363 | So for each direct dependency with scope: 364 | 365 | * ```compile``` 366 | - ```compile``` transitive dependencies have ```compile``` direct-scope. 367 | - ```runtime``` transitive dependencies have ```runtime``` direct-scope. 368 | * ```runtime``` 369 | - ```compile``` transitive dependencies have ```compile``` direct-scope. 370 | - ```runtime``` transitive dependencies have ```runtime``` direct-scope. 371 | * ```provided``` 372 | - ```compile``` & ```runtime``` transitive dependencies have ```provided``` direct-scope. 373 | * ```system``` 374 | - ```compile``` & ```runtime``` transitive dependencies have ```system``` direct-scope. 375 | * ```test``` 376 | - ```compile``` & ```runtime``` transitive dependencies have ```test``` direct-scope. 377 | 378 | So, all the ```includeXYZ``` and ```resolveXYZ``` follow the above rules. 379 | 380 | 381 | 382 | 383 | ## Runtime Resolution 384 | 385 | To perform the resolution at runtime (such as needed by the ```thin``` and ```empty``` types), the capsule will include the necessary code to do this (namely the ```MavenCaplet```). This adds slightly to the overall file size of the generated capsule jar. This additional code is obviously mandatory if any dependencies (or the app itself) needs to be resolved at runtime. 386 | 387 | To build the capsule without this additional code, make sure none of the ```resolveXYZ``` flags are set to true (by default all set to false or the `````` is set to ```fat```). 388 | 389 | If making a custom build and resolution is needed at runtime, then add the desired ```resolveXYZ``` tags in the `````` tag like so: 390 | 391 | ``` 392 | 393 | hello.HelloWorld 394 | true 395 | true 396 | 397 | ``` 398 | 399 | ## Really Executable Capsules (Mac/Linux only) 400 | 401 | It is possible to `chmod+x` a jar so it can be run without needing to prefix the command with `java -jar`. You can see more info about this concept [here](https://github.com/brianm/really-executable-jars-maven-plugin) and [here](http://skife.org/java/unix/2011/06/20/really_executable_jars.html). 402 | 403 | The plugin can build really executable jars for you automatically! 404 | 405 | Add the `true` to your configuration (default is false). 406 | 407 | ``` 408 | 409 | hello.HelloWorld 410 | true 411 | 412 | ``` 413 | 414 | The plugin will then output the really executables with the extension `.x`. 415 | 416 | ``` 417 | target/my-app-1.0-cap.jar 418 | target/my-app-1.0-cap.x 419 | ``` 420 | 421 | So normally you would run the capsule like so: 422 | 423 | ``` 424 | java -jar target/my-app-1.0-cap.jar 425 | ``` 426 | 427 | However with the really executable builds, you can alternatively run the capsule nice and cleanly: 428 | 429 | ``` 430 | ./target/my-app-1.0-cap.x 431 | ``` 432 | 433 | or 434 | 435 | ``` 436 | sh target/my-app-1.0-cap.x 437 | ``` 438 | 439 | ##### Trampoline 440 | 441 | When a capsule is launched, two processes are involved: first, a JVM process runs the capsule launcher, which then starts up a second, child process that runs the actual application. The two processes are linked so that killing or suspending one, will do the same for the other. While this model works well enough in most scenarios, sometimes it is desirable to directly launch the process running the application, rather than indirectly. This is supported by "capsule trampoline". [See more here at capsule](http://www.capsule.io/user-guide/#the-capsule-execution-process). 442 | 443 | Essentially the concept defines that that when you execute the built Capsule jar, it will simply just output (in text) the full command needed to run the app (this will be a long command with all jvm and classpath args defined). The idea is then to just copy/paste the command and execute it raw. 444 | 445 | If you would like to build 'trampoline' executable capsules you can add the `true` flag to the plugin's configuration: 446 | 447 | ``` 448 | 449 | hello.HelloWorld 450 | true 451 | 452 | ``` 453 | 454 | This will build `.tx` files like so: 455 | 456 | ``` 457 | target/my-app-1.0-cap.jar 458 | target/my-app-1.0-cap.tx 459 | ``` 460 | 461 | Which you can run: 462 | 463 | ``` 464 | ./target/my-app-1.0-cap.tx 465 | ``` 466 | 467 | This will output the command which you then have to copy and paste and run it yourself manually, thus ensuring you have only one process for your app. 468 | 469 | ## Providing your app System Properties 470 | 471 | Capsule also supports providing your app with system properties. This can be done at runtime but its also convenient to define some properties at build time too. 472 | 473 | Simply add the `` tag in the plugin's configuration and declare any properties. 474 | 475 | ``` 476 | 477 | hello.HelloWorld 478 | 479 | 480 | boo 481 | ya 482 | 483 | 484 | 485 | ``` 486 | 487 | Then, anywhere in your app's code you call upon this system property: 488 | 489 | ``` 490 | package hello; 491 | public class HelloWorld { 492 | public static void main(String[] args) { 493 | System.out.println(System.getProperty("boo")); // outputs 'ya' 494 | } 495 | } 496 | ``` 497 | 498 | ## Additional Manifest Entries 499 | 500 | Capsule supports a number of manifest entries to configure your app to your heart's content. See the full reference [here](http://www.capsule.io/reference/#manifest-attributes). 501 | 502 | So for e.g if you would like to set the `JVM-Args`: 503 | 504 | ``` 505 | 506 | hello.HelloWorld 507 | 508 | 509 | JVM-Args 510 | -Xmx512m 511 | 512 | 513 | 514 | ``` 515 | 516 | Note you do **not** need `Main-Class`, `Application-Class`, `Application`, `Dependencies` and `System-Properties` as these are generated automatically by the plugin. 517 | 518 | ## Custom File Name 519 | 520 | The output capsule jar's name is as per the `` tag with the appending of the ```-capsule```, by default. 521 | 522 | Essentially this is ```-capsule.jar``` so for example your app might be ```app-capsule.jar```. 523 | 524 | If you wish to have custom text, then you can optionally set either of parameters ```fileName``` and ```fileDesc``` which make up the format: 525 | 526 | ``` 527 | .jar 528 | ``` 529 | 530 | So for example if you'd like to have your output capsule jar like 'my-amazing-app-cap.jar' then you would do the following: 531 | 532 | ``` 533 | 534 | 535 | 536 | com.github.chrisdchristo 537 | capsule-maven-plugin 538 | ${capsule.maven.plugin.version} 539 | 540 | hello.HelloWorld 541 | my-amazing-app 542 | -cap 543 | 544 | 545 | 546 | 547 | ``` 548 | 549 | ## Modes 550 | 551 | Capsule supports the concept of modes, which essentially means defining your app jar into different ways depending on certain characteristics. 552 | You define different modes for your app by setting specific manifest and/or system properties for each mode. So for e.g you could have a test mode which will define a test database connection, and likewise a production mode which will define a production database connection. 553 | You can then easily run your capsule in a specific mode by adding the `-Dcapsule.mode=MODE` argument at the command line. See more at [capsule modes](http://www.capsule.io/user-guide/#modes-platform--and-version-specific-configuration). 554 | 555 | The maven plugin supports a convenient way to define modes for your capsule (include the below in the `` tag). 556 | 557 | ``` 558 | 559 | 560 | production 561 | 562 | 563 | dbConnectionServer 564 | aws.amazon.example 565 | 566 | 567 | 568 | 569 | JVM-Args 570 | -Xmx1024m 571 | 572 | 573 | 574 | 575 | ``` 576 | 577 | A mode must have the `` tag, and you may define two things for each mode, namely, `` and `` (in exactly the same syntax as above). 578 | 579 | If the mode is activated at runtime (`-Dcapsule.mode=production`) then the properties listed in the mode will completely override the properties set in the main configuration. Thus, only the properties listed in the mode section will be available to the app. 580 | 581 | However, the mode's manifest entries will be appended to the existing set of entries defined in the main section (unless any match up, then the mode's entry will override). 582 | 583 | Of course, you can define multiple modes. 584 | 585 | ## FileSets 586 | 587 | If you'd like to copy over specific files from some local folder then you can use the 588 | assembly style `` in the `` tag. 589 | 590 | ``` 591 | 592 | 593 | config/ 594 | config/ 595 | 596 | myconfig.yml 597 | 598 | 599 | 600 | ``` 601 | 602 | So from above we copy over the myconfig.yml file that we have in our config folder and place it within the config directory in the capsule jar (the plugin will create this folder in the capsule jar). 603 | 604 | You specify a number of `` which must contain the `` (the location of the folder to copy), the `` (the destination directory within the capsule jar) and finally a set of `` to specify which files from the `` to copy over. 605 | 606 | The `` tag supports a single wildcard `*`. So for example `*.yml`, `myconfig*` or `my*.yml` can work. 607 | 608 | 609 | ## DependencySets 610 | 611 | If you'd like to copy over specific files from files embedded in some dependency then you can use the 612 | assembly style `` in the `` tag. 613 | 614 | ``` 615 | 616 | 617 | com.google.guava 618 | guava 619 | config/ 620 | 621 | META-INF/MANIFEST.MF 622 | 623 | 624 | 625 | ``` 626 | 627 | So from the above we pull the manifest file from within Google Guava's jar and place it within the config directory in the capsule jar (the plugin will create this folder in the capsule jar). 628 | 629 | You specify a number of `` which must contain the coords (``, ``, ``, ``) of a project dependency (the classifier and version are optional), the `` (the destination directory within the capsule jar) and finally a set of `` to specify which files from the dependency to copy over. 630 | 631 | The `` tag supports a single wildcard `*`. So for example `META-INF/*`, `*MANIFEST.MF` or `META-INF/*.MF` can work. 632 | 633 | 634 | You could also copy over the whole dependency directly if you leave out the ```includes``` tag: 635 | 636 | ``` 637 | 638 | 639 | com.google.guava 640 | guava 641 | config/ 642 | 643 | 644 | ``` 645 | 646 | You could also copy over the whole dependency in an **unpacked** form if you mark the ```true``` flag. 647 | 648 | ``` 649 | 650 | 651 | com.google.guava 652 | guava 653 | config/ 654 | true 655 | 656 | 657 | ``` 658 | 659 | 660 | ## Custom Capsule Version 661 | 662 | Ths plugin can support older or newer versions of capsule (at your own risk). You can specify a maven property for the capsule version (this will be the version of capsule to package within the build of the capsules). 663 | 664 | ``` 665 | 666 | 0.6.0 667 | 668 | ``` 669 | Otherwise, the default version of capsule will be used automatically. This is recommended. 670 | 671 | ## Caplets 672 | 673 | Capsule supports defining your own Capsule class by extending the `Capsule.class`. If you want to specify your custom Capsule class, add a manifest entry pointing to it: 674 | 675 | ``` 676 | 677 | hello.HelloWorld 678 | MyCapsule 679 | 680 | ``` 681 | 682 | If you have more than one, just add a space in between each one for e.g `MyCapsule MyCapsule2`. 683 | 684 | If you want to use a caplet that's not a local class (i.e from a dependency) then you must specify the full coordinates of it like so: 685 | 686 | `co.paralleluniverse:capsule-daemon:0.1.0` 687 | 688 | And you can mix local and non-local caplets too: 689 | 690 | `MyCapsule co.paralleluniverse:capsule-daemon:0.1.0` 691 | 692 | See more info on [caplets](http://www.capsule.io/caplets/). 693 | 694 | ## Maven Exec Plugin Integration 695 | 696 | The [maven exec plugin](http://www.mojohaus.org/exec-maven-plugin/) is a useful tool to run your jar all from within maven (using its classpath). 697 | 698 | ``` 699 | 700 | org.codehaus.mojo 701 | exec-maven-plugin 702 | ${maven.exec.plugin.version} 703 | 704 | hello.HelloWorld 705 | 706 | 707 | ``` 708 | 709 | You can then run your normal jar by: 710 | 711 | ``` 712 | mvn package exec:java 713 | ``` 714 | 715 | Notice that the exec plugin provides a configuration where you can specify the `` as well as other fields such as ``. The Capsule plugin provides the ability to pull this config and apply it to the built capsules, thus saving you from having to enter it twice (once at the exec plugin and second at the capsule plugin). 716 | 717 | In the capsule plugin you can set the `` tag to do this: 718 | 719 | ``` 720 | 721 | com.github.chrisdchristo 722 | capsule-maven-plugin 723 | ${capsule.maven.plugin.version} 724 | 725 | root 726 | 727 | 728 | ``` 729 | 730 | The value `root` will tell the capsule plugin to pull the config from the `` element at the root of the exec plugin. 731 | 732 | If you are using executions within the exec plugin like so: 733 | 734 | ``` 735 | 736 | org.codehaus.mojo 737 | exec-maven-plugin 738 | ${maven.exec.plugin.version} 739 | 740 | 741 | default-cli 742 | 743 | java 744 | 745 | 746 | hello.HelloWorld 747 | 748 | 749 | 750 | 751 | ``` 752 | 753 | Then you can specify the `` to the ID of the execution: 754 | 755 | ``` 756 | 757 | com.github.chrisdchristo 758 | capsule-maven-plugin 759 | ${capsule.maven.plugin.version} 760 | 761 | default-cli 762 | 763 | 764 | ``` 765 | 766 | ##### How the capsule plugin maps the config from the exec plugin 767 | 768 | The capsule plugin will map values from the exec plugin: 769 | 770 | ``` 771 | mainClass -> appClass 772 | systemProperties -> properties 773 | arguments -> JVM-Args (manifest entry) 774 | ``` 775 | 776 | So the `` element in the exec's `` will be the `` in the capsules's ``. 777 | 778 | ##### A complete solution 779 | 780 | So essentially you can setup as follows: 781 | 782 | ``` 783 | 784 | org.codehaus.mojo 785 | exec-maven-plugin 786 | ${maven.exec.plugin.version} 787 | 788 | hello.HelloWorld 789 | 790 | 791 | propertyName1 792 | propertyValue1 793 | 794 | 795 | 796 | 797 | 798 | com.github.chrisdchristo 799 | capsule-maven-plugin 800 | ${capsule.maven.plugin.version} 801 | 802 | root 803 | 804 | 805 | ``` 806 | 807 | ##### Overriding the exec plugin config 808 | 809 | Note that if you do specify the ``, `` or `JVM-Args` (in the ``) of the capsule plugin, then these will override the config of the exec plugin. 810 | 811 | ## Reference 812 | 813 | * ``: The class with the main method (with package declaration) of your app that the capsule should run. This can be optional too, if you are using the maven exec plugin and have specified a `execPluginConfig`. 814 | * ` (Optional)`: If executable (chmod +x) versions of the capsules should be built in the form of '.x' files (Applicable for Mac/Unix style systems). See [here](https://github.com/brianm/really-executable-jars-maven-plugin) and [here](http://skife.org/java/unix/2011/06/20/really_executable_jars.html) for more info. Defaults to false. 815 | * ` (Optional)`: This will create trampoline style executable capsules in the form of '.tx' files. See more info [here](https://github.com/chrisdchristo/capsule-maven-plugin#trampoline). 816 | * ` (Optional)`: Specifies the output directory. Defaults to the `${project.build.directory}`. 817 | * ` (Optional)`: Specifies the ID of an execution within the exec-maven-plugin. The configuration from this execution will then be used to configure the capsules. If you specify 'root' then the `` at root will be used instead of a particular execution. The exec's `` will map to Capsule's ``. The exec's `` will map to capsule's ``. If you specify this tag then the `` tag does not need to present. 818 | * ` (Optional)`: The system properties to provide the app with. 819 | * ` (Optional)`: Can be either ```empty```, ```thin``` or ```fat```. Tells the plugin to build a capsule based on of these predefined builds. If present, the plugin will ignore all of the `````` and ``````. 820 | * ` (Optional)`: Can either be ```true``` or ```false```, default is ```false```. This will append a manifest entry ```Repositories``` with values as defined by the project's ```pom.xml```. 821 | * ` (Optional)`: Specify whether the app itself should be embedded. Default is true. Also, this is ignored if `````` is present. 822 | * ` (Optional)`: Specify whether normal app dependencies should be embedded. Default is false. Also, this is ignored if `````` is present. 823 | * ` (Optional)`: Specify whether the plugin dependencies should be embedded. Default is false. Also, this is ignored if `````` is present. 824 | * ` (Optional)`: Specify whether transitive dependencies should also be embedded. Default is false. Also, this is ignored if `````` is present. 825 | * ` (Optional)`: Specify whether compile scope dependencies should be embedded. Default is false. Also, this is ignored if `````` is present. 826 | * ` (Optional)`: Specify whether runtime scope dependencies should be embedded. Default is false. Also, this is ignored if `````` is present. 827 | * ` (Optional)`: Specify whether provided scope dependencies should be embedded. Default is false. Also, this is ignored if `````` is present. 828 | * ` (Optional)`: Specify whether system scope dependencies should be embedded. Default is false. Also, this is ignored if `````` is present. 829 | * ` (Optional)`: Specify whether test scope dependencies should be embedded. Default is false. Also, this is ignored if `````` is present. 830 | * ` (Optional)`: Specify whether optional dependencies should also be embedded. The default is false. Also, this is ignored if `````` is present. 831 | * ` (Optional)`: Specifies whether the app should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 832 | * ` (Optional)`: Specifies whether the app dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 833 | * ` (Optional)`: Specifies whether the plugin dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 834 | * ` (Optional)`: Specifies whether the transitive dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 835 | * ` (Optional)`: Specifies whether the compile scoped dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 836 | * ` (Optional)`: Specifies whether the runtime scoped dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 837 | * ` (Optional)`: Specifies whether the system scoped dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 838 | * ` (Optional)`: Specifies whether the system scoped dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 839 | * ` (Optional)`: Specifies whether the test scoped dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 840 | * ` (Optional)`: Specifies whether the optional dependencies should be resolved at launch. The default is false. Also, this is ignored if `````` is present. 841 | * ` (Optional)`: The set of additional manifest entries, for e.g `JVM-Args`. See [capsule](http://www.capsule.io/reference/) for an exhaustive list. Note you do **not** need `Main-Class`, `Application-Class`, `Application`, `Dependencies` and `System-Properties` as these are generated automatically. 842 | * ` (Optional)`: Define a set of `` with its own set of `` and `` entries to categorise the capsule into different modes. The mode can be set at runtime. [See more here](https://github.com/chrisdchristo/capsule-maven-plugin#modes). 843 | * ` (Optional)`: Define a set of `` to copy over files into the capsule. [See more here](https://github.com/chrisdchristo/capsule-maven-plugin#filesets-and-dependencysets). 844 | * ` (Optional)`: Define a set of `` to copy over files contained within remote dependencies into the capsule. [See more here](https://github.com/chrisdchristo/capsule-maven-plugin#filesets-and-dependencysets). 845 | * ` (Optional)`: Define a list of caplets (custom Capsule classes). [See more here](https://github.com/chrisdchristo/capsule-maven-plugin#caplets). 846 | * ` (Optional)`: The custom text for the file name part of the name of the output jar. By default this is ```````. 847 | * ` (Optional)`: The custom text for the descriptor part of the name of the output jar. This combined with the `````` tag creates the output name of the jar. 848 | 849 | ``` 850 | 851 | 852 | com.github.chrisdchristo 853 | capsule-maven-plugin 854 | ${capsule.maven.plugin.version} 855 | 856 | 857 | hello.HelloWorld 858 | 859 | target 860 | MyCapsule MyCapsule2 861 | 862 | fat 863 | true 864 | true 865 | true 866 | 867 | true 868 | false 869 | false 870 | false 871 | false 872 | false 873 | false 874 | false 875 | false 876 | false 877 | 878 | false 879 | false 880 | false 881 | false 882 | false 883 | false 884 | false 885 | false 886 | false 887 | false 888 | 889 | root 890 | my-amazing-app 891 | -cap 892 | 893 | 894 | 895 | propertyName1 896 | propertyValue1 897 | 898 | 899 | 900 | 901 | 902 | JVM-Args 903 | -Xmx512m 904 | 905 | 906 | Min-Java-Version 907 | 1.8.0 908 | 909 | 910 | 911 | 912 | 913 | production 914 | 915 | 916 | dbConnectionServer 917 | aws.amazon.example 918 | 919 | 920 | 921 | 922 | JVM-Args 923 | -Xmx1024m 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | config/ 932 | config/ 933 | 934 | myconfig.yml 935 | 936 | 937 | 938 | 939 | 940 | 941 | com.google.guava 942 | guava 943 | optional 944 | config/ 945 | 946 | 947 | META-INF/MANIFEST.MF 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | build 957 | 958 | 959 | 960 | 961 | ``` 962 | 963 | ## License 964 | 965 | This project is released under the [MIT license](http://opensource.org/licenses/MIT). 966 | --------------------------------------------------------------------------------