diff --git a/spring-boot-tools/spring-boot-gradle-plugin/pom.xml b/spring-boot-tools/spring-boot-gradle-plugin/pom.xml index 29f58c0ec3b..6fbd3f3b042 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/pom.xml +++ b/spring-boot-tools/spring-boot-gradle-plugin/pom.xml @@ -54,30 +54,6 @@ provided - - src/main/groovy - - - org.apache.maven.plugins - maven-compiler-plugin - - groovy-eclipse-compiler - - - - org.codehaus.groovy - groovy-eclipse-compiler - 2.8.0-01 - - - org.codehaus.groovy - groovy-eclipse-batch - 2.1.8-01 - - - - - gradle diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPlugin.groovy b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPlugin.groovy deleted file mode 100644 index 8c95630de66..00000000000 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPlugin.groovy +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2012-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.gradle - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.plugins.ApplicationPlugin -import org.gradle.api.plugins.BasePlugin -import org.gradle.api.plugins.JavaPlugin -import org.springframework.boot.gradle.agent.AgentPluginFeatures -import org.springframework.boot.gradle.exclude.ExcludePluginFeatures -import org.springframework.boot.gradle.repackage.RepackagePluginFeatures -import org.springframework.boot.gradle.resolve.ResolvePluginFeatures -import org.springframework.boot.gradle.run.RunPluginFeatures - - -/** - * Gradle 'Spring Boot' {@link Plugin}. - * - * @author Phillip Webb - * @author Dave Syer - */ -class SpringBootPlugin implements Plugin { - - @Override - void apply(Project project) { - project.getPlugins().apply(BasePlugin) - - project.getExtensions().create("springBoot", SpringBootPluginExtension) - project.getConfigurations().create(VersionManagedDependencies.CONFIGURATION); - - project.getPlugins().apply(JavaPlugin) - project.getPlugins().apply(ApplicationPlugin) - new AgentPluginFeatures().apply(project) - new RepackagePluginFeatures().apply(project) - new RunPluginFeatures().apply(project) - new ResolvePluginFeatures().apply(project) - new ExcludePluginFeatures().apply(project) - - useUtf8Encoding(project) - } - - private useUtf8Encoding(Project project) { - project.tasks.withType(org.gradle.api.tasks.compile.JavaCompile).all { - it.doFirst { - if(!it.options.encoding) { - it.options.encoding = 'UTF-8' - } - } - } - } -} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy deleted file mode 100644 index 86abbc3a745..00000000000 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2012-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.gradle - -import org.springframework.boot.loader.tools.Layout -import org.springframework.boot.loader.tools.Layouts - - -/** - * Gradle DSL Extension for 'Spring Boot'. Most of the time Spring Boot can guess the - * settings in this extension, but occasionally you might need to explicitly set one - * or two of them. E.g. - * - *
- *     apply plugin: "spring-boot"
- *     springBoot {
- *         mainClass = 'org.demo.Application'
- *         layout = 'ZIP'
- *     }
- * 
- * - * @author Phillip Webb - * @author Dave Syer - */ -public class SpringBootPluginExtension { - - static enum LayoutType { - - JAR(new Layouts.Jar()), - - WAR(new Layouts.War()), - - ZIP(new Layouts.Expanded()), - - DIR(new Layouts.Expanded()), - - MODULE(new Layouts.Module()), - - NONE(new Layouts.None()); - - Layout layout; - - private LayoutType(Layout layout) { - this.layout = layout; - } - } - - /** - * The main class that should be run. Instead of setting this explicitly you can use the - * 'mainClassName' of the project or the 'main' of the 'run' task. If not specified the - * value from the MANIFEST will be used, or if no manifest entry is the archive will be - * searched for a suitable class. - */ - String mainClass - - /** - * The classifier (file name part before the extension). Instead of setting this explicitly - * you can use the 'classifier' property of the 'bootRepackage' task. If not specified the archive - * will be replaced instead of renamed. - */ - String classifier - - /** - * The name of the ivy configuration name to treat as 'provided' (when packaging - * those dependencies in a separate path). If not specified 'providedRuntime' will - * be used. - */ - String providedConfiguration - - /** - * The name of the custom configuration to use. - */ - String customConfiguration - - /** - * If the original source archive should be backed-up before being repackaged. - */ - boolean backupSource = true; - - /** - * The layout of the archive if it can't be derived from the file extension. - * Valid values are JAR, WAR, ZIP, DIR (for exploded zip file). ZIP and DIR - * are actually synonymous, and should be used if there is no MANIFEST.MF - * available, or if you want the MANIFEST.MF 'Main-Class' to be - * PropertiesLauncher. Gradle will coerce literal String values to the - * correct type. - */ - LayoutType layout; - - /** - * Convenience method for use in a custom task. - * @return the Layout to use or null if not explicitly set - */ - Layout convertLayout() { - (layout == null ? null : layout.layout) - } - - /** - * Libraries that must be unpacked from fat jars in order to run. Use Strings in the - * form {@literal groupId:artifactId}. - */ - Set requiresUnpack; - - /** - * Location of an agent jar to attach to the VM when running the application with runJar task. - */ - File agent; - - /** - * Flag to indicate that the agent requires -noverify (and the plugin will refuse to start if it is not set) - */ - Boolean noverify; - - /** - * If exclude rules should be applied to dependencies based on the spring-dependencies-bom - */ - boolean applyExcludeRules = true; - -} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/PluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/PluginFeatures.java similarity index 100% rename from spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/PluginFeatures.java rename to spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/PluginFeatures.java diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPlugin.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPlugin.java new file mode 100644 index 00000000000..2064b438222 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPlugin.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle; + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.ApplicationPlugin; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.compile.JavaCompile; +import org.springframework.boot.gradle.agent.AgentPluginFeatures; +import org.springframework.boot.gradle.exclude.ExcludePluginFeatures; +import org.springframework.boot.gradle.repackage.RepackagePluginFeatures; +import org.springframework.boot.gradle.resolve.ResolvePluginFeatures; +import org.springframework.boot.gradle.run.RunPluginFeatures; + +/** + * Gradle 'Spring Boot' {@link Plugin}. + * + * @author Phillip Webb + * @author Dave Syer + */ +class SpringBootPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPlugins().apply(BasePlugin.class); + project.getExtensions().create("springBoot", SpringBootPluginExtension.class); + project.getConfigurations().create(VersionManagedDependencies.CONFIGURATION); + project.getPlugins().apply(JavaPlugin.class); + project.getPlugins().apply(ApplicationPlugin.class); + new AgentPluginFeatures().apply(project); + new RepackagePluginFeatures().apply(project); + new RunPluginFeatures().apply(project); + new ResolvePluginFeatures().apply(project); + new ExcludePluginFeatures().apply(project); + project.getTasks().withType(JavaCompile.class).all(new SetUtf8EncodingAction()); + } + + private static class SetUtf8EncodingAction implements Action { + + @Override + public void execute(final JavaCompile compile) { + compile.doFirst(new Action() { + + @Override + @SuppressWarnings("deprecation") + public void execute(Task t) { + if (compile.getOptions().getEncoding() == null) { + compile.getOptions().setEncoding("UTF-8"); + } + } + + }); + } + + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java new file mode 100644 index 00000000000..ec80ec4a4db --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java @@ -0,0 +1,220 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle; + +import java.io.File; +import java.util.Set; + +import org.springframework.boot.loader.tools.Layout; +import org.springframework.boot.loader.tools.Layouts; + +/** + * Gradle DSL Extension for 'Spring Boot'. Most of the time Spring Boot can guess the + * settings in this extension, but occasionally you might need to explicitly set one or + * two of them. E.g. + * + *
+ *     apply plugin: "spring-boot"
+ *     springBoot {
+ *         mainClass = 'org.demo.Application'
+ *         layout = 'ZIP'
+ *     }
+ * 
+ * + * @author Phillip Webb + * @author Dave Syer + */ +public class SpringBootPluginExtension { + + /** + * The main class that should be run. Instead of setting this explicitly you can use + * the 'mainClassName' of the project or the 'main' of the 'run' task. If not + * specified the value from the MANIFEST will be used, or if no manifest entry is the + * archive will be searched for a suitable class. + */ + private String mainClass; + + /** + * The classifier (file name part before the extension). Instead of setting this + * explicitly you can use the 'classifier' property of the 'bootRepackage' task. If + * not specified the archive will be replaced instead of renamed. + */ + private String classifier; + + /** + * The name of the ivy configuration name to treat as 'provided' (when packaging those + * dependencies in a separate path). If not specified 'providedRuntime' will be used. + */ + private String providedConfiguration; + + /** + * The name of the custom configuration to use. + */ + private String customConfiguration; + + /** + * If the original source archive should be backed-up before being repackaged. + */ + private boolean backupSource = true; + + /** + * The layout of the archive if it can't be derived from the file extension. Valid + * values are JAR, WAR, ZIP, DIR (for exploded zip file). ZIP and DIR are actually + * synonymous, and should be used if there is no MANIFEST.MF available, or if you want + * the MANIFEST.MF 'Main-Class' to be PropertiesLauncher. Gradle will coerce literal + * String values to the correct type. + */ + private LayoutType layout; + + /** + * Libraries that must be unpacked from fat jars in order to run. Use Strings in the + * form {@literal groupId:artifactId}. + */ + private Set requiresUnpack; + + /** + * Location of an agent jar to attach to the VM when running the application with + * runJar task. + */ + private File agent; + + /** + * Flag to indicate that the agent requires -noverify (and the plugin will refuse to + * start if it is not set) + */ + private Boolean noverify; + + /** + * If exclude rules should be applied to dependencies based on the + * spring-dependencies-bom + */ + private boolean applyExcludeRules = true; + + /** + * Convenience method for use in a custom task. + * @return the Layout to use or null if not explicitly set + */ + public Layout convertLayout() { + return (this.layout == null ? null : this.layout.layout); + } + + public String getMainClass() { + return this.mainClass; + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public String getClassifier() { + return this.classifier; + } + + public void setClassifier(String classifier) { + this.classifier = classifier; + } + + public String getProvidedConfiguration() { + return this.providedConfiguration; + } + + public void setProvidedConfiguration(String providedConfiguration) { + this.providedConfiguration = providedConfiguration; + } + + public String getCustomConfiguration() { + return this.customConfiguration; + } + + public void setCustomConfiguration(String customConfiguration) { + this.customConfiguration = customConfiguration; + } + + public boolean isBackupSource() { + return this.backupSource; + } + + public void setBackupSource(boolean backupSource) { + this.backupSource = backupSource; + } + + public LayoutType getLayout() { + return this.layout; + } + + public void setLayout(LayoutType layout) { + this.layout = layout; + } + + public Set getRequiresUnpack() { + return this.requiresUnpack; + } + + public void setRequiresUnpack(Set requiresUnpack) { + this.requiresUnpack = requiresUnpack; + } + + public File getAgent() { + return this.agent; + } + + public void setAgent(File agent) { + this.agent = agent; + } + + public Boolean getNoverify() { + return this.noverify; + } + + public void setNoverify(Boolean noverify) { + this.noverify = noverify; + } + + public boolean isApplyExcludeRules() { + return this.applyExcludeRules; + } + + public void setApplyExcludeRules(boolean applyExcludeRules) { + this.applyExcludeRules = applyExcludeRules; + } + + /** + * Layout types. + */ + static enum LayoutType { + + JAR(new Layouts.Jar()), + + WAR(new Layouts.War()), + + ZIP(new Layouts.Expanded()), + + DIR(new Layouts.Expanded()), + + MODULE(new Layouts.Module()), + + NONE(new Layouts.None()); + + Layout layout; + + LayoutType(Layout layout) { + this.layout = layout; + } + + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/VersionManagedDependencies.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/VersionManagedDependencies.java similarity index 91% rename from spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/VersionManagedDependencies.java rename to spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/VersionManagedDependencies.java index 51dd654cc70..b42eada7207 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/VersionManagedDependencies.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/VersionManagedDependencies.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,8 +47,8 @@ public class VersionManagedDependencies { private ManagedDependencies managedDependencies; public VersionManagedDependencies(Project project) { - this.versionManagementConfiguration = project.getConfigurations().getByName( - CONFIGURATION); + this.versionManagementConfiguration = project.getConfigurations() + .getByName(CONFIGURATION); } public ManagedDependencies getManagedDependencies() { @@ -60,15 +60,15 @@ public class VersionManagedDependencies { } private Collection getVersionManagedDependencies() { - if (versionManagedDependencies == null) { - Set files = versionManagementConfiguration.resolve(); + if (this.versionManagedDependencies == null) { + Set files = this.versionManagementConfiguration.resolve(); List dependencies = new ArrayList(files.size()); for (File file : files) { dependencies.add(getPropertiesFileManagedDependencies(file)); } this.versionManagedDependencies = dependencies; } - return versionManagedDependencies; + return this.versionManagedDependencies; } private Dependencies getPropertiesFileManagedDependencies(File file) { diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/agent/AgentPluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/agent/AgentPluginFeatures.java similarity index 100% rename from spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/agent/AgentPluginFeatures.java rename to spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/agent/AgentPluginFeatures.java diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/agent/AgentTasksEnhancer.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/agent/AgentTasksEnhancer.java new file mode 100644 index 00000000000..1ca136ac105 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/agent/AgentTasksEnhancer.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.agent; + +import java.io.File; +import java.net.URISyntaxException; +import java.security.CodeSource; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.tasks.JavaExec; +import org.springframework.boot.gradle.SpringBootPluginExtension; + +/** + * Add a java agent to the "run" task if configured. You can add an agent in 3 ways (4 if + * you want to use native gradle features as well): + * + *
    + *
  1. Use "-Prun.agent=[path-to-jar]" on the gradle command line
  2. + *
  3. Add an "agent" property (jar file) to the "springBoot" extension in build.gradle + *
  4. + *
  5. As a special case springloaded is detected as a build script dependency
  6. + *
+ * + * @author Dave Syer + * @author Phillip Webb + */ +public class AgentTasksEnhancer implements Action { + + private static final String SPRING_LOADED_AGENT_CLASSNAME = "org.springsource.loaded.agent.SpringLoadedAgent"; + + private File agent; + + private Boolean noverify; + + @Override + public void execute(Project project) { + setup(project); + if (this.agent != null) { + for (Task task : project.getTasks()) { + addAgent(project, task); + } + } + } + + private void setup(Project project) { + project.getLogger().info("Configuring agent"); + SpringBootPluginExtension extension = project.getExtensions() + .getByType(SpringBootPluginExtension.class); + this.noverify = extension.getNoverify(); + this.agent = getAgent(project, extension); + if (this.agent == null) { + this.agent = getSpringLoadedAgent(); + if (this.noverify == null) { + this.noverify = true; + } + } + project.getLogger().debug("Agent: " + this.agent); + } + + private File getAgent(Project project, SpringBootPluginExtension extension) { + if (project.hasProperty("run.agent")) { + return project.file(project.property("run.agent")); + } + return extension.getAgent(); + } + + private File getSpringLoadedAgent() { + try { + Class loaded = Class.forName(SPRING_LOADED_AGENT_CLASSNAME); + if (loaded != null) { + CodeSource source = loaded.getProtectionDomain().getCodeSource(); + if (source != null) { + try { + return new File(source.getLocation().toURI()); + } + catch (URISyntaxException ex) { + return new File(source.getLocation().getPath()); + } + } + } + } + catch (ClassNotFoundException ex) { + // ignore; + } + return null; + } + + private void addAgent(Project project, Task task) { + if (task instanceof JavaExec) { + addAgent(project, (JavaExec) task); + } + } + + private void addAgent(Project project, JavaExec exec) { + project.getLogger().debug("Attaching to: " + exec); + if (this.agent != null) { + project.getLogger().info("Attaching agent: " + this.agent); + exec.jvmArgs("-javaagent:" + this.agent.getAbsolutePath()); + if (this.noverify != null && this.noverify) { + exec.jvmArgs("-noverify"); + } + Iterable defaultJvmArgs = exec.getConventionMapping() + .getConventionValue(null, "jvmArgs", false); + if (defaultJvmArgs != null) { + exec.jvmArgs(defaultJvmArgs); + } + } + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/exclude/ApplyExcludeRules.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/exclude/ApplyExcludeRules.java new file mode 100644 index 00000000000..f5bd0d1d8ce --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/exclude/ApplyExcludeRules.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.exclude; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ResolvableDependencies; +import org.gradle.api.internal.artifacts.DefaultExcludeRule; +import org.gradle.api.logging.Logger; +import org.springframework.boot.dependency.tools.Dependency.Exclusion; +import org.springframework.boot.dependency.tools.ManagedDependencies; +import org.springframework.boot.gradle.VersionManagedDependencies; + +/** + * {@link Action} to apply exclude rules. + * + * @author Phillip Webb + */ +public class ApplyExcludeRules implements Action { + + private final Logger logger; + + private final VersionManagedDependencies versionManagedDependencies; + + public ApplyExcludeRules(Project project) { + this.logger = project.getLogger(); + this.versionManagedDependencies = new VersionManagedDependencies(project); + } + + @Override + public void execute(Configuration configuration) { + if (!VersionManagedDependencies.CONFIGURATION.equals(configuration.getName())) { + configuration.getIncoming() + .beforeResolve(new Action() { + @Override + public void execute( + ResolvableDependencies resolvableDependencies) { + resolvableDependencies.getDependencies() + .all(new Action() { + @Override + public void execute(Dependency dependency) { + applyExcludeRules(dependency); + } + }); + } + }); + } + } + + private void applyExcludeRules(Dependency dependency) { + if (dependency instanceof ModuleDependency) { + applyExcludeRules((ModuleDependency) dependency); + } + } + + private void applyExcludeRules(ModuleDependency dependency) { + ManagedDependencies managedDependencies = this.versionManagedDependencies + .getManagedDependencies(); + // flat directory repositories do not have groups + if (dependency.getGroup() != null) { + org.springframework.boot.dependency.tools.Dependency managedDependency = managedDependencies + .find(dependency.getGroup(), dependency.getName()); + if (managedDependency != null) { + for (Exclusion exclusion : managedDependency.getExclusions()) { + addExcludeRule(dependency, exclusion); + } + addImplicitExcludeRules(dependency); + return; + } + } + this.logger.debug( + "No exclusions rules applied for non-managed dependency " + dependency); + } + + private void addExcludeRule(ModuleDependency dependency, Exclusion exclusion) { + this.logger + .info("Adding managed exclusion rule " + exclusion + " to " + dependency); + DefaultExcludeRule rule = new DefaultExcludeRule(exclusion.getGroupId(), + exclusion.getArtifactId()); + dependency.getExcludeRules().add(rule); + } + + private void addImplicitExcludeRules(ModuleDependency dependency) { + if (isStarter(dependency)) { + this.logger.info( + "Adding implicit managed exclusion rules to starter " + dependency); + dependency.getExcludeRules() + .add(new DefaultExcludeRule("commons-logging", "commons-logging")); + dependency.getExcludeRules().add( + new DefaultExcludeRule("commons-logging", "commons-logging-api")); + } + } + + private boolean isStarter(ModuleDependency dependency) { + return (dependency.getGroup() != null + && dependency.getGroup().equals("org.springframework.boot") + && dependency.getName().startsWith("spring-boot-starter")); + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/exclude/ExcludePluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/exclude/ExcludePluginFeatures.java new file mode 100644 index 00000000000..04253842d26 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/exclude/ExcludePluginFeatures.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.exclude; + +import org.gradle.api.Project; +import org.springframework.boot.gradle.PluginFeatures; +import org.springframework.boot.gradle.SpringBootPluginExtension; + +/** + * {@link PluginFeatures} to apply exclusion rules. + * + * @author Phillip Webb + */ +public class ExcludePluginFeatures implements PluginFeatures { + + @Override + public void apply(Project project) { + SpringBootPluginExtension extension = project.getExtensions() + .getByType(SpringBootPluginExtension.class); + if (extension.isApplyExcludeRules()) { + project.getConfigurations().all(new ApplyExcludeRules(project)); + } + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/ProjectLibraries.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/ProjectLibraries.java new file mode 100644 index 00000000000..331ca33f5b6 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/ProjectLibraries.java @@ -0,0 +1,243 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.repackage; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.FileCollectionDependency; +import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.springframework.boot.gradle.SpringBootPluginExtension; +import org.springframework.boot.loader.tools.Libraries; +import org.springframework.boot.loader.tools.Library; +import org.springframework.boot.loader.tools.LibraryCallback; +import org.springframework.boot.loader.tools.LibraryScope; + +/** + * Expose Gradle {@link Configuration}s as {@link Libraries}. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +class ProjectLibraries implements Libraries { + + private final Project project; + + private final SpringBootPluginExtension extension; + + private String providedConfigurationName = "providedRuntime"; + + private String customConfigurationName = null; + + /** + * Create a new {@link ProjectLibraries} instance of the specified {@link Project} . + * @param project the gradle project + * @param extension the extension + */ + public ProjectLibraries(Project project, SpringBootPluginExtension extension) { + this.project = project; + this.extension = extension; + } + + /** + * Set the name of the provided configuration. Defaults to 'providedRuntime'. + * @param providedConfigurationName the providedConfigurationName to set + */ + public void setProvidedConfigurationName(String providedConfigurationName) { + this.providedConfigurationName = providedConfigurationName; + } + + public void setCustomConfigurationName(String customConfigurationName) { + this.customConfigurationName = customConfigurationName; + } + + @Override + public void doWithLibraries(LibraryCallback callback) throws IOException { + Set custom = getLibraries(this.customConfigurationName, + LibraryScope.CUSTOM); + if (custom != null) { + libraries(custom, callback); + } + else { + Set compile = getLibraries("compile", LibraryScope.COMPILE); + Set runtime = getLibraries("runtime", LibraryScope.RUNTIME); + runtime = minus(runtime, compile); + Set provided = getLibraries(this.providedConfigurationName, + LibraryScope.PROVIDED); + if (provided != null) { + compile = minus(compile, provided); + runtime = minus(runtime, provided); + } + libraries(compile, callback); + libraries(runtime, callback); + libraries(provided, callback); + } + } + + private Set getLibraries(String configurationName, + LibraryScope scope) { + Configuration configuration = (configurationName == null ? null + : this.project.getConfigurations().findByName(configurationName)); + if (configuration == null) { + return null; + } + Set libraries = new LinkedHashSet(); + for (ResolvedArtifact artifact : configuration.getResolvedConfiguration() + .getResolvedArtifacts()) { + libraries.add(new ResolvedArtifactLibrary(artifact, scope)); + } + libraries.addAll(getLibrariesForFileDependencies(configuration, scope)); + return libraries; + } + + private Set getLibrariesForFileDependencies( + Configuration configuration, LibraryScope scope) { + Set libraries = new LinkedHashSet(); + for (Dependency dependency : configuration.getIncoming().getDependencies()) { + if (dependency instanceof FileCollectionDependency) { + FileCollectionDependency fileDependency = (FileCollectionDependency) dependency; + for (File file : fileDependency.resolve()) { + libraries.add( + new GradleLibrary(fileDependency.getGroup(), file, scope)); + } + } + else if (dependency instanceof ProjectDependency) { + ProjectDependency projectDependency = (ProjectDependency) dependency; + libraries.addAll(getLibrariesForFileDependencies( + projectDependency.getProjectConfiguration(), scope)); + } + } + return libraries; + } + + private Set minus(Set source, + Set toRemove) { + if (source == null || toRemove == null) { + return source; + } + Set filesToRemove = new HashSet(); + for (GradleLibrary library : toRemove) { + filesToRemove.add(library.getFile()); + } + Set result = new LinkedHashSet(); + for (GradleLibrary library : source) { + if (!filesToRemove.contains(library.getFile())) { + result.add(library); + } + } + return result; + } + + private void libraries(Set libraries, LibraryCallback callback) + throws IOException { + if (libraries != null) { + Set duplicates = getDuplicates(libraries); + for (GradleLibrary library : libraries) { + library.setIncludeGroupName(duplicates.contains(library.getName())); + callback.library(library); + } + } + } + + private Set getDuplicates(Set libraries) { + Set duplicates = new HashSet(); + Set seen = new HashSet(); + for (GradleLibrary library : libraries) { + if (library.getFile() != null && !seen.add(library.getFile().getName())) { + duplicates.add(library.getFile().getName()); + } + } + return duplicates; + } + + private class GradleLibrary extends Library { + + private final String group; + + private boolean includeGroupName; + + public GradleLibrary(String group, File file, LibraryScope scope) { + super(file, scope); + this.group = group; + } + + public void setIncludeGroupName(boolean includeGroupName) { + this.includeGroupName = includeGroupName; + } + + @Override + public String getName() { + String name = super.getName(); + if (this.includeGroupName && this.group != null) { + name = this.group + "-" + name; + } + return name; + } + + @Override + public int hashCode() { + return getFile().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof GradleLibrary) { + return getFile().equals(((GradleLibrary) obj).getFile()); + } + return false; + } + + @Override + public String toString() { + return getFile().getAbsolutePath(); + } + } + + /** + * Adapts a {@link ResolvedArtifact} to a {@link Library}. + */ + private class ResolvedArtifactLibrary extends GradleLibrary { + + private final ResolvedArtifact artifact; + + public ResolvedArtifactLibrary(ResolvedArtifact artifact, LibraryScope scope) { + super(artifact.getModuleVersion().getId().getGroup(), artifact.getFile(), + scope); + this.artifact = artifact; + } + + @Override + public boolean isUnpackRequired() { + if (ProjectLibraries.this.extension.getRequiresUnpack() != null) { + ModuleVersionIdentifier id = this.artifact.getModuleVersion().getId(); + return ProjectLibraries.this.extension.getRequiresUnpack() + .contains(id.getGroup() + ":" + id.getName()); + } + return false; + } + + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackagePluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackagePluginFeatures.java new file mode 100644 index 00000000000..f6e00efc9eb --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackagePluginFeatures.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.repackage; + +import java.io.File; +import java.io.IOException; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.logging.Logger; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.TaskDependency; +import org.gradle.api.tasks.bundling.Jar; +import org.springframework.boot.gradle.PluginFeatures; +import org.springframework.boot.gradle.SpringBootPluginExtension; +import org.springframework.boot.loader.tools.Library; +import org.springframework.boot.loader.tools.LibraryCallback; +import org.springframework.util.StringUtils; + +/** + * {@link PluginFeatures} to add repackage support. + * + * @author Phillip Webb + * @author Dave Syer + * @author Andy Wilkinson + */ +public class RepackagePluginFeatures implements PluginFeatures { + + public static final String REPACKAGE_TASK_NAME = "bootRepackage"; + + @Override + public void apply(Project project) { + addRepackageTask(project); + registerRepackageTaskProperty(project); + } + + private void addRepackageTask(Project project) { + RepackageTask task = project.getTasks().create(REPACKAGE_TASK_NAME, + RepackageTask.class); + task.setDescription("Repackage existing JAR and WAR " + + "archives so that they can be executed from the command " + + "line using 'java -jar'"); + task.setGroup(BasePlugin.BUILD_GROUP); + Configuration runtimeConfiguration = project.getConfigurations() + .getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME); + TaskDependency runtimeProjectDependencyJarTasks = runtimeConfiguration + .getTaskDependencyFromProjectDependency(true, JavaPlugin.JAR_TASK_NAME); + task.dependsOn( + project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION) + .getAllArtifacts().getBuildDependencies(), + runtimeProjectDependencyJarTasks); + registerOutput(project, task); + ensureTaskRunsOnAssembly(project, task); + } + + private void registerOutput(Project project, final RepackageTask task) { + project.afterEvaluate(new Action() { + @Override + public void execute(Project project) { + project.getTasks().withType(Jar.class, + new RegisterInputsOutputsAction(task)); + Object withJar = task.getWithJarTask(); + if (withJar != null) { + task.dependsOn(withJar); + } + } + }); + } + + private void ensureTaskRunsOnAssembly(Project project, Task task) { + project.getTasks().getByName(BasePlugin.ASSEMBLE_TASK_NAME).dependsOn(task); + } + + /** + * Register BootRepackage so that we can use task {@code foo(type: BootRepackage)} . + */ + private void registerRepackageTaskProperty(Project project) { + project.getExtensions().getExtraProperties().set("BootRepackage", + RepackageTask.class); + } + + /** + * Register task input/outputs when classifiers are used + */ + private static class RegisterInputsOutputsAction implements Action { + + private final RepackageTask task; + + private final Project project; + + public RegisterInputsOutputsAction(RepackageTask task) { + this.task = task; + this.project = task.getProject(); + } + + @Override + public void execute(Jar jarTask) { + if ("".equals(jarTask.getClassifier())) { + String classifier = this.task.getClassifier(); + if (classifier == null) { + SpringBootPluginExtension extension = this.project.getExtensions() + .getByType(SpringBootPluginExtension.class); + classifier = extension.getClassifier(); + this.task.setClassifier(classifier); + } + if (classifier != null) { + setupInputOutputs(jarTask, classifier); + } + } + } + + private void setupInputOutputs(Jar jarTask, String classifier) { + Logger logger = this.project.getLogger(); + logger.debug("Using classifier: " + classifier + " for task " + + this.task.getName()); + File inputFile = jarTask.getArchivePath(); + String outputName = inputFile.getName(); + outputName = StringUtils.stripFilenameExtension(outputName) + "-" + classifier + + "." + StringUtils.getFilenameExtension(outputName); + File outputFile = new File(inputFile.getParentFile(), outputName); + this.task.getInputs().file(jarTask); + addLibraryDependencies(this.task); + this.task.getOutputs().file(outputFile); + this.task.setOutputFile(outputFile); + } + + private void addLibraryDependencies(final RepackageTask task) { + try { + task.getLibraries().doWithLibraries(new LibraryCallback() { + @Override + public void library(Library library) throws IOException { + task.getInputs().file(library.getFile()); + } + }); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java new file mode 100644 index 00000000000..5e71239fbf9 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java @@ -0,0 +1,236 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.repackage; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.gradle.api.Action; +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.bundling.Jar; +import org.springframework.boot.gradle.SpringBootPluginExtension; +import org.springframework.boot.loader.tools.Repackager; +import org.springframework.util.FileCopyUtils; + +/** + * Repackage task. + * + * @author Phillip Webb + * @author Janne Valkealahti + * @author Andy Wilkinson + */ +public class RepackageTask extends DefaultTask { + + private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + + private String customConfiguration; + + private Object withJarTask; + + private String mainClass; + + private String classifier; + + private File outputFile; + + public void setCustomConfiguration(String customConfiguration) { + this.customConfiguration = customConfiguration; + } + + public Object getWithJarTask() { + return this.withJarTask; + } + + public void setWithJarTask(Object withJarTask) { + this.withJarTask = withJarTask; + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public String getMainClass() { + return this.mainClass; + } + + public String getClassifier() { + return this.classifier; + } + + public void setClassifier(String classifier) { + this.classifier = classifier; + } + + @TaskAction + public void repackage() { + Project project = getProject(); + SpringBootPluginExtension extension = project.getExtensions() + .getByType(SpringBootPluginExtension.class); + ProjectLibraries libraries = getLibraries(); + project.getTasks().withType(Jar.class, new RepackageAction(extension, libraries)); + } + + public ProjectLibraries getLibraries() { + Project project = getProject(); + SpringBootPluginExtension extension = project.getExtensions() + .getByType(SpringBootPluginExtension.class); + ProjectLibraries libraries = new ProjectLibraries(project, extension); + if (extension.getProvidedConfiguration() != null) { + libraries.setProvidedConfigurationName(extension.getProvidedConfiguration()); + } + if (this.customConfiguration != null) { + libraries.setCustomConfigurationName(this.customConfiguration); + } + else if (extension.getCustomConfiguration() != null) { + libraries.setCustomConfigurationName(extension.getCustomConfiguration()); + } + return libraries; + } + + /** + * Action to repackage JARs. + */ + private class RepackageAction implements Action { + + private final SpringBootPluginExtension extension; + + private final ProjectLibraries libraries; + + public RepackageAction(SpringBootPluginExtension extension, + ProjectLibraries libraries) { + this.extension = extension; + this.libraries = libraries; + } + + @Override + public void execute(Jar jarTask) { + if (!RepackageTask.this.isEnabled()) { + getLogger().info("Repackage disabled"); + return; + } + Object withJarTask = RepackageTask.this.withJarTask; + if (!isTaskMatch(jarTask, withJarTask)) { + getLogger().info( + "Jar task not repackaged (didn't match withJarTask): " + jarTask); + return; + } + File file = jarTask.getArchivePath(); + if (file.exists()) { + repackage(file); + } + } + + private boolean isTaskMatch(Jar task, Object withJarTask) { + if (withJarTask == null) { + if ("".equals(task.getClassifier())) { + Set tasksWithCustomRepackaging = new HashSet(); + for (RepackageTask repackageTask : RepackageTask.this.getProject() + .getTasks().withType(RepackageTask.class)) { + if (repackageTask.getWithJarTask() != null) { + tasksWithCustomRepackaging + .add(repackageTask.getWithJarTask()); + } + } + return !tasksWithCustomRepackaging.contains(task); + } + return false; + } + return task.equals(withJarTask) || task.getName().equals(withJarTask); + } + + private void repackage(File file) { + File outputFile = RepackageTask.this.outputFile; + if (outputFile != null && !file.equals(outputFile)) { + copy(file, outputFile); + file = outputFile; + } + Repackager repackager = new LoggingRepackager(file); + setMainClass(repackager); + if (this.extension.convertLayout() != null) { + repackager.setLayout(this.extension.convertLayout()); + } + repackager.setBackupSource(this.extension.isBackupSource()); + try { + repackager.repackage(file, this.libraries); + } + catch (IOException ex) { + throw new IllegalStateException(ex.getMessage(), ex); + } + } + + private void copy(File source, File dest) { + try { + FileCopyUtils.copy(source, dest); + } + catch (IOException ex) { + throw new IllegalStateException(ex.getMessage(), ex); + } + } + + private void setMainClass(Repackager repackager) { + String mainClass = (String) getProject().property("mainClassName"); + if (RepackageTask.this.mainClass != null) { + mainClass = RepackageTask.this.mainClass; + } + else if (this.extension.getMainClass() != null) { + mainClass = this.extension.getMainClass(); + } + else if (getProject().getTasks().getByName("run").hasProperty("main")) { + mainClass = (String) getProject().getTasks().getByName("run") + .property("main"); + } + getLogger().info("Setting mainClass: " + mainClass); + repackager.setMainClass(mainClass); + } + } + + /** + * {@link Repackager} that also logs when searching takes too long. + */ + private class LoggingRepackager extends Repackager { + + public LoggingRepackager(File source) { + super(source); + } + + @Override + protected String findMainMethod(java.util.jar.JarFile source) throws IOException { + long startTime = System.currentTimeMillis(); + try { + return super.findMainMethod(source); + } + finally { + long duration = System.currentTimeMillis() - startTime; + if (duration > FIND_WARNING_TIMEOUT) { + getLogger().warn("Searching for the main-class is taking " + + "some time, consider using setting " + + "'springBoot.mainClass'"); + } + } + } + } + + void setOutputFile(File file) { + this.outputFile = file; + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/resolve/ResolvePluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/resolve/ResolvePluginFeatures.java new file mode 100644 index 00000000000..0aa37180706 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/resolve/ResolvePluginFeatures.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.resolve; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.springframework.boot.gradle.PluginFeatures; + +/** + * {@link PluginFeatures} to add version resolution support. + * + * @author Phillip Webb + */ +public class ResolvePluginFeatures implements PluginFeatures { + + @Override + public void apply(final Project project) { + project.getConfigurations().all(new Action() { + + @Override + public void execute(Configuration configuration) { + SpringBootResolutionStrategy.applyToConfiguration(project, configuration); + } + + }); + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/resolve/SpringBootResolutionStrategy.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/resolve/SpringBootResolutionStrategy.java new file mode 100644 index 00000000000..739d0eb1647 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/resolve/SpringBootResolutionStrategy.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.resolve; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.DependencyResolveDetails; +import org.gradle.api.artifacts.ModuleVersionSelector; +import org.springframework.boot.dependency.tools.Dependency; +import org.springframework.boot.dependency.tools.ManagedDependencies; +import org.springframework.boot.gradle.VersionManagedDependencies; + +/** + * A resolution strategy to resolve missing version numbers using the + * 'spring-boot-dependencies' POM. + * + * @author Phillip Webb + */ +public class SpringBootResolutionStrategy { + + private static final String SPRING_BOOT_GROUP = "org.springframework.boot"; + + public static void applyToConfiguration(final Project project, + Configuration configuration) { + if (VersionManagedDependencies.CONFIGURATION.equals(configuration.getName())) { + return; + } + VersionResolver versionResolver = new VersionResolver(project); + configuration.getResolutionStrategy().eachDependency(versionResolver); + } + + private static class VersionResolver implements Action { + + private final VersionManagedDependencies versionManagedDependencies; + + public VersionResolver(Project project) { + this.versionManagedDependencies = new VersionManagedDependencies(project); + } + + @Override + public void execute(DependencyResolveDetails resolveDetails) { + String version = resolveDetails.getTarget().getVersion(); + if (version == null || version.trim().length() == 0) { + resolve(resolveDetails); + } + } + + private void resolve(DependencyResolveDetails resolveDetails) { + ManagedDependencies dependencies = this.versionManagedDependencies + .getManagedDependencies(); + ModuleVersionSelector target = resolveDetails.getTarget(); + if (SPRING_BOOT_GROUP.equals(target.getGroup())) { + resolveDetails.useVersion(dependencies.getSpringBootVersion()); + return; + } + Dependency dependency = dependencies.find(target.getGroup(), + target.getName()); + if (dependency != null) { + resolveDetails.useVersion(dependency.getVersion()); + } + } + + } +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/BootRunTask.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/BootRunTask.java new file mode 100644 index 00000000000..05c9d52fff7 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/BootRunTask.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.run; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.gradle.api.internal.file.collections.SimpleFileCollection; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.SourceSet; +import org.springframework.boot.loader.tools.FileUtils; + +/** + * Extension of the standard 'run' task with additional Spring Boot features. + * + * @author Dave Syer + * @author Phillip Webb + */ +public class BootRunTask extends JavaExec { + + /** + * Whether or not resources (typically in {@code src/main/resources} are added + * directly to the classpath. When enabled (the default), this allows live in-place + * editing of resources. Duplicate resources are removed from the resource output + * directory to prevent them from appearing twice if + * {@code ClassLoader.getResources()} is called. + */ + private boolean addResources = true; + + public boolean getAddResources() { + return this.addResources; + } + + public void setAddResources(boolean addResources) { + this.addResources = addResources; + } + + @Override + public void exec() { + addResourcesIfNecessary(); + super.exec(); + } + + private void addResourcesIfNecessary() { + if (this.addResources) { + SourceSet mainSourceSet = SourceSets.findMainSourceSet(getProject()); + final File outputDir = (mainSourceSet == null ? null + : mainSourceSet.getOutput().getResourcesDir()); + final Set resources = new LinkedHashSet(); + if (mainSourceSet != null) { + resources.addAll(mainSourceSet.getResources().getSrcDirs()); + } + List classPath = new ArrayList(getClasspath().getFiles()); + classPath.addAll(0, resources); + getLogger().info("Adding classpath: " + resources); + setClasspath(new SimpleFileCollection(classPath)); + if (outputDir != null) { + for (File directory : resources) { + FileUtils.removeDuplicatesFromOutputDirectory(outputDir, directory); + } + } + } + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/FindMainClassTask.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/FindMainClassTask.java new file mode 100644 index 00000000000..00f553b5ea9 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/FindMainClassTask.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.run; + +import java.io.IOException; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.ApplicationPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskAction; +import org.springframework.boot.gradle.SpringBootPluginExtension; +import org.springframework.boot.loader.tools.MainClassFinder; + +/** + * Task to find and set the 'mainClassName' convention when it's missing by searching the + * main source code. + * + * @author Dave Syer + * @author Phillip Webb + */ +public class FindMainClassTask extends DefaultTask { + + @TaskAction + public void setMainClassNameProperty() { + Project project = getProject(); + if (project.property("mainClassName") == null) { + project.setProperty("mainClassName", findMainClass()); + } + } + + private String findMainClass() { + Project project = getProject(); + + String mainClass = null; + + // Try the SpringBoot extension setting + SpringBootPluginExtension bootExtension = project.getExtensions() + .getByType(SpringBootPluginExtension.class); + if (bootExtension.getMainClass() != null) { + mainClass = bootExtension.getMainClass(); + } + + ApplicationPluginConvention application = (ApplicationPluginConvention) project + .getConvention().getPlugins().get("application"); + // Try the Application extension setting + if (mainClass == null && application.getMainClassName() != null) { + mainClass = application.getMainClassName(); + } + + Task runTask = getProject().getTasks().getByName("run"); + if (mainClass == null && runTask.hasProperty("main")) { + mainClass = (String) runTask.property("main"); + } + + if (mainClass == null) { + // Search + SourceSet mainSourceSet = SourceSets.findMainSourceSet(project); + if (mainSourceSet != null) { + project.getLogger().debug("Looking for main in: " + + mainSourceSet.getOutput().getClassesDir()); + try { + mainClass = MainClassFinder.findSingleMainClass( + mainSourceSet.getOutput().getClassesDir()); + project.getLogger().info("Computed main class: " + mainClass); + } + catch (IOException ex) { + throw new IllegalStateException("Cannot find main class", ex); + } + } + } + + project.getLogger().info("Found main: " + mainClass); + + if (bootExtension.getMainClass() == null) { + bootExtension.setMainClass(mainClass); + } + if (application.getMainClassName() == null) { + application.setMainClassName(mainClass); + } + if (!runTask.hasProperty("main")) { + runTask.setProperty("main", mainClass); + } + + return mainClass; + } +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/RunPluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/RunPluginFeatures.java new file mode 100644 index 00000000000..40f8e34c54f --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/RunPluginFeatures.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.run; + +import java.util.Collections; +import java.util.concurrent.Callable; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.application.CreateStartScripts; +import org.springframework.boot.gradle.PluginFeatures; + +/** + * {@link PluginFeatures} to add run support. + * + * @author Phillip Webb + */ +public class RunPluginFeatures implements PluginFeatures { + + private static final String FIND_MAIN_CLASS_TASK_NAME = "findMainClass"; + + private static final String RUN_APP_TASK_NAME = "bootRun"; + + @Override + public void apply(Project project) { + mainClassNameFinder(project); + addBootRunTask(project); + } + + private void mainClassNameFinder(Project project) { + project.getTasks().create(FIND_MAIN_CLASS_TASK_NAME, FindMainClassTask.class); + project.getTasks().all(new Action() { + @Override + public void execute(Task task) { + if (task instanceof JavaExec || task instanceof CreateStartScripts) { + task.dependsOn(FIND_MAIN_CLASS_TASK_NAME); + } + } + }); + } + + private void addBootRunTask(final Project project) { + final JavaPluginConvention javaConvention = project.getConvention() + .getPlugin(JavaPluginConvention.class); + + BootRunTask run = project.getTasks().create(RUN_APP_TASK_NAME, BootRunTask.class); + run.setDescription("Run the project with support for " + + "auto-detecting main class and reloading static resources"); + run.setGroup("application"); + run.setClasspath( + javaConvention.getSourceSets().findByName("main").getRuntimeClasspath()); + run.getConventionMapping().map("main", new Callable() { + @Override + public Object call() throws Exception { + return project.property("mainClassName"); + } + }); + run.getConventionMapping().map("jvmArgs", new Callable() { + @Override + public Object call() throws Exception { + if (project.hasProperty("applicationDefaultJvmArgs")) { + return project.property("applicationDefaultJvmArgs"); + } + return Collections.emptyList(); + } + }); + } +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/SourceSets.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/SourceSets.java new file mode 100644 index 00000000000..b7c020d2b31 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/run/SourceSets.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.run; + +import java.util.Collections; + +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; + +/** + * Utilities for working with {@link SourceSet}s. + * + * @author Dave Syer + * @author Phillip Webb + */ +class SourceSets { + + public static SourceSet findMainSourceSet(Project project) { + for (SourceSet sourceSet : getJavaSourceSets(project)) { + if (SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) { + return sourceSet; + } + } + return null; + } + + private static Iterable getJavaSourceSets(Project project) { + JavaPluginConvention plugin = project.getConvention() + .getPlugin(JavaPluginConvention.class); + if (plugin == null) { + return Collections.emptyList(); + } + return plugin.getSourceSets(); + } + +}