diff --git a/src/main/java/org/terracotta/build/plugins/BlacklistPlugin.java b/src/main/java/org/terracotta/build/plugins/BlacklistPlugin.java index d24faca..9204dfc 100644 --- a/src/main/java/org/terracotta/build/plugins/BlacklistPlugin.java +++ b/src/main/java/org/terracotta/build/plugins/BlacklistPlugin.java @@ -4,6 +4,7 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; import org.gradle.api.artifacts.MutableVersionConstraint; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.tasks.SourceSetContainer; @@ -12,6 +13,7 @@ import static org.gradle.api.tasks.SourceSet.TEST_SOURCE_SET_NAME; +@SuppressWarnings("UnstableApiUsage") public class BlacklistPlugin implements Plugin { private static final String BLACKLIST_CONFIGURATION = "blacklist"; @@ -19,11 +21,7 @@ public class BlacklistPlugin implements Plugin { @Override public void apply(Project project) { - NamedDomainObjectProvider blacklist = project.getConfigurations().register(BLACKLIST_CONFIGURATION, config -> { - config.setVisible(false); - config.setCanBeConsumed(false); - config.setCanBeResolved(false); - }); + NamedDomainObjectProvider blacklist = project.getConfigurations().dependencyScope(BLACKLIST_CONFIGURATION); project.getConfigurations().configureEach(other -> { Configuration config = blacklist.get(); @@ -32,11 +30,7 @@ public void apply(Project project) { } }); - NamedDomainObjectProvider testBlacklist = project.getConfigurations().register(TEST_BLACKLIST_CONFIGURATION, config -> { - config.setVisible(false); - config.setCanBeConsumed(false); - config.setCanBeResolved(false); - }); + NamedDomainObjectProvider testBlacklist = project.getConfigurations().dependencyScope(TEST_BLACKLIST_CONFIGURATION); project.getPlugins().withType(JavaBasePlugin.class).configureEach(javaBasePlugin -> { project.getExtensions().configure(SourceSetContainer.class, sourceSets -> diff --git a/src/main/java/org/terracotta/build/plugins/PackagePlugin.java b/src/main/java/org/terracotta/build/plugins/PackagePlugin.java index 02bca6a..ee9337f 100644 --- a/src/main/java/org/terracotta/build/plugins/PackagePlugin.java +++ b/src/main/java/org/terracotta/build/plugins/PackagePlugin.java @@ -24,23 +24,24 @@ import javax.inject.Inject; -import static org.gradle.api.internal.tasks.JvmConstants.JAVA_COMPONENT_NAME; +import static org.gradle.api.artifacts.Dependency.DEFAULT_CONFIGURATION; +import static org.gradle.api.internal.tasks.JvmConstants.RUNTIME_ELEMENTS_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.API_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.COMPILE_ONLY_API_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME; import static org.terracotta.build.plugins.packaging.PackageInternal.CONTENTS_API_CONFIGURATION_NAME; import static org.terracotta.build.plugins.packaging.PackageInternal.CONTENTS_CONFIGURATION_NAME; -import static org.terracotta.build.plugins.packaging.PackageInternal.PROVIDED_CONFIGURATION_NAME; -import static org.terracotta.build.plugins.packaging.PackageInternal.UNPACKAGED_JAVA_RUNTIME; import static org.terracotta.build.plugins.packaging.PackageInternal.camelPrefix; /** - * EhDistribute + * A plugin which assembles partial uber-jars. */ @SuppressWarnings("UnstableApiUsage") public abstract class PackagePlugin implements Plugin { + public static final String PACKAGE_COMPONENT_NAME = "package"; + public static final String EXPLODED_JAVA_RUNTIME = "exploded-java-runtime"; public static final String COMMON_PREFIX = "common"; @Inject @@ -50,13 +51,16 @@ public abstract class PackagePlugin implements Plugin { public void apply(Project project) { project.getPlugins().apply(BasePlugin.class); - AdhocComponentWithVariants javaComponent = getSoftwareComponentFactory().adhoc(JAVA_COMPONENT_NAME); - project.getComponents().add(javaComponent); + project.getComponents().registerFactory(AdhocComponentWithVariants.class, getSoftwareComponentFactory()::adhoc); + NamedDomainObjectProvider component = project.getComponents().register(PACKAGE_COMPONENT_NAME, AdhocComponentWithVariants.class); project.getTasks().withType(ShadowJar.class).configureEach(shadow -> shadow.getExtensions().create("osgi", OsgiManifestJarExtension.class, shadow)); ConfigurationContainer configurations = project.getConfigurations(); + /* + * Create the set of common dependency scopes that all the package specific scopes extend. + */ NamedDomainObjectProvider commonContentsApi = configurations.dependencyScope(camelPrefix(COMMON_PREFIX, CONTENTS_API_CONFIGURATION_NAME), c -> c.setDescription("API dependencies for all packages contents.")); configurations.dependencyScope(camelPrefix(COMMON_PREFIX, CONTENTS_CONFIGURATION_NAME), @@ -70,41 +74,45 @@ public void apply(Project project) { c -> c.setDescription("Compile-only API dependencies for all packaged artifacts.")); configurations.dependencyScope(camelPrefix(COMMON_PREFIX, RUNTIME_ONLY_CONFIGURATION_NAME), c -> c.setDescription("Runtime-only dependencies for all packaged artifacts.")); - configurations.dependencyScope(camelPrefix(COMMON_PREFIX, PROVIDED_CONFIGURATION_NAME), - c -> c.setDescription("'Provided' API dependencies for all packaged artifacts.")); PackagingExtensionInternal packaging = (PackagingExtensionInternal) project.getExtensions().create(PackagingExtension.class, "packaging", PackagingExtensionInternal.class); packaging.getDefaultPackage().create(); packaging.getVariants().all(PackageInternal::create); + configurations.named(DEFAULT_CONFIGURATION).configure(c -> c.extendsFrom(configurations.getByName(RUNTIME_ELEMENTS_CONFIGURATION_NAME))); + project.getPlugins().withType(MavenPublishPlugin.class).configureEach(plugin -> { project.getExtensions().configure(PublishingExtension.class, publishing -> { - publishing.getPublications().register("mavenJava", MavenPublication.class, mavenPublication -> { - mavenPublication.from(javaComponent); + publishing.getPublications().register("mavenPackage", MavenPublication.class, mavenPublication -> { + mavenPublication.from(component.get()); }); }); }); } public static void augmentAttributeSchema(AttributesSchema schema) { - schema.attribute(Usage.USAGE_ATTRIBUTE, strategy -> strategy.getCompatibilityRules().add(UnpackagedJavaRuntimeCompatibility.class)); + schema.attribute(Usage.USAGE_ATTRIBUTE, strategy -> strategy.getCompatibilityRules().add(ExplodedJavaRuntimeCompatibility.class)); } - public static class UnpackagedJavaRuntimeCompatibility implements AttributeCompatibilityRule { + @SuppressWarnings("deprecation") + public static class ExplodedJavaRuntimeCompatibility implements AttributeCompatibilityRule { @Override public void execute(CompatibilityCheckDetails details) { - Usage consumer = details.getConsumerValue(); + Usage consumerValue = details.getConsumerValue(); + Usage producerValue = details.getProducerValue(); + + if (consumerValue == null) { + details.compatible(); + return; + } - if (consumer != null && UNPACKAGED_JAVA_RUNTIME.equals(consumer.getName())) { - Usage producer = details.getProducerValue(); - if (producer != null) { - switch (producer.getName()) { - case Usage.JAVA_RUNTIME: + if (producerValue != null && EXPLODED_JAVA_RUNTIME.equals(consumerValue.getName())) { + switch (producerValue.getName()) { + case Usage.JAVA_RUNTIME: case JavaEcosystemSupport.DEPRECATED_JAVA_RUNTIME_JARS: - details.compatible(); - break; - } + details.compatible(); + break; } } } diff --git a/src/main/java/org/terracotta/build/plugins/VoltronPlugin.java b/src/main/java/org/terracotta/build/plugins/VoltronPlugin.java index 38bd3d9..0e85566 100644 --- a/src/main/java/org/terracotta/build/plugins/VoltronPlugin.java +++ b/src/main/java/org/terracotta/build/plugins/VoltronPlugin.java @@ -4,8 +4,10 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.artifacts.ResolvableConfiguration; import org.gradle.api.artifacts.dsl.DependencyFactory; import org.gradle.api.capabilities.Capability; import org.gradle.api.file.FileCollection; @@ -44,6 +46,7 @@ public class VoltronPlugin implements Plugin { public static final String XML_CONFIG_VARIANT_NAME = "xml"; public static final String VOLTRON_CONFIGURATION_NAME = "voltron"; public static final String SERVICE_CONFIGURATION_NAME = "service"; + private static final String PROVIDED_CLASSPATH_CONFIGURATION_NAME = "providedClasspath"; @Override public void apply(Project project) { @@ -67,16 +70,12 @@ public void apply(Project project) { }); }); - NamedDomainObjectProvider voltron = project.getConfigurations().register(VOLTRON_CONFIGURATION_NAME, config -> { - config.setDescription("Dependencies provided by the platform Kit, either from server/lib or from plugins/api"); - config.setCanBeResolved(true); - config.setCanBeConsumed(true); - }); - NamedDomainObjectProvider service = project.getConfigurations().register(SERVICE_CONFIGURATION_NAME, config -> { - config.setDescription("Services consumed by this plugin"); - config.setCanBeResolved(true); - config.setCanBeConsumed(true); - }); + NamedDomainObjectProvider voltron = project.getConfigurations().dependencyScope(VOLTRON_CONFIGURATION_NAME, + c -> c.setDescription("Dependencies provided by the platform Kit, either from server/lib or from plugins/api")); + NamedDomainObjectProvider service = project.getConfigurations().dependencyScope(SERVICE_CONFIGURATION_NAME, + c -> c.setDescription("Services consumed by this plugin")); + NamedDomainObjectProvider providedClasspath = project.getConfigurations().resolvable(PROVIDED_CLASSPATH_CONFIGURATION_NAME, c -> c.extendsFrom(voltron.get(), service.get())); + project.getConfigurations().named(JavaPlugin.API_CONFIGURATION_NAME, config -> config.extendsFrom(service.get())); project.getConfigurations().named(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, config -> config.extendsFrom(voltron.get())); project.getConfigurations().named(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, config -> config.extendsFrom(voltron.get())); @@ -85,10 +84,9 @@ public void apply(Project project) { NamedDomainObjectProvider runtimeClasspath = project.getConfigurations().named(RUNTIME_CLASSPATH_CONFIGURATION_NAME); jar.getInputs().property(RUNTIME_CLASSPATH_CONFIGURATION_NAME, runtimeClasspath.flatMap(c -> c.getElements().map(e -> e.stream().map(f -> f.getAsFile().getName()).collect(toSet())))); - jar.getInputs().property(SERVICE_CONFIGURATION_NAME, service.flatMap(c -> c.getElements().map(e -> e.stream().map(f -> f.getAsFile().getName()).collect(toSet())))); - jar.getInputs().property(VOLTRON_CONFIGURATION_NAME, voltron.flatMap(c -> c.getElements().map(e -> e.stream().map(f -> f.getAsFile().getName()).collect(toSet())))); + jar.getInputs().property(PROVIDED_CLASSPATH_CONFIGURATION_NAME, providedClasspath.flatMap(c -> c.getElements().map(e -> e.stream().map(f -> f.getAsFile().getName()).collect(toSet())))); - Provider voltronClasspath = runtimeClasspath.zip(service.zip(voltron, FileCollection::plus), FileCollection::minus) + Provider voltronClasspath = runtimeClasspath.zip(providedClasspath, FileCollection::minus) .map(c -> c.getFiles().stream().map(File::getName).collect(joining(" "))); jar.manifest(manifest -> manifest.attributes(mapOf(Attributes.Name.CLASS_PATH.toString(), voltronClasspath))); }); diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerBuild.java b/src/main/java/org/terracotta/build/plugins/docker/DockerBuild.java index 366a809..c0d92c0 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerBuild.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerBuild.java @@ -25,6 +25,9 @@ import static java.util.Collections.emptyMap; import static java.util.stream.Stream.of; +/** + * Docker '{@code docker build}' task. + */ public abstract class DockerBuild extends DockerTask { public DockerBuild() { @@ -62,21 +65,51 @@ public void build() { }); } + /** + * Dockerfile to build + * + * @return target Dockerfile + */ @InputFile public abstract RegularFileProperty getDockerfile(); + /** + * Assembled environment for the Dockerfile + * + * @return dockerfile environment + */ @InputDirectory @SkipWhenEmpty public abstract DirectoryProperty getEnvironment(); + /** + * Image ID file where the resultant image hash will be written + * + * @return output image id file + */ @OutputFile public abstract RegularFileProperty getImageIdFile(); + /** + * Metadata labels to apply to the image. + * + * @return image metadata labels + */ @Input public abstract MapProperty getMetadata(); + /** + * Map of input build arguments + * + * @return build arguments + */ @Input public abstract MapProperty getBuildArgs(); + /** + * Reusltant image hash/id. + * + * @return output image hash + */ @Internal public Provider getImageId() { return getImageIdFile().map(DockerBuild::readImageId); diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerBuildPlugin.java b/src/main/java/org/terracotta/build/plugins/docker/DockerBuildPlugin.java index d077792..11b3ddb 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerBuildPlugin.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerBuildPlugin.java @@ -33,6 +33,7 @@ import static org.terracotta.build.Utils.mapOf; import static org.terracotta.build.plugins.docker.DockerEcosystemPlugin.DOCKER_IMAGE_ID; +@SuppressWarnings("UnstableApiUsage") public class DockerBuildPlugin implements Plugin { private static final Pattern DOCKERFILE_EVAL_PATTERN = Pattern.compile("\\$\\$\\{(?.+)}"); @@ -43,10 +44,16 @@ public void apply(Project project) { DockerExtension dockerExtension = project.getExtensions().getByType(DockerExtension.class); + /* + * Attach build extension to docker extension installed by ecosystem plugin. + */ DockerBuildExtension buildExtension = ((ExtensionAware) dockerExtension).getExtensions().create("build", DockerBuildExtension.class); configureBuildDefaults(project, buildExtension); + /* + * + */ TaskProvider dockerEnvironment = project.getTasks().register("dockerEnvironment", Sync.class, sync -> { sync.setDescription("Assembles the Docker build environment."); sync.setGroup(LifecycleBasePlugin.BUILD_GROUP); @@ -173,14 +180,11 @@ public void apply(Project project) { purge.getArguments().add("--force"); }); - project.getConfigurations().register("outgoingDocker", config -> { - config.setDescription("Built Docker image-id files."); - config.setCanBeConsumed(true); - config.setCanBeResolved(false); - config.setVisible(false); - config.attributes(attrs -> attrs.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, DOCKER_IMAGE_ID))); - config.outgoing(outgoing -> outgoing.artifact(dockerBuild)); - }); + project.getConfigurations().consumable("outgoingDocker", c -> c + .setDescription("Built Docker image-id files.") + .attributes(attrs -> attrs.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, DOCKER_IMAGE_ID))) + .outgoing(outgoing -> outgoing.artifact(dockerBuild)) + ); project.getPlugins().withType(CopyrightPlugin.class).configureEach(plugin -> ((ExtensionAware) buildExtension).getExtensions().add("copyright", plugin.createCopyrightSet(project, "docker", copyright -> { @@ -197,9 +201,9 @@ private void configureBuildDefaults(Project project, DockerBuildExtension docker dockerBuild.getBuildArgs().convention(emptyMap()); dockerBuild.getDockerReadme().convention(dockerSourceBase.file("README.md")); dockerBuild.getContentsDirectory().convention(dockerSourceBase.dir("contents")); - dockerBuild.getDocTemplates().convention(project.getLayout().getProjectDirectory().dir("../doc/templates")); + dockerBuild.getDocTemplates().convention(project.getLayout().getProjectDirectory().dir("doc/templates")); dockerBuild.getDocMetadata().convention(emptyMap()); - dockerBuild.getSagDocDirectory().convention(project.getLayout().getProjectDirectory().dir("../doc/acr-data")); + dockerBuild.getSagDocDirectory().convention(project.getLayout().getProjectDirectory().dir("doc/acr-data")); dockerBuild.getTags().convention(project.provider(() -> singletonList(project.getVersion().toString()))); dockerBuild.getMetadata().put("gradle.build.host", getLocalHostName(project)); diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerEcosystemPlugin.java b/src/main/java/org/terracotta/build/plugins/docker/DockerEcosystemPlugin.java index 8cd3cc8..cc0c777 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerEcosystemPlugin.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerEcosystemPlugin.java @@ -4,6 +4,9 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; +import org.gradle.api.artifacts.ResolvableConfiguration; +import org.gradle.api.artifacts.ResolvableDependencies; import org.gradle.api.attributes.Category; import org.gradle.api.provider.Provider; @@ -11,28 +14,33 @@ import static java.util.stream.Collectors.toMap; +@SuppressWarnings("UnstableApiUsage") public class DockerEcosystemPlugin implements Plugin { public static final String DOCKER_IMAGE_ID = "docker-image"; @Override public void apply(Project project) { - NamedDomainObjectProvider dockerBucket = project.getConfigurations().register("docker", config -> { - config.setDescription("Docker image dependencies."); - config.setCanBeConsumed(false); - config.setCanBeResolved(false); - config.setVisible(false); - }); - - NamedDomainObjectProvider dockerImageIds = project.getConfigurations().register("dockerImageIds", config -> { - config.setDescription("Incoming docker image-id files."); - config.setCanBeConsumed(false); - config.setCanBeResolved(true); - config.setVisible(false); - config.extendsFrom(dockerBucket.get()); - config.attributes(attrs -> attrs.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, DOCKER_IMAGE_ID))); + /* + * Dependency scope for docker image dependencies. + */ + NamedDomainObjectProvider dockerBucket = project.getConfigurations().dependencyScope("docker", c -> { + c.setDescription("Docker image dependencies."); }); + /* + * Resolvable configuration that resolves docker image dependencies to a set of files containing Docker images hashes. + * We define a new {@code Category} attribute value to distinguish this configuration. + */ + NamedDomainObjectProvider dockerImageIds = project.getConfigurations().resolvable("dockerImageIds", c -> c + .setDescription("Incoming docker image-id files.") + .extendsFrom(dockerBucket.get()) + .attributes(attrs -> attrs.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, DOCKER_IMAGE_ID))) + ); + + /* + * Register docker extension to house convenience accessor for image ids. + */ project.getExtensions().create("docker", DockerExtension.class, dockerImageIds); } @@ -40,7 +48,7 @@ public static class DockerExtension { private final Images images; - public DockerExtension(Provider imageIdConfiguration) { + public DockerExtension(Provider imageIdConfiguration) { this.images = new Images(imageIdConfiguration); } @@ -53,7 +61,7 @@ public static class Images { private final Provider> images; - public Images(Provider imageIdConfiguration) { + public Images(Provider imageIdConfiguration) { this.images = imageIdConfiguration.flatMap(c -> c.getElements().map(files -> files.stream().collect( toMap(a -> a.getAsFile().getName().replaceAll("\\.iid$", ""), DockerBuild::readImageId)))); } diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerPush.java b/src/main/java/org/terracotta/build/plugins/docker/DockerPush.java index af48a15..6215684 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerPush.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerPush.java @@ -13,6 +13,9 @@ import static org.gradle.api.publish.plugins.PublishingPlugin.PUBLISH_TASK_GROUP; +/** + * Docker '{@code docker push}' task. + */ public abstract class DockerPush extends DockerTask { public DockerPush() { @@ -35,9 +38,19 @@ public void push() { } } + /** + * Docker registry to push images to. + * + * @return target docker registry + */ @Internal public abstract Property getRegistry(); + /** + * List of docker tags to push. + * + * @return tags to push + */ @Input public abstract ListProperty getTags(); } diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerPushReadme.java b/src/main/java/org/terracotta/build/plugins/docker/DockerPushReadme.java index 9c707c4..080228b 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerPushReadme.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerPushReadme.java @@ -27,6 +27,9 @@ import static java.util.Collections.singletonMap; import static org.apache.hc.client5.http.fluent.Request.patch; +/** + * Task that uploads readme content to a "Docker Trusted Registry" server. + */ public abstract class DockerPushReadme extends DockerTask { public DockerPushReadme() { @@ -62,12 +65,27 @@ public void pushReadme() throws Exception { })); } + /** + * Target registry configuration. + * + * @return registry configuration + */ @Input public abstract Property getRegistry(); + /** + * Target repository name. + * + * @return repository name + */ @Input public abstract Property getRepositoryName(); + /** + * Input file containing readme content. + * + * @return readme file + */ @InputFile public abstract RegularFileProperty getReadmeFile(); } diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerRegistryService.java b/src/main/java/org/terracotta/build/plugins/docker/DockerRegistryService.java index 7584eb6..10fd008 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerRegistryService.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerRegistryService.java @@ -42,6 +42,12 @@ public abstract class DockerRegistryService implements BuildService login(Action action) { return spec -> { login().ifPresent(config -> spec.args("--config", config.toString())); @@ -49,6 +55,9 @@ public Action login(Action action) { }; } + /** + * Logs out from the registry and deletes the local config context files. + */ public void close() { logout(); } diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerRmi.java b/src/main/java/org/terracotta/build/plugins/docker/DockerRmi.java index 00a00d0..f33e4d2 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerRmi.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerRmi.java @@ -9,6 +9,9 @@ import static java.util.stream.Collectors.toList; +/** + * Docker '{@code docker rmi}' task. + */ public abstract class DockerRmi extends DockerTask { public DockerRmi() { @@ -24,12 +27,29 @@ public void rmi() { } } + /** + * Filters for the images to remove, as defined by the docker CLI. + * + * @return the image filters + */ @Input public abstract ListProperty getFilters(); + /** + * Arguments for `rmi` command. + * + * @return `rmi` arguments + */ @Input public abstract ListProperty getArguments(); + /** + * Metadata mappings to filter for when removing images. + *

+ * These mappings are converted to filters of the form: {@code label==}. + * + * @return metadata values to filter for + */ @Input public abstract MapProperty getMetadata(); } diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerTag.java b/src/main/java/org/terracotta/build/plugins/docker/DockerTag.java index d1c1189..ebbab0e 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerTag.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerTag.java @@ -6,6 +6,9 @@ import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; +/** + * Docker '{@code docker tag}' task. + */ public abstract class DockerTag extends DockerTask { public DockerTag() { @@ -17,9 +20,19 @@ public void tag() { getTags().get().forEach(tag -> docker(spec -> spec.args("tag", getImageId().get(), tag))); } + /** + * Tags to create. + * + * @return tags to create + */ @Input public abstract ListProperty getTags(); + /** + * Image id (hash) to tag. + * + * @return image id + */ @Input public abstract Property getImageId(); } diff --git a/src/main/java/org/terracotta/build/plugins/docker/DockerTask.java b/src/main/java/org/terracotta/build/plugins/docker/DockerTask.java index a3099f5..5d2c4e6 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/DockerTask.java +++ b/src/main/java/org/terracotta/build/plugins/docker/DockerTask.java @@ -28,6 +28,9 @@ import static org.terracotta.build.ExecUtils.execUnder; import static org.terracotta.build.PluginUtils.capitalize; +/** + * Abstract super class for all docker command invoking tasks. + */ public abstract class DockerTask extends DefaultTask { public DockerTask() { @@ -35,12 +38,30 @@ public DockerTask() { getAvailableTimeout().convention(getProject().getProviders().gradleProperty("dockerAvailableTimeout").map(timeout -> Duration.ofMillis(parseLong(timeout))).orElse(Duration.ofMillis(1000))); } + /** + * The docker command, convention defaults this to {@code docker} + * + * @return the docker command + */ @Input public abstract Property getDocker(); + /** + * The docker execution environment variables. + * + * @return the docker command environment + */ @Input public abstract MapProperty getDockerEnv(); + /** + * Execute docker using the provided action to configure the command. + *

+ * Command output is logged at info, and relogged at error on command failure. + * + * @param action execution configuration action + * @return the standard output of the command + */ public String docker(Action action) { return execUnder(this, composite(exec -> { exec.environment(getDockerEnv().getOrElse(emptyMap())); @@ -48,6 +69,15 @@ public String docker(Action action) { }, action)); } + /** + * Execute docker quietly using the provided action to configure the command. + *

+ * Command output is logged at debug regardless of the result of the command. + * This method is useful for commands where failure is 'normal'. + * + * @param action execution configuration action + * @return the standard output of the command + */ public String dockerQuietly(Action action) { return execQuietlyUnder(this, composite(exec -> { exec.environment(getDockerEnv().getOrElse(emptyMap())); @@ -55,9 +85,21 @@ public String dockerQuietly(Action action) { }, action)); } + /** + * Connection timeout when checking the availability of remote docker servers. + * + * @return remote docker availability timeout + */ @Input public abstract Property getAvailableTimeout(); + /** + * Spec for docker 'availability' (as configured in this task). + *

+ * This method is useful when commands are optional based on the availability of docker. + * + * @return docker available {@code Spec} + */ public Spec dockerAvailable() { Property dockerAvailableProperty = getProject().getObjects().property(Boolean.class).value(getProject().provider(() -> { getDocker().finalizeValue(); @@ -80,7 +122,7 @@ public Spec dockerAvailable() { } try { - dockerQuietly(spec -> spec.args("info", "--format", "'{{.ServerVersion}}'")); + dockerQuietly(spec -> spec.args("info")); return true; } catch (ExecException e) { return false; @@ -91,6 +133,12 @@ public Spec dockerAvailable() { return unused -> dockerAvailableProperty.get(); } + /** + * Registry service used to manage login to and eventual logout from a Docker registry. + * + * @param registry docker registry details + * @return registry service for session management + */ protected DockerRegistryService getRegistryServiceFor(Registry registry) { return getProject().getGradle().getSharedServices().registerIfAbsent( "dockerRegistry" + capitalize(registry.getName()), @@ -98,13 +146,23 @@ protected DockerRegistryService getRegistryServiceFor(Registry registry) { parameters.getDocker().set(getDocker()); parameters.getRegistryUri().set(registry.getUri()); Property credentials = registry.getCredentials(); - if (credentials.isPresent()) { - parameters.getUsername().set(credentials.map(PasswordCredentials::getUsername)); - parameters.getPassword().set(credentials.map(PasswordCredentials::getPassword)); - } + parameters.getUsername().set(credentials.map(PasswordCredentials::getUsername)); + parameters.getPassword().set(credentials.map(PasswordCredentials::getPassword)); })).get(); } + /** + * Retry a task that can fail based on the provided configuration. + * + * @param description task description for logging purposes + * @param retry retry options + * @param retryable failure type to trigger retry + * @param runnable task to retry + * @return result of the final successful execution + * @param result type + * @param retry failure type + * @throws F on failure through all retries + */ protected T withRetry(String description, Registry.Retry retry, Class retryable, Retryable runnable) throws F { int retryAttempts = retry.getAttempts().get(); Function delay = retry.getDelay().get(); @@ -128,7 +186,13 @@ protected T withRetry(String description, Registry.Retr } } - interface Retryable { + /** + * A retryable, failable task. + * + * @param task result type + * @param task failure type + */ + public interface Retryable { T execute() throws F; } } diff --git a/src/main/java/org/terracotta/build/plugins/docker/Registry.java b/src/main/java/org/terracotta/build/plugins/docker/Registry.java index 10572a3..52f2797 100644 --- a/src/main/java/org/terracotta/build/plugins/docker/Registry.java +++ b/src/main/java/org/terracotta/build/plugins/docker/Registry.java @@ -12,12 +12,31 @@ public interface Registry extends Named { + /** + * Root URI for the Docker registry. + * + * @return registry root uri + */ Property getUri(); + /** + * Organization all images will be published under. + * + * @return target organization + */ Property getOrganization(); + /** + * Credentials to authenticate against the registry. + * + * @return registry authentication credential + */ Property getCredentials(); + /** + * Retry parameters to use when pushing to the registry + * @return + */ @Nested Retry getRetry(); diff --git a/src/main/java/org/terracotta/build/plugins/packaging/CustomCapabilities.java b/src/main/java/org/terracotta/build/plugins/packaging/CustomCapabilities.java index 7f95446..485e391 100644 --- a/src/main/java/org/terracotta/build/plugins/packaging/CustomCapabilities.java +++ b/src/main/java/org/terracotta/build/plugins/packaging/CustomCapabilities.java @@ -1,11 +1,24 @@ package org.terracotta.build.plugins.packaging; import org.gradle.api.DomainObjectSet; +import org.gradle.api.artifacts.ModuleDependencyCapabilitiesHandler; import org.gradle.api.capabilities.Capability; +import org.gradle.api.plugins.JavaPluginExtension; public interface CustomCapabilities { + /** + * The capabilities of this package/variant. + * + * @return the capability set + */ DomainObjectSet getCapabilities(); + /** + * Declares a capability for this package/variant. + * + * @param notation capability notation + * @see ModuleDependencyCapabilitiesHandler + */ void capability(Object notation); } diff --git a/src/main/java/org/terracotta/build/plugins/packaging/Package.java b/src/main/java/org/terracotta/build/plugins/packaging/Package.java index 660be37..a0fafeb 100644 --- a/src/main/java/org/terracotta/build/plugins/packaging/Package.java +++ b/src/main/java/org/terracotta/build/plugins/packaging/Package.java @@ -1,42 +1,107 @@ package org.terracotta.build.plugins.packaging; import org.gradle.api.Action; +import org.gradle.api.DomainObjectSet; import org.gradle.api.Named; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.DependencySet; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; +import javax.inject.Inject; + public interface Package { + /** + * Package the contributing source and register the produced JAR as a variant, à la {@link JavaPluginExtension#withSourcesJar()}. + */ void withSourcesJar(); + /** + * Generate then package Javadoc and register the produced JAR as a variant, à la {@link JavaPluginExtension#withJavadocJar()}. + *

+ * The javadoc is by default generated by executing the Javadoc tool over the merged source code of the contributing + * dependencies. + */ default void withJavadocJar() { withJavadocJar(javadoc -> {}); } + /** + * Generate then package Javadoc and register the produced JAR as a variant, à la {@link JavaPluginExtension#withJavadocJar()}. + *

+ * Javdoc generation is customized using the provided action. + */ void withJavadocJar(Action action); + /** + * The set of additional optional feature variants. + *

+ * Optional feature variants share the same primary artifact but provide additional dependencies mapped to additional + * outgoing variants, or to optional in Maven. + * + * @return the additional optional feature variants + */ NamedDomainObjectContainer getOptionalFeatures(); interface OptionalFeature extends Named, CustomCapabilities {} interface Javadoc { - SetProperty getClasspath(); + /** + * The additional dependencies required for Javadoc generation. + * + * @return the additional javadoc classpath dependencies + */ + DomainObjectSet getClasspath(); + /** + * The dependency that defines the public Javadoc for this package, or an absent value if Javadoc should be generated from merged sources. + * + * @return the optional javadoc dependency + */ Property getArtifact(); - default void classpath(Object dependency) { - classpath(dependency, d -> {}); + /** + * Add a dependency to the Javadoc classpath by parsing the given notation + * + * @param notation dependency notation + * @see org.gradle.api.artifacts.dsl.DependencyHandler + */ + default void classpath(Object notation) { + classpath(notation, d -> {}); } + /** + * Add a dependency to the Javadoc classpath by parsing the given notation + * and configuring it using the provided action. + * + * @param notation dependency notation + * @param action configuring action + * @see org.gradle.api.artifacts.dsl.DependencyHandler + */ void classpath(Object notation, Action action); + /** + * Assign a dependency to provide the Javadoc of this package by parsing the given notation. + * + * @param notation dependency notation + * @see org.gradle.api.artifacts.dsl.DependencyHandler + */ default void from(Object notation) { from(notation, d -> {}); } + /** + * Assign a dependency to provide the Javadoc of this package by parsing the given notation + * and configuring it using the provided action. + * + * @param notation dependency notation + * @param action configuring action + * @see org.gradle.api.artifacts.dsl.DependencyHandler + */ void from(Object notation, Action action); } } diff --git a/src/main/java/org/terracotta/build/plugins/packaging/PackageInternal.java b/src/main/java/org/terracotta/build/plugins/packaging/PackageInternal.java index c49fcb2..e466c07 100644 --- a/src/main/java/org/terracotta/build/plugins/packaging/PackageInternal.java +++ b/src/main/java/org/terracotta/build/plugins/packaging/PackageInternal.java @@ -9,7 +9,6 @@ import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.ConsumableConfiguration; import org.gradle.api.artifacts.Dependency; @@ -27,9 +26,7 @@ import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; -import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal; import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal; -import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyConstraint; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.jvm.internal.JvmEcosystemAttributesDetails; import org.gradle.api.plugins.jvm.internal.JvmPluginServices; @@ -38,55 +35,46 @@ import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; -import org.gradle.internal.resolve.ArtifactResolveException; import org.gradle.jvm.toolchain.JavaLanguageVersion; -import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.terracotta.build.plugins.JavaVersionPlugin; import org.terracotta.build.plugins.PackagePlugin; import javax.inject.Inject; -import java.io.File; import java.util.Collections; -import java.util.Optional; -import static org.gradle.api.artifacts.Dependency.DEFAULT_CONFIGURATION; import static org.gradle.api.attributes.DocsType.JAVADOC; import static org.gradle.api.attributes.DocsType.SOURCES; import static org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE; -import static org.gradle.api.internal.tasks.JvmConstants.JAVA_COMPONENT_NAME; import static org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP; import static org.gradle.api.plugins.JavaPlugin.API_CONFIGURATION_NAME; +import static org.gradle.api.plugins.JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.COMPILE_ONLY_API_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.JAR_TASK_NAME; import static org.gradle.api.plugins.JavaPlugin.JAVADOC_ELEMENTS_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.JAVADOC_TASK_NAME; +import static org.gradle.api.plugins.JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME; +import static org.gradle.api.plugins.JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME; import static org.gradle.api.plugins.JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME; import static org.terracotta.build.PluginUtils.capitalize; import static org.terracotta.build.Utils.coordinate; import static org.terracotta.build.Utils.mapOf; +import static org.terracotta.build.plugins.PackagePlugin.EXPLODED_JAVA_RUNTIME; +import static org.terracotta.build.plugins.PackagePlugin.PACKAGE_COMPONENT_NAME; @SuppressWarnings("UnstableApiUsage") public abstract class PackageInternal implements Package { - public static final String UNPACKAGED_JAVA_RUNTIME = "unpackaged-java-runtime"; - public static final String CONTENTS_CONFIGURATION_NAME = "contents"; public static final String CONTENTS_API_CONFIGURATION_NAME = "contentsApi"; - public static final String CONTENTS_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "contentsRuntimeClasspath"; + public static final String CONTENTS_CLASSPATH_CONFIGURATION_NAME = "contentsClasspath"; public static final String CONTENTS_SOURCES_CONFIGURATION_NAME = "contentsSources"; - public static final String CONTENTS_SOURCES_ELEMENTS_CONFIGURATION_NAME = "contentsSourcesElements"; - - public static final String PROVIDED_CONFIGURATION_NAME = "provided"; - - public static final String UNPACKAGED_API_ELEMENTS_CONFIGURATION_NAME = "unpackagedApiElements"; - public static final String UNPACKAGED_RUNTIME_ELEMENTS_CONFIGURATION_NAME = "unpackagedRuntimeElements"; - public static final String MAXIMAL_UNPACKAGED_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "maximalUnpackagedRuntimeClasspath"; + public static final String CONTENTS_SOURCES_CLASSPATH_CONFIGURATION_NAME = "contentsSourcesClasspath"; - public static final String PACKAGED_API_ELEMENTS_CONFIGURATION_NAME = "packagedApiElements"; - public static final String PACKAGED_RUNTIME_ELEMENTS_CONFIGURATION_NAME = "packagedRuntimeElements"; - public static final String PACKAGED_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "packagedRuntimeClasspath"; + public static final String EXPLODED_API_ELEMENTS_CONFIGURATION_NAME = camelPrefix("exploded", API_ELEMENTS_CONFIGURATION_NAME); + public static final String EXPLODED_RUNTIME_ELEMENTS_CONFIGURATION_NAME = camelPrefix("exploded", RUNTIME_ELEMENTS_CONFIGURATION_NAME); + public static final String EXPLODED_RUNTIME_CLASSPATH_CONFIGURATION_NAME = camelPrefix("exploded", RUNTIME_CLASSPATH_CONFIGURATION_NAME); public static final String SOURCES_TASK_NAME = "sources"; @@ -100,134 +88,26 @@ public abstract class PackageInternal implements Package { public abstract DomainObjectSet getCapabilities(); - @Override - public void withSourcesJar() { - TaskContainer tasks = getProject().getTasks(); - - TaskProvider sourcesJar = tasks.register(camelName("sourcesJar"), Jar.class, jar -> { - jar.setDescription(description("Assembles a jar archive containing {0} packaged sources.")); - jar.setGroup(BasePlugin.BUILD_GROUP); - jar.from(tasks.named(camelName(SOURCES_TASK_NAME))); - jar.from(tasks.named(camelName(JAR_TASK_NAME)), spec -> spec.include("META-INF/**", "LICENSE", "NOTICE")); - jar.getArchiveClassifier().set(kebabName("sources")); - }); - - ConfigurationContainer configurations = getProject().getConfigurations(); - Provider sourcesElements = configurations.consumable(camelName(SOURCES_ELEMENTS_CONFIGURATION_NAME), c -> { - c.setDescription(description("Sources elements for {0} packaged artifact.")); - getJvmPluginServices().configureAttributes(c, details -> details.runtimeUsage().withExternalDependencies().documentation(SOURCES)); - c.outgoing(o -> { - getCapabilities().all(o::capability); - o.artifact(sourcesJar); - }); - }); - - getProject().getComponents().named(JAVA_COMPONENT_NAME, AdhocComponentWithVariants.class, - java -> java.addVariantsFromConfiguration(sourcesElements.get(), variantDetails -> {})); - - tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(sourcesJar)); - } - - @Override - public void withJavadocJar(Action action) { - Javadoc javadocSettings = getProject().getObjects().newInstance(JavadocInternal.class); - action.execute(javadocSettings); - - ConfigurationContainerInternal configurations = (ConfigurationContainerInternal) getProject().getConfigurations(); - TaskContainer tasks = getProject().getTasks(); - - if (javadocSettings.getArtifact().isPresent()) { - Provider javadoc = configurations.dependencyScope(camelName("inheritedJavadoc"), c -> c - .setDescription(description("Dependencies for {0} inherited javadoc.")) - .getDependencies().add(javadocSettings.getArtifact().get()) - ); - - Provider javadocJars = configurations.resolvable(camelName("inheritedJavadocJars"), c -> { - c.setDescription("Inherited javadoc files for merging."); - c.extendsFrom(javadoc.get()); - getJvmPluginServices().configureAttributes(c, details -> details.documentation(JAVADOC).asJar()); - }); - - TaskProvider javadocJar = tasks.register(camelName("javadocJar"), Jar.class, jar -> { - jar.setDescription("Assembles a jar archive containing the inherited javadoc."); - jar.setGroup(BasePlugin.BUILD_GROUP); - jar.from(javadocJars.flatMap(FileCollection::getElements).map(e -> e.stream().map(getProject()::zipTree).toArray())).exclude("LICENSE"); - jar.getArchiveClassifier().set(kebabName("javadoc")); - }); - - Provider javadocElements = configurations.consumable(camelName(JAVADOC_ELEMENTS_CONFIGURATION_NAME), c -> { - getJvmPluginServices().configureAttributes(c, details -> details.runtimeUsage().withExternalDependencies().documentation(JAVADOC)); - c.outgoing(o -> { - getCapabilities().all(o::capability); - o.artifact(javadocJar); - }); - }); - - getProject().getComponents().named("java", AdhocComponentWithVariants.class, - java -> java.addVariantsFromConfiguration(javadocElements.get(), variantDetails -> {})); - - tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(javadocJar)); - - } else { - Provider additionalJavadoc = configurations.dependencyScope(camelName("additionalJavadoc"), c -> { - c.setDescription("Additional javadoc generation dependencies."); - javadocSettings.getClasspath().get().forEach(notation -> c.getDependencies().add(notation)); - }); - - Provider javadocClasspath = configurations.resolvable(camelName("javadocClasspath"), c -> { - c.setDescription(description("Classpath for {0} javadoc generation.")); - c.extendsFrom(additionalJavadoc.get(), configurations.getByName(camelName(CONTENTS_CONFIGURATION_NAME))); - getJvmPluginServices().configureAsRuntimeClasspath(c); - }); - - TaskProvider javadoc = tasks.register(camelName(JAVADOC_TASK_NAME), org.gradle.api.tasks.javadoc.Javadoc.class, task -> { - task.setDescription("Generates Javadoc API documentation for {0} packaged source code."); - task.setGroup(DOCUMENTATION_GROUP); - task.setTitle(getProject().getName() + " " + getProject().getVersion() + " API"); - task.source(tasks.named(camelName(SOURCES_TASK_NAME))); - task.include("**/*.java"); - task.getModularity().getInferModulePath().set(false); - task.setClasspath(javadocClasspath.get()); - task.setDestinationDir(getProject().getLayout().getBuildDirectory().dir(kebabName(JAVADOC_TASK_NAME)).get().getAsFile()); - }); - TaskProvider javadocJar = tasks.register(camelName("javadocJar"), Jar.class, jar -> { - jar.setDescription(description("Assembles a jar archive containing {0} packaged javadoc.")); - jar.setGroup(BasePlugin.BUILD_GROUP); - jar.from(javadoc); - jar.getArchiveClassifier().set(kebabName("javadoc")); - }); - - Provider javadocElements = configurations.consumable(camelName(JAVADOC_ELEMENTS_CONFIGURATION_NAME), c -> { - c.setDescription(description("Javadoc elements for {0} packaged artifact.")); - getJvmPluginServices().configureAttributes(c, details -> details.runtimeUsage().withExternalDependencies().documentation(JAVADOC)); - c.outgoing(o -> { - getCapabilities().all(o::capability); - o.artifact(javadocJar); - }); - }); - - getProject().getComponents().named("java", AdhocComponentWithVariants.class, - java -> java.addVariantsFromConfiguration(javadocElements.get(), variantDetails -> {})); - - tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(javadocJar)); - } - } - @SuppressWarnings("UnstableApiUsage") public void create() { - ConfigurationContainerInternal configurations = (ConfigurationContainerInternal) getProject().getConfigurations(); + ConfigurationContainer configurations = getProject().getConfigurations(); DependencyHandler dependencies = getProject().getDependencies(); TaskContainer tasks = getProject().getTasks(); Provider javaCompileVersion = getProject().getExtensions().getByType(JavaVersionPlugin.JavaVersions.class).getCompileVersion().map(JavaLanguageVersion::asInt); - Provider commonContentsApi = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, CONTENTS_API_CONFIGURATION_NAME)); - Provider commonContents = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, CONTENTS_CONFIGURATION_NAME)); - Provider commonApi = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, API_CONFIGURATION_NAME)); - Provider commonImplementation = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, IMPLEMENTATION_CONFIGURATION_NAME)); - Provider commonCompileOnlyApi = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, COMPILE_ONLY_API_CONFIGURATION_NAME)); - Provider commonRuntimeOnly = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, RUNTIME_ONLY_CONFIGURATION_NAME)); - Provider commonProvided = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, PROVIDED_CONFIGURATION_NAME)); + /* + * Retrieve the common dependency scopes that all packages inherit from + */ + Provider commonContentsApi = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, CONTENTS_API_CONFIGURATION_NAME), DependencyScopeConfiguration.class); + Provider commonContents = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, CONTENTS_CONFIGURATION_NAME), DependencyScopeConfiguration.class); + Provider commonApi = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, API_CONFIGURATION_NAME), DependencyScopeConfiguration.class); + Provider commonImplementation = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, IMPLEMENTATION_CONFIGURATION_NAME), DependencyScopeConfiguration.class); + Provider commonCompileOnlyApi = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, COMPILE_ONLY_API_CONFIGURATION_NAME), DependencyScopeConfiguration.class); + Provider commonRuntimeOnly = configurations.named(camelPrefix(PackagePlugin.COMMON_PREFIX, RUNTIME_ONLY_CONFIGURATION_NAME), DependencyScopeConfiguration.class); + /* + * Register the `contentsApi` and `contents` dependency scopes and wire them to the relative common scopes. + */ Provider contentsApi = configurations.dependencyScope(camelName(CONTENTS_API_CONFIGURATION_NAME), c -> c .extendsFrom(commonContentsApi.get()) .setDescription(description("API dependencies for {0} package contents."))); @@ -236,9 +116,12 @@ public void create() { .setDescription(description("Implementation dependencies for {0} package contents.")) ); - NamedDomainObjectProvider contentsRuntimeClasspath = configurations.resolvable(camelName(CONTENTS_RUNTIME_CLASSPATH_CONFIGURATION_NAME), c -> { + /* + * Register the contentsClasspath. Once fully configured this will be the input to the `ShadowJar` task. + */ + NamedDomainObjectProvider contentsClasspath = configurations.resolvable(camelName(CONTENTS_CLASSPATH_CONFIGURATION_NAME), c -> { c.extendsFrom(contents.get()); - c.setDescription(description("Runtime classpath of {0} package contents.")); + c.setDescription(description("Classpath of {0} package contents.")); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); getJvmPluginServices().configureAsRuntimeClasspath(c); }); @@ -246,11 +129,11 @@ public void create() { /* * The variant metadata rules are not complex enough, nor applied uniformly enough to give us the "transient sources" * configuration that we need. Instead, we populate the contentSourcesElements configuration using the resolved - * artifacts of the shadow contents configuration. + * artifacts of the `contentsClasspath` contents configuration. */ Provider contentsSources = configurations.dependencyScope(camelName(CONTENTS_SOURCES_CONFIGURATION_NAME), c -> c .setVisible(false) - .withDependencies(config -> contentsRuntimeClasspath.get().getIncoming().artifactView(view -> {}).getArtifacts() + .withDependencies(config -> contentsClasspath.get().getIncoming().artifactView(view -> {}).getArtifacts() .getResolvedArtifacts().get().stream().map(ResolvedArtifactResult::getVariant).forEach(variant -> { ComponentIdentifier id = variant.getOwner(); Dependency dependency; @@ -268,36 +151,42 @@ public void create() { }) ) ); - Provider contentsSourcesElements = configurations.resolvable(camelName(CONTENTS_SOURCES_ELEMENTS_CONFIGURATION_NAME), c -> { + Provider contentsSourcesClasspath = configurations.resolvable(camelName(CONTENTS_SOURCES_CLASSPATH_CONFIGURATION_NAME), c -> { c.setDescription(description("Source artifacts for {0} package contents.")); c.extendsFrom(contentsSources.get()); getJvmPluginServices().configureAttributes(c, details -> details.documentation(SOURCES)); c.resolutionStrategy(resolutionStrategy -> ((ResolutionStrategyInternal) resolutionStrategy).assumeFluidDependencies()); }); + /* + * Form a virtual uber-sources file-tree. This is just files from the (lenient) resolution of the `contentsSourcesClasspath` + * configuration mapped to individual file-trees and then summed together. + */ + Provider sourcesTree = contentsSourcesClasspath.flatMap(c -> c.getIncoming().artifactView(view -> view.lenient(true)) + .getFiles().getElements().map(files -> files.stream().map(file -> { + if (file.getAsFile().isFile()) { + return getProject().zipTree(file); + } else { + return getProject().fileTree(file); + } + }).reduce(FileTree::plus).orElse(getProject().files().getAsFileTree()))); - Provider sourcesTree = getProject().provider(() -> contentsSourcesElements.get().getResolvedConfiguration().getLenientConfiguration().getAllModuleDependencies().stream().flatMap(d -> d.getModuleArtifacts().stream()).map(artifact -> { - try { - return Optional.of(artifact.getFile()); - } catch (ArtifactResolveException e) { - return Optional.empty(); - } - }).filter(Optional::isPresent).map(Optional::get).distinct().map(file -> { - if (file.isFile()) { - return getProject().zipTree(file); - } else { - return getProject().fileTree(file); - } - }).reduce(FileTree::plus).orElse(getProject().files().getAsFileTree())); - + /* + * Realize the virtual uber-sources file tree in to the build directory. We could eliminate this step and feed the + * other tasks directly from the composite... but that will likely just realize the tree in to a temp location anyway. + * Easier to just have it where we can easily poke at it ourselves. + */ TaskProvider sources = tasks.register(camelName(SOURCES_TASK_NAME), Sync.class, sync -> { sync.setDescription(description("Collects the sources contributing to {0} packaged artifact.")); sync.setGroup(DOCUMENTATION_GROUP); - sync.dependsOn(contentsSourcesElements); sync.from(sourcesTree, spec -> spec.exclude("META-INF/**")); sync.into(getProject().getLayout().getBuildDirectory().dir(kebabName("sources"))); }); + /* + * Register the 'user-visible' dependency scopes. Dependencies in these scopes (and their transitive graphs) + * will not be packaged, but will instead be exposed as dependencies of the final package. + */ Provider api = configurations.dependencyScope(camelName(API_CONFIGURATION_NAME), c -> c.extendsFrom(commonApi.get()).setDescription(description("API dependencies for {0} packaged artifact."))); NamedDomainObjectProvider implementation = configurations.dependencyScope(camelName(IMPLEMENTATION_CONFIGURATION_NAME), @@ -306,52 +195,103 @@ public void create() { c -> c.extendsFrom(commonCompileOnlyApi.get()).setDescription(description("Compile-only API dependencies for {0} packaged artifact."))); Provider runtimeOnly = configurations.dependencyScope(camelName(RUNTIME_ONLY_CONFIGURATION_NAME), c -> c.extendsFrom(commonRuntimeOnly.get()).setDescription(description("Runtime-only dependencies for {0} packaged artifact."))); - Provider provided = configurations.dependencyScope(camelName(PROVIDED_CONFIGURATION_NAME), - c -> c.extendsFrom(commonProvided.get()).setDescription(description("'Provided' API dependencies for {0} packaged artifact."))); - configurations.consumable(camelName(UNPACKAGED_API_ELEMENTS_CONFIGURATION_NAME), c -> { + /* + * Consumable API configuration with 'exploded' external dependencies. This configuration gets selected by dependent + * local projects for use at compile time. This means projects that depend on the packaged artifact can be safely + * compiled using these elements before the actual jar is assembled. + */ + configurations.consumable(camelName(EXPLODED_API_ELEMENTS_CONFIGURATION_NAME), c -> { c.setDescription(description("API elements for {0} unpackaged contents.")); c.extendsFrom(api.get(), compileOnlyApi.get(), contentsApi.get()); - getJvmPluginServices().configureAttributes(c, details -> details.apiUsage().library().asJar().withExternalDependencies()); + getJvmPluginServices().configureAsApiElements(c); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); c.outgoing(o -> getCapabilities().all(o::capability)); }); - configurations.consumable(camelName(UNPACKAGED_RUNTIME_ELEMENTS_CONFIGURATION_NAME), c -> { + Usage explodedJavaRuntime = getProject().getObjects().named(Usage.class, EXPLODED_JAVA_RUNTIME); + + /* + * Consumable runtime configuration with 'exploded' external dependencies. This configuration gets selected by + * dependent local projects for use at runtime. This is used to ensure that when `project-a` depends on `project-b` + * we don't package in to `project-a` things that are already packaged in `project-b`. + */ + configurations.consumable(camelName(EXPLODED_RUNTIME_ELEMENTS_CONFIGURATION_NAME), c -> { c.setDescription(description("Runtime elements for {0} unpackaged contents.")); c.extendsFrom(implementation.get(), runtimeOnly.get(), contents.get()); - getJvmPluginServices().configureAttributes(c, details -> details.library().asJar().withExternalDependencies()); + getJvmPluginServices().configureAsRuntimeElements(c); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion) - .attribute(Usage.USAGE_ATTRIBUTE, getProject().getObjects().named(Usage.class, UNPACKAGED_JAVA_RUNTIME))); + .attribute(Usage.USAGE_ATTRIBUTE, explodedJavaRuntime)); c.outgoing(o -> getCapabilities().all(o::capability)); }); - Provider maximalUnpackagedRuntimeClasspath = - configurations.resolvable(camelName(MAXIMAL_UNPACKAGED_RUNTIME_CLASSPATH_CONFIGURATION_NAME), c -> { - c.setDescription(description("Maximal (incl all optional features) runtime classpath of {0} unpackaged contents.")); + /* + * Resolvable runtime configuration with 'exploded' external dependencies. This configuration requests the + * `exploded-java-runtime` usage attribute, which selects the explodedRuntimeElements consumable configuration when + * it is available. When it is not available the `ExplodedJavaRuntimeCompatibility` attribute compatibility rule + * allows fallback to the conventional `runtimeElements` configuration. + */ + Provider explodedRuntimeClasspath = + configurations.resolvable(camelName(EXPLODED_RUNTIME_CLASSPATH_CONFIGURATION_NAME), c -> { + c.setDescription(description("Runtime classpath of {0} unpackaged contents.")); c.extendsFrom(implementation.get(), runtimeOnly.get()); - getJvmPluginServices().configureAttributes(c, details -> details.withExternalDependencies()); + getJvmPluginServices().configureAsRuntimeClasspath(c); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion) - .attribute(Usage.USAGE_ATTRIBUTE, getProject().getObjects().named(Usage.class, UNPACKAGED_JAVA_RUNTIME))); + .attribute(Usage.USAGE_ATTRIBUTE, explodedJavaRuntime)); }); + /* + * Exclude things on the exploded runtime classpath from the package contents. + */ + contentsClasspath.configure(c -> c.getIncoming().beforeResolve(config -> { + explodedRuntimeClasspath.get().getResolvedConfiguration().getResolvedArtifacts().forEach(resolvedArtifact -> { + ModuleVersionIdentifier identifier = resolvedArtifact.getModuleVersion().getId(); + c.exclude(mapOf(String.class, String.class, "group", identifier.getGroup(), "module", identifier.getName())); + }); + })); + + /* + * Resolvable runtime configuration with default (potentially embedded) dependencies. + */ + Provider runtimeClasspath = configurations.resolvable(camelName(RUNTIME_CLASSPATH_CONFIGURATION_NAME), c -> { + c.setDescription(description("Runtime classpath of {0} packaged artifact.")); + c.extendsFrom(implementation.get(), runtimeOnly.get()); + getJvmPluginServices().configureAsRuntimeClasspath(c); + getJvmPluginServices().configureAttributes(c, JvmEcosystemAttributesDetails::withEmbeddedDependencies); + c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); + }); + + + /* + * Create the main packaging task. Shading the contentsClasspath resolution, and generating an associated OSGi manifest + */ TaskProvider shadowJar = tasks.register(camelName(JAR_TASK_NAME), ShadowJar.class, shadow -> { shadow.setDescription(description("Assembles a jar archive containing {0} packaged classes.")); shadow.setGroup(BasePlugin.BUILD_GROUP); - shadow.setConfigurations(Collections.singletonList(contentsRuntimeClasspath.get())); + shadow.setConfigurations(Collections.singletonList(contentsClasspath.get())); shadow.getArchiveClassifier().set(kebabName("")); shadow.mergeServiceFiles(); shadow.exclude("META-INF/MANIFEST.MF"); + + shadow.getExtensions().configure(OsgiManifestJarExtension.class, osgi -> { + osgi.getClasspath().from(runtimeClasspath); + osgi.getSources().from(sources); + + osgi.instruction(Constants.BUNDLE_VERSION, new MavenVersion(getProject().getVersion().toString()).getOSGiVersion().toString()); + }); }); - configurations.named(DEFAULT_CONFIGURATION).configure(c -> c.outgoing(o -> o.artifact(shadowJar))); - Provider packagedApiElements = configurations.consumable(camelName(PACKAGED_API_ELEMENTS_CONFIGURATION_NAME), c -> { + /* + * Register the normal `apiElements` configuration, composed of the api dependency scopes, along with the packaged jar, and marked as embedded. + */ + Provider apiElements = configurations.consumable(camelName(API_ELEMENTS_CONFIGURATION_NAME), c -> { c.setDescription(description("API elements for {0} packaged artifact.")); c.extendsFrom(api.get(), compileOnlyApi.get()); - getJvmPluginServices().configureAttributes(c, details -> details.apiUsage().library().asJar().withEmbeddedDependencies()); + getJvmPluginServices().configureAsApiElements(c); + getJvmPluginServices().configureAttributes(c, JvmEcosystemAttributesDetails::withEmbeddedDependencies); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); c.outgoing(o -> { getCapabilities().all(o::capability); @@ -359,59 +299,40 @@ public void create() { }); }); - Provider packagedRuntimeElements = configurations.consumable(camelName(PACKAGED_RUNTIME_ELEMENTS_CONFIGURATION_NAME), c -> { + /* + * Register the normal `runtimeElements` configuration, composed of the api dependency scopes, along with the packaged jar, and marked as embedded. + */ + Provider runtimeElements = configurations.consumable(camelName(RUNTIME_ELEMENTS_CONFIGURATION_NAME), c -> { c.setDescription(description("Runtime elements for {0} packaged artifact.")); c.extendsFrom(implementation.get(), runtimeOnly.get()); - getJvmPluginServices().configureAttributes(c, details -> details.withEmbeddedDependencies().runtimeUsage().library().asJar()); + getJvmPluginServices().configureAsRuntimeElements(c); + getJvmPluginServices().configureAttributes(c, JvmEcosystemAttributesDetails::withEmbeddedDependencies); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); - c.outgoing(o -> { getCapabilities().all(o::capability); o.artifact(shadowJar); }); }); - Provider packagedRuntimeClasspath = configurations.resolvable(camelName(PACKAGED_RUNTIME_CLASSPATH_CONFIGURATION_NAME), c -> { - c.setDescription(description("Runtime classpath of {0} packaged artifact.")); - c.extendsFrom(implementation.get(), runtimeOnly.get()); - getJvmPluginServices().configureAttributes(c, details -> details.withEmbeddedDependencies().runtimeUsage().library().asJar()); - c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); - }); - - - contentsRuntimeClasspath.configure(c -> c.getIncoming().beforeResolve(config -> { - maximalUnpackagedRuntimeClasspath.get().getResolvedConfiguration().getResolvedArtifacts().forEach(resolvedArtifact -> { - ModuleVersionIdentifier identifier = resolvedArtifact.getModuleVersion().getId(); - c.exclude(mapOf(String.class, String.class, "group", identifier.getGroup(), "module", identifier.getName())); - }); - })); - - shadowJar.configure(shadow -> { - OsgiManifestJarExtension osgi = shadow.getExtensions().findByType(OsgiManifestJarExtension.class); - osgi.getClasspath().from(packagedRuntimeClasspath); - osgi.getSources().from(sources); - - osgi.instruction(Constants.BUNDLE_VERSION, new MavenVersion(getProject().getVersion().toString()).getOSGiVersion().toString()); - }); - - getProject().getComponents().named("java", AdhocComponentWithVariants.class, java -> { - java.addVariantsFromConfiguration(packagedApiElements.get(), variantDetails -> variantDetails.mapToMavenScope("compile")); - java.addVariantsFromConfiguration(packagedRuntimeElements.get(), variantDetails -> variantDetails.mapToMavenScope("runtime")); + /* + * Tie the 'normal' consumable configurations to the `java` software component so that they can be published. + */ + getProject().getComponents().named(PACKAGE_COMPONENT_NAME, AdhocComponentWithVariants.class, java -> { + java.addVariantsFromConfiguration(apiElements.get(), variantDetails -> variantDetails.mapToMavenScope("compile")); + java.addVariantsFromConfiguration(runtimeElements.get(), variantDetails -> variantDetails.mapToMavenScope("runtime")); }); - tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(shadowJar)); - - implementation.configure(c -> provided.get().getDependencies().configureEach(dependency -> - c.getDependencyConstraints().add(DefaultDependencyConstraint.strictly(dependency.getGroup(), dependency.getName(), dependency.getVersion())))); - getOptionalFeatures().all(this::createOptionalFeature); } - public void createOptionalFeature(OptionalFeatureInternal feature) { + private void createOptionalFeature(OptionalFeatureInternal feature) { String featureName = feature.getName(); - ConfigurationContainerInternal configurations = (ConfigurationContainerInternal) getProject().getConfigurations(); + ConfigurationContainer configurations = getProject().getConfigurations(); + /* + * Register the 'user-visible' dependency scopes for the optional feature bucket + */ Provider api = configurations.dependencyScope(camelName(camelPrefix(featureName, API_CONFIGURATION_NAME)), c -> c.setDescription(description("API dependencies for {0} packaged artifact '" + featureName + "' feature."))); Provider implementation = configurations.dependencyScope(camelName(camelPrefix(featureName, IMPLEMENTATION_CONFIGURATION_NAME)), @@ -422,14 +343,13 @@ public void createOptionalFeature(OptionalFeatureInternal feature) { c -> c.setDescription(description("Runtime-only dependencies for {0} packaged artifact '" + featureName + "' feature."))); - configurations.named(camelName(MAXIMAL_UNPACKAGED_RUNTIME_CLASSPATH_CONFIGURATION_NAME)).configure(c -> c.extendsFrom(implementation.get(), runtimeOnly.get())); - Provider javaCompileVersion = getProject().getExtensions().getByType(JavaVersionPlugin.JavaVersions.class).getCompileVersion().map(JavaLanguageVersion::asInt); - Provider packagedApiElements = configurations.consumable(camelName(camelPrefix(featureName, PACKAGED_API_ELEMENTS_CONFIGURATION_NAME)), c -> { + Provider apiElements = configurations.consumable(camelName(camelPrefix(featureName, API_ELEMENTS_CONFIGURATION_NAME)), c -> { c.setDescription(description("API elements for {0} packaged artifact '" + featureName + "' feature.")); c.extendsFrom(api.get(), compileOnlyApi.get()); - getJvmPluginServices().configureAttributes(c, details -> details.apiUsage().library().asJar().withEmbeddedDependencies()); + getJvmPluginServices().configureAsApiElements(c); + getJvmPluginServices().configureAttributes(c, JvmEcosystemAttributesDetails::withEmbeddedDependencies); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); c.outgoing(o -> { feature.getCapabilities().all(o::capability); @@ -437,10 +357,11 @@ public void createOptionalFeature(OptionalFeatureInternal feature) { }); }); - Provider packagedRuntimeElements = configurations.consumable(camelName(camelPrefix(featureName, PACKAGED_RUNTIME_ELEMENTS_CONFIGURATION_NAME)), c -> { + Provider runtimeElements = configurations.consumable(camelName(camelPrefix(featureName, RUNTIME_ELEMENTS_CONFIGURATION_NAME)), c -> { c.setDescription(description("Runtime elements for {0} packaged artifact '" + featureName + "' feature variant.")); c.extendsFrom(implementation.get(), runtimeOnly.get()); - getJvmPluginServices().configureAttributes(c, details -> details.withEmbeddedDependencies().runtimeUsage().library().asJar()); + getJvmPluginServices().configureAsRuntimeElements(c); + getJvmPluginServices().configureAttributes(c, JvmEcosystemAttributesDetails::withEmbeddedDependencies); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); c.outgoing(outgoing -> { feature.getCapabilities().all(outgoing::capability); @@ -448,32 +369,159 @@ public void createOptionalFeature(OptionalFeatureInternal feature) { }); }); - getProject().getComponents().named(JAVA_COMPONENT_NAME, AdhocComponentWithVariants.class, java -> { - java.addVariantsFromConfiguration(packagedApiElements.get(), variantDetails -> { + getProject().getComponents().named(PACKAGE_COMPONENT_NAME, AdhocComponentWithVariants.class, java -> { + java.addVariantsFromConfiguration(apiElements.get(), variantDetails -> { variantDetails.mapToMavenScope("compile"); variantDetails.mapToOptional(); }); - java.addVariantsFromConfiguration(packagedRuntimeElements.get(), variantDetails -> { + java.addVariantsFromConfiguration(runtimeElements.get(), variantDetails -> { variantDetails.mapToMavenScope("runtime"); variantDetails.mapToOptional(); }); }); - configurations.consumable(camelName(camelPrefix(featureName, UNPACKAGED_API_ELEMENTS_CONFIGURATION_NAME)), c -> { + configurations.consumable(camelName(camelPrefix(featureName, EXPLODED_API_ELEMENTS_CONFIGURATION_NAME)), c -> { c.setDescription(description("API elements for {0} unpackaged '" + featureName + "' feature contents.")); c.extendsFrom(api.get(), compileOnlyApi.get()); - getJvmPluginServices().configureAttributes(c, details -> details.withExternalDependencies().apiUsage()); + getJvmPluginServices().configureAsApiElements(c); + getJvmPluginServices().configureAttributes(c, JvmEcosystemAttributesDetails::withExternalDependencies); c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion)); c.outgoing(out -> feature.getCapabilities().all(out::capability)); }); - configurations.consumable(camelName(camelPrefix(featureName, UNPACKAGED_RUNTIME_ELEMENTS_CONFIGURATION_NAME)), c -> { + Usage explodedJavaRuntime = getProject().getObjects().named(Usage.class, EXPLODED_JAVA_RUNTIME); + + configurations.consumable(camelName(camelPrefix(featureName, EXPLODED_RUNTIME_ELEMENTS_CONFIGURATION_NAME)), c -> { c.setDescription(description("Runtime elements for {0} unpackaged '" + featureName + "' feature contents.")); c.extendsFrom(implementation.get(), runtimeOnly.get()); + getJvmPluginServices().configureAsRuntimeElements(c); getJvmPluginServices().configureAttributes(c, JvmEcosystemAttributesDetails::withExternalDependencies); - c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion).attribute(Usage.USAGE_ATTRIBUTE, getProject().getObjects().named(Usage.class, UNPACKAGED_JAVA_RUNTIME))); + c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion).attribute(Usage.USAGE_ATTRIBUTE, explodedJavaRuntime)); c.outgoing(o -> feature.getCapabilities().all(o::capability)); }); + + Provider explodedRuntimeClasspath = + configurations.resolvable(camelName(camelPrefix(featureName, EXPLODED_RUNTIME_CLASSPATH_CONFIGURATION_NAME)), c -> { + c.setDescription(description("Runtime classpath of {0} unpackaged '" + featureName + "' contents.")); + c.extendsFrom(implementation.get(), runtimeOnly.get()); + getJvmPluginServices().configureAsRuntimeClasspath(c); + c.attributes(attr -> attr.attributeProvider(TARGET_JVM_VERSION_ATTRIBUTE, javaCompileVersion) + .attribute(Usage.USAGE_ATTRIBUTE, explodedJavaRuntime)); + }); + + + configurations.named(camelName(CONTENTS_CLASSPATH_CONFIGURATION_NAME), ResolvableConfiguration.class).configure(c -> c.getIncoming().beforeResolve(config -> { + explodedRuntimeClasspath.get().getResolvedConfiguration().getResolvedArtifacts().forEach(resolvedArtifact -> { + ModuleVersionIdentifier identifier = resolvedArtifact.getModuleVersion().getId(); + c.exclude(mapOf(String.class, String.class, "group", identifier.getGroup(), "module", identifier.getName())); + }); + })); + } + + @Override + public void withSourcesJar() { + TaskContainer tasks = getProject().getTasks(); + + TaskProvider sourcesJar = tasks.register(camelName("sourcesJar"), Jar.class, jar -> { + jar.setDescription(description("Assembles a jar archive containing {0} packaged sources.")); + jar.setGroup(BasePlugin.BUILD_GROUP); + jar.from(tasks.named(camelName(SOURCES_TASK_NAME))); + jar.from(tasks.named(camelName(JAR_TASK_NAME)), spec -> spec.include("META-INF/**", "LICENSE", "NOTICE")); + jar.getArchiveClassifier().set(kebabName("sources")); + }); + + ConfigurationContainer configurations = getProject().getConfigurations(); + Provider sourcesElements = configurations.consumable(camelName(SOURCES_ELEMENTS_CONFIGURATION_NAME), c -> { + c.setDescription(description("Sources elements for {0} packaged artifact.")); + getJvmPluginServices().configureAttributes(c, details -> details.runtimeUsage().withExternalDependencies().documentation(SOURCES)); + c.outgoing(o -> { + getCapabilities().all(o::capability); + o.artifact(sourcesJar); + }); + }); + + getProject().getComponents().named(PACKAGE_COMPONENT_NAME, AdhocComponentWithVariants.class, + java -> java.addVariantsFromConfiguration(sourcesElements.get(), variantDetails -> {})); + } + + @Override + public void withJavadocJar(Action action) { + Javadoc javadocSettings = getProject().getObjects().newInstance(JavadocInternal.class); + action.execute(javadocSettings); + + ConfigurationContainer configurations = getProject().getConfigurations(); + TaskContainer tasks = getProject().getTasks(); + + if (javadocSettings.getArtifact().isPresent()) { + Provider javadoc = configurations.dependencyScope(camelName("inheritedJavadoc"), c -> c + .setDescription(description("Dependencies for {0} inherited javadoc.")) + .getDependencies().add(javadocSettings.getArtifact().get()) + ); + + Provider javadocJars = configurations.resolvable(camelName("inheritedJavadocJars"), c -> { + c.setDescription("Inherited javadoc files for merging."); + c.extendsFrom(javadoc.get()); + getJvmPluginServices().configureAttributes(c, details -> details.documentation(JAVADOC).asJar()); + }); + + TaskProvider javadocJar = tasks.register(camelName("javadocJar"), Jar.class, jar -> { + jar.setDescription("Assembles a jar archive containing the inherited javadoc."); + jar.setGroup(BasePlugin.BUILD_GROUP); + jar.from(javadocJars.flatMap(FileCollection::getElements).map(e -> e.stream().map(getProject()::zipTree).toArray())).exclude("LICENSE"); + jar.getArchiveClassifier().set(kebabName("javadoc")); + }); + + Provider javadocElements = configurations.consumable(camelName(JAVADOC_ELEMENTS_CONFIGURATION_NAME), c -> { + getJvmPluginServices().configureAttributes(c, details -> details.runtimeUsage().withExternalDependencies().documentation(JAVADOC)); + c.outgoing(o -> { + getCapabilities().all(o::capability); + o.artifact(javadocJar); + }); + }); + + getProject().getComponents().named(PACKAGE_COMPONENT_NAME, AdhocComponentWithVariants.class, + java -> java.addVariantsFromConfiguration(javadocElements.get(), variantDetails -> {})); + } else { + Provider additionalJavadoc = configurations.dependencyScope(camelName("additionalJavadoc"), c -> { + c.setDescription("Additional javadoc generation dependencies."); + javadocSettings.getClasspath().all(notation -> c.getDependencies().add(notation)); + }); + + Provider javadocClasspath = configurations.resolvable(camelName("javadocClasspath"), c -> { + c.setDescription(description("Classpath for {0} javadoc generation.")); + c.extendsFrom(additionalJavadoc.get(), configurations.getByName(camelName(CONTENTS_CONFIGURATION_NAME))); + getJvmPluginServices().configureAsRuntimeClasspath(c); + }); + + TaskProvider javadoc = tasks.register(camelName(JAVADOC_TASK_NAME), org.gradle.api.tasks.javadoc.Javadoc.class, task -> { + task.setDescription("Generates Javadoc API documentation for {0} packaged source code."); + task.setGroup(DOCUMENTATION_GROUP); + task.setTitle(getProject().getName() + " " + getProject().getVersion() + " API"); + task.source(tasks.named(camelName(SOURCES_TASK_NAME))); + task.include("**/*.java"); + task.getModularity().getInferModulePath().set(false); + task.setClasspath(javadocClasspath.get()); + task.setDestinationDir(getProject().getLayout().getBuildDirectory().dir(kebabName(JAVADOC_TASK_NAME)).get().getAsFile()); + }); + TaskProvider javadocJar = tasks.register(camelName("javadocJar"), Jar.class, jar -> { + jar.setDescription(description("Assembles a jar archive containing {0} packaged javadoc.")); + jar.setGroup(BasePlugin.BUILD_GROUP); + jar.from(javadoc); + jar.getArchiveClassifier().set(kebabName("javadoc")); + }); + + Provider javadocElements = configurations.consumable(camelName(JAVADOC_ELEMENTS_CONFIGURATION_NAME), c -> { + c.setDescription(description("Javadoc elements for {0} packaged artifact.")); + getJvmPluginServices().configureAttributes(c, details -> details.runtimeUsage().withExternalDependencies().documentation(JAVADOC)); + c.outgoing(o -> { + getCapabilities().all(o::capability); + o.artifact(javadocJar); + }); + }); + + getProject().getComponents().named(PACKAGE_COMPONENT_NAME, AdhocComponentWithVariants.class, + java -> java.addVariantsFromConfiguration(javadocElements.get(), variantDetails -> {})); + } } protected abstract String camelName(String base); @@ -510,5 +558,5 @@ default void from(Object notation, Action action) { } } - interface OptionalFeatureInternal extends OptionalFeature, CustomCapabilitiesInternal {} + public interface OptionalFeatureInternal extends OptionalFeature, CustomCapabilitiesInternal {} } diff --git a/src/main/java/org/terracotta/build/plugins/packaging/PackagingExtension.java b/src/main/java/org/terracotta/build/plugins/packaging/PackagingExtension.java index 28e891c..7445e4c 100644 --- a/src/main/java/org/terracotta/build/plugins/packaging/PackagingExtension.java +++ b/src/main/java/org/terracotta/build/plugins/packaging/PackagingExtension.java @@ -4,5 +4,10 @@ public interface PackagingExtension extends Package { + /** + * The set of named variant packages. + * + * @return named variant packages + */ NamedDomainObjectContainer getVariants(); }