diff --git a/CHANGES.md b/CHANGES.md index 53f9d597b..7f79fbc1e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ - Generated manifest is now put into the output resources directory, to make sure that it's available at runtime for development. - Fixed a bug in ProjectDepsPlugin where similarly-named jars might not replace all of the desired projects. + e.g. if you want to replace `durian-swt`, `durian-swt.os`, and `durian-swt.cocoa.macosx.x86_64`, in the old version `durian-swt` would not get replaced. Now fixed. ([#80](https://github.com/diffplug/goomph/pull/80)) +- Add BndManifest support for every Jar task [(#79)](https://github.com/diffplug/goomph/pull/79) ### Version 3.16.0 - August 1st 2018 ([javadoc](http://diffplug.github.io/goomph/javadoc/3.16.0/), [jcenter](https://bintray.com/diffplug/opensource/goomph/3.16.0/view)) diff --git a/src/main/java/com/diffplug/gradle/osgi/BndManifestExtension.java b/src/main/java/com/diffplug/gradle/osgi/BndManifestExtension.java index 10687b945..8ba819022 100644 --- a/src/main/java/com/diffplug/gradle/osgi/BndManifestExtension.java +++ b/src/main/java/com/diffplug/gradle/osgi/BndManifestExtension.java @@ -15,8 +15,19 @@ */ package com.diffplug.gradle.osgi; +import java.util.*; + +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.util.GUtil; + /** Determines where the manifest is written out by {@link BndManifestPlugin}. */ public class BndManifestExtension { + public String copyFromTask = JavaPlugin.JAR_TASK_NAME; + + public void copyFromTask(String copyFromTask) { + this.copyFromTask = copyFromTask; + } + public Object copyTo = null; public void copyTo(Object copyTo) { @@ -29,5 +40,20 @@ public void mergeWithExisting(boolean mergeWithExisting) { this.mergeWithExisting = mergeWithExisting; } + public Set includeTasks = new HashSet<>(Collections.singletonList(JavaPlugin.JAR_TASK_NAME)); + + public void includeTask(Object task) { + includeTasks.add(task); + } + + public void includeTasks(Object... tasks) { + Collections.addAll(includeTasks, tasks); + } + + public void setIncludeTasks(Iterable includeTasks) { + this.includeTasks.clear(); + GUtil.addToCollection(this.includeTasks, includeTasks); + } + static final String NAME = "osgiBndManifest"; } diff --git a/src/main/java/com/diffplug/gradle/osgi/BndManifestPlugin.java b/src/main/java/com/diffplug/gradle/osgi/BndManifestPlugin.java index a96185e39..766765d16 100644 --- a/src/main/java/com/diffplug/gradle/osgi/BndManifestPlugin.java +++ b/src/main/java/com/diffplug/gradle/osgi/BndManifestPlugin.java @@ -22,13 +22,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; import org.gradle.api.Project; +import org.gradle.api.Task; import org.gradle.api.java.archives.Attributes; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; @@ -39,10 +37,7 @@ import aQute.bnd.osgi.Builder; import aQute.bnd.osgi.Constants; -import com.diffplug.common.base.Errors; -import com.diffplug.common.base.Predicates; -import com.diffplug.common.base.StringPrinter; -import com.diffplug.common.base.Throwing; +import com.diffplug.common.base.*; import com.diffplug.common.collect.ImmutableMap; import com.diffplug.gradle.FileMisc; import com.diffplug.gradle.ProjectPlugin; @@ -50,11 +45,11 @@ /** * Generates a manifest using purely bnd, and outputs it for IDE consumption. - * + * * Generating manifests by hand is a recipe for mistakes. Bnd does a fantastic * job generating all this stuff for you, but there's a lot of wiring required * to tie bnd into both Eclipse PDE and Gradle. Which is what Goomph is for! - * + * * ```groovy * apply plugin: 'com.diffplug.gradle.osgi.bndmanifest' * // Pass headers and bnd directives: http://www.aqute.biz/Bnd/Format @@ -76,17 +71,17 @@ * // By default, the existing manifest is completely ignored. * // The line below will cause the existing manifest's fields * // to be merged with the fields set by bnd. - * mergeWithExisting true + * mergeWithExisting true * } * ``` - * + * * Besides passing raw headers and bnd directives, this plugin also takes the following actions: - * + * * * Passes the project version to bnd if {@code Bundle-Version} hasn't been set explicitly. * * Replaces `-SNAPSHOT` in the version with `.IyyyyMMddkkmm` (to-the-minute timestamp). * * Passes the {@code runtime} configuration's classpath to bnd for manifest calculation. * * Instructs bnd to respect the result of the {@code processResources} task. - * + * * Many thanks to JRuyi and Agemo Cui for their excellent * [osgibnd-gradle-plugin](https://github.com/jruyi/osgibnd-gradle-plugin). * This plugin follows the template set by their plugin, but with fewer automagic @@ -97,36 +92,52 @@ public class BndManifestPlugin extends ProjectPlugin { protected void applyOnce(Project proj) { ProjectPlugin.getPlugin(proj, JavaPlugin.class); BndManifestExtension extension = proj.getExtensions().create(BndManifestExtension.NAME, BndManifestExtension.class); - Jar jarTask = (Jar) proj.getTasks().getByName(JavaPlugin.JAR_TASK_NAME); - // at the end of the jar, modify the manifest, and possibly write it to `osgiBndManifest { bndManifestcopyTo }`. - jarTask.doLast(unused -> { - Errors.rethrow().run(() -> { - byte[] manifest = getManifestContent(jarTask, extension).getBytes(StandardCharsets.UTF_8); - // modify the jar - Map> toModify = ImmutableMap.of("META-INF/MANIFEST.MF", in -> manifest); - ZipMisc.modify(jarTask.getArchivePath(), toModify, Predicates.alwaysFalse()); - // write manifest to the output resources directory - Throwing.Consumer writeManifest = path -> { - if (Files.exists(path)) { - if (Arrays.equals(Files.readAllBytes(path), manifest)) { - return; + + proj.afterEvaluate(project -> { + + // copyFromTask must be configured if copyTo is used + Preconditions.checkArgument(extension.copyTo == null || extension.copyFromTask != null, + "copyFromTask can not be null if copyTo is set. Please provide a source task."); + + final Jar copyFromTask = (extension.copyFromTask == null) ? null : getAsJar(proj, extension.copyFromTask); + + Preconditions.checkArgument(extension.copyFromTask == null || extension.includeTasks.contains(extension.copyFromTask), + "copyFromTask must reside within includeTask"); + + extension.includeTasks.forEach(name -> { + + Jar jarTask = getAsJar(proj, (String) name); + + // at the end of the jar, modify the manifest + jarTask.doLast(unused -> { + Errors.rethrow().run(() -> { + byte[] manifest = getManifestContent(jarTask, extension).getBytes(StandardCharsets.UTF_8); + // modify the jar + Map> toModify = ImmutableMap.of("META-INF/MANIFEST.MF", in -> manifest); + ZipMisc.modify(jarTask.getArchivePath(), toModify, Predicates.alwaysFalse()); + // write manifest to the output resources directory + Throwing.Consumer writeManifest = path -> { + if (Files.exists(path)) { + if (Arrays.equals(Files.readAllBytes(path), manifest)) { + return; + } + } + Files.createDirectories(path.getParent()); + Files.write(path, manifest); + }; + writeManifest.accept(outputManifest(jarTask)); + + // and maybe write it to `osgiBndManifest { copyTo }`. + if (extension.copyTo != null && jarTask.equals(copyFromTask)) { + writeManifest.accept(jarTask.getProject().file(extension.copyTo).toPath()); } - } - Files.createDirectories(path.getParent()); - Files.write(path, manifest); - }; - writeManifest.accept(outputManifest(jarTask)); - // and the jarTask, maybe - if (extension.copyTo != null) { - writeManifest.accept(jarTask.getProject().file(extension.copyTo).toPath()); - } + }); + }); }); - }); - proj.afterEvaluate(project -> { // find the file that the user would like us to copy to (if any) - if (extension.copyTo != null) { - jarTask.getOutputs().file(extension.copyTo); + if (extension.copyTo != null && copyFromTask != null) { + copyFromTask.getOutputs().file(extension.copyTo); } }); } @@ -142,7 +153,7 @@ private static String getManifestContent(Jar jarTask, BndManifestExtension exten if (!extension.mergeWithExisting) { Files.deleteIfExists(outputManifest(jarTask)); } - // take the bnd action + // take the bnd action return BndManifestPlugin.takeBndAction(jarTask.getProject(), jarTask, jar -> { return StringPrinter.buildString(printer -> { try (OutputStream output = printer.toOutputStream(StandardCharsets.UTF_8)) { @@ -173,15 +184,14 @@ private static String takeBndAction(Project project, Jar jarTask, Throwing.Funct JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class); SourceSetOutput main = javaConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput(); // delete empty folders so that bnd doesn't make Export-Package entries for them - StringBuilder includeresource = new StringBuilder(); + Set includeresource = new LinkedHashSet<>(); deleteEmptyFoldersIfExists(main.getResourcesDir()); - includeresource.append(fix(main.getResourcesDir())); + includeresource.add(fix(main.getResourcesDir())); for (File file : main.getClassesDirs()) { deleteEmptyFoldersIfExists(file); - includeresource.append(","); - includeresource.append(fix(file)); + includeresource.add(fix(file)); } - builder.set(Constants.INCLUDERESOURCE, includeresource.toString()); + builder.set(Constants.INCLUDERESOURCE, String.join(",", includeresource)); // set the version if (builder.getBundleVersion() == null) { @@ -215,4 +225,10 @@ private static String dateQualifier() { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddkkmm"); return dateFormat.format(new Date()); } + + private static Jar getAsJar(Project project, String taskName) { + Task task = project.getTasks().getByName(taskName); + Preconditions.checkArgument(task instanceof Jar, "Task " + taskName + " must be a Jar derived task for generating BndManifest"); + return (Jar) task; + } } diff --git a/src/test/java/com/diffplug/gradle/osgi/BndManifestPluginTest.java b/src/test/java/com/diffplug/gradle/osgi/BndManifestPluginTest.java index c9b29b888..e4a344309 100644 --- a/src/test/java/com/diffplug/gradle/osgi/BndManifestPluginTest.java +++ b/src/test/java/com/diffplug/gradle/osgi/BndManifestPluginTest.java @@ -74,7 +74,110 @@ public void assertMerging() throws IOException { testCase("osgiBndManifest { mergeWithExisting true }", expectedMerge); } + @Test + public void assertCustomJarTask() throws IOException { + write("src/main/resources/META-INF/MANIFEST.MF", + "Manifest-Version: 1.0", + "Bundle-ManifestVersion: 2", + "Bundle-Name: Mock", + "Bundle-SymbolicName: org.eclipse.e4.demo.e4photo.flickr.mock; singleton:=true", + "Bundle-Version: 1.0.0.qualifier", + "Bundle-ActivationPolicy: lazy", + "Require-Bundle: org.eclipse.core.runtime"); + String expectedMerge = StringPrinter.buildStringFromLines( + "Manifest-Version: 1.0", + "Bundle-ActivationPolicy: lazy", + "Bundle-ManifestVersion: 2", + "Bundle-SymbolicName: test", + "Bundle-Version: 0.0.0.ERRORSETVERSION", + "Export-Package: test;uses:=\"com.diffplug.common.base\";version=\"0.0.0\"", + "Import-Package: com.diffplug.common.base;version=\"[3.4,4)\"", + "Require-Bundle: org.eclipse.core.runtime", + "Require-Capability: osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\""); + String buildScript = StringPrinter.buildStringFromLines( + "task customJar(type: Jar) {", + " with jar", + " manifest.attributes(", + " '-exportcontents': 'test.*',", + " '-removeheaders': 'Bnd-LastModified,Bundle-Name,Created-By,Tool,Private-Package',", + " 'Bundle-SymbolicName': 'test'", + " )", + " classifier 'custom'", + "}", + "osgiBndManifest { ", + " mergeWithExisting true", + " includeTask 'customJar'", + "}"); + + testCase(buildScript, expectedMerge, "customJar"); + } + + @Test + public void assertCustomJarOnly() throws IOException { + write("src/main/resources/META-INF/MANIFEST.MF", + "Manifest-Version: 1.0", + "Bundle-ManifestVersion: 2", + "Bundle-Name: Mock", + "Bundle-SymbolicName: org.eclipse.e4.demo.e4photo.flickr.mock; singleton:=true", + "Bundle-Version: 1.0.0.qualifier", + "Bundle-ActivationPolicy: lazy", + "Require-Bundle: org.eclipse.core.runtime"); + + String expected = StringPrinter.buildStringFromLines( + "Manifest-Version: 1.0", + "Bundle-ActivationPolicy: lazy", + "Bundle-ManifestVersion: 2", + "Bundle-SymbolicName: test", + "Bundle-Version: 0.0.0.ERRORSETVERSION", + "Export-Package: test;uses:=\"com.diffplug.common.base\";version=\"0.0.0\"", + "Import-Package: com.diffplug.common.base;version=\"[3.4,4)\"", + "Require-Bundle: org.eclipse.core.runtime", + "Require-Capability: osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\""); + + String buildscriptAddendum = StringPrinter.buildStringFromLines( + "task customJar(type: Jar) {", + " with jar", + " manifest.attributes(", + " '-exportcontents': 'test.*',", + " '-removeheaders': 'Bnd-LastModified,Bundle-Name,Created-By,Tool,Private-Package',", + " 'Bundle-SymbolicName': 'test'", + " )", + " classifier 'custom'", + "}", + "osgiBndManifest { ", + " copyFromTask 'customJar'", + " mergeWithExisting true", + " includeTasks = ['customJar']", + "}"); + + testCase(buildscriptAddendum, expected, "customJar"); + } + + @Test + public void assertCustomJarExcluded() throws IOException { + String expected = StringPrinter.buildStringFromLines( + "Manifest-Version: 1.0", + ""); + + write("src/main/resources/META-INF/MANIFEST.MF", + "Manifest-Version: 1.0"); + + String buildscriptAddendum = StringPrinter.buildStringFromLines( + "task customJar(type: Jar) {", + " classifier 'custom'", + "}", + "osgiBndManifest { ", + " mergeWithExisting true", + "}"); + + testCase(buildscriptAddendum, expected, "customJar"); + } + private void testCase(String buildscriptAddendum, String expectedManifest) throws IOException { + testCase(buildscriptAddendum, expectedManifest, "jar"); + } + + private void testCase(String buildscriptAddendum, String expectedManifest, String task) throws IOException { write("build.gradle", "plugins {", " id 'java'", @@ -98,7 +201,7 @@ private void testCase(String buildscriptAddendum, String expectedManifest) throw " return new StringPrinter(str -> {});", " }", "}"); - gradleRunner().withArguments("jar", "--stacktrace").build(); + gradleRunner().withArguments(task, "--stacktrace").build(); // make sure the jar contains the proper manifest File libsDir = file("build/libs");