/* * Copyright 2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gradle.jvm.plugins; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import org.gradle.api.Action; import org.gradle.api.Incubating; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.Transformer; import org.gradle.internal.jvm.Jvm; import org.gradle.internal.service.ServiceRegistry; import org.gradle.jvm.JarBinarySpec; import org.gradle.jvm.JvmBinarySpec; import org.gradle.jvm.JvmLibrarySpec; import org.gradle.jvm.internal.DefaultJarBinarySpec; import org.gradle.jvm.internal.DefaultJvmBinarySpec; import org.gradle.jvm.internal.DefaultJvmLibrarySpec; import org.gradle.jvm.internal.JarBinarySpecInternal; import org.gradle.jvm.internal.JarFile; import org.gradle.jvm.internal.JavaPlatformResolver; import org.gradle.jvm.internal.JvmAssembly; import org.gradle.jvm.internal.JvmBinarySpecInternal; import org.gradle.jvm.internal.JvmLibrarySpecInternal; import org.gradle.jvm.internal.toolchain.JavaToolChainInternal; import org.gradle.jvm.platform.JavaPlatform; import org.gradle.jvm.platform.internal.DefaultJavaPlatform; import org.gradle.jvm.tasks.Jar; import org.gradle.jvm.tasks.api.ApiJar; import org.gradle.jvm.toolchain.JavaToolChainRegistry; import org.gradle.jvm.toolchain.LocalJava; import org.gradle.jvm.toolchain.internal.DefaultJavaToolChainRegistry; import org.gradle.jvm.toolchain.internal.InstalledJdk; import org.gradle.jvm.toolchain.internal.InstalledJdkInternal; import org.gradle.jvm.toolchain.internal.InstalledJre; import org.gradle.jvm.toolchain.internal.JavaInstallationProbe; import org.gradle.jvm.toolchain.internal.LocalJavaInstallation; import org.gradle.language.base.internal.ProjectLayout; import org.gradle.model.Defaults; import org.gradle.model.Each; import org.gradle.model.Model; import org.gradle.model.ModelMap; import org.gradle.model.Mutate; import org.gradle.model.Path; import org.gradle.model.RuleSource; import org.gradle.model.internal.core.Hidden; import org.gradle.platform.base.BinaryTasks; import org.gradle.platform.base.ComponentBinaries; import org.gradle.platform.base.ComponentType; import org.gradle.platform.base.DependencySpec; import org.gradle.platform.base.InvalidModelException; import org.gradle.platform.base.TypeBuilder; import org.gradle.platform.base.internal.BinaryNamingScheme; import org.gradle.platform.base.internal.DefaultBinaryNamingScheme; import org.gradle.platform.base.internal.DefaultPlatformRequirement; import org.gradle.platform.base.internal.PlatformRequirement; import org.gradle.platform.base.internal.PlatformResolvers; import org.gradle.util.CollectionUtils; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import static org.apache.commons.lang.StringUtils.capitalize; /** * Base plugin for JVM component support. Applies the {@link org.gradle.language.base.plugins.ComponentModelBasePlugin}. Registers the {@link JvmLibrarySpec} library type for the components * container. */ @Incubating public class JvmComponentPlugin implements Plugin<Project> { @Override public void apply(Project project) { } @SuppressWarnings("UnusedDeclaration") static class Rules extends RuleSource { @ComponentType public void register(TypeBuilder<JvmLibrarySpec> builder) { builder.defaultImplementation(DefaultJvmLibrarySpec.class); builder.internalView(JvmLibrarySpecInternal.class); } @ComponentType public void registerJvmBinarySpec(TypeBuilder<JvmBinarySpec> builder) { builder.defaultImplementation(DefaultJvmBinarySpec.class); builder.internalView(JvmBinarySpecInternal.class); } @ComponentType public void registerJarBinarySpec(TypeBuilder<JarBinarySpec> builder) { builder.defaultImplementation(DefaultJarBinarySpec.class); builder.internalView(JarBinarySpecInternal.class); } @Model @Hidden public JavaToolChainRegistry javaToolChain(ServiceRegistry serviceRegistry) { JavaToolChainInternal toolChain = serviceRegistry.get(JavaToolChainInternal.class); return new DefaultJavaToolChainRegistry(toolChain); } @Model public void javaInstallations(ModelMap<LocalJava> jdks) { } @Model @Hidden public void javaToolChains(ModelMap<LocalJavaInstallation> javaInstallations, final JavaInstallationProbe probe) { javaInstallations.create("currentGradleJDK", InstalledJdk.class, new Action<InstalledJdk>() { @Override public void execute(InstalledJdk installedJdk) { installedJdk.setJavaHome(Jvm.current().getJavaHome()); probe.current(installedJdk); } }); } private static void validateNoDuplicate(ModelMap<LocalJava> jdks) { ListMultimap<String, LocalJava> jdksByPath = indexByPath(jdks); List<String> errors = Lists.newArrayList(); for (String path : jdksByPath.keySet()) { checkDuplicateForPath(jdksByPath, path, errors); } if (!errors.isEmpty()) { throw new InvalidModelException(String.format("Duplicate Java installation found:\n%s", Joiner.on("\n").join(errors))); } } @Mutate public void registerPlatformResolver(PlatformResolvers platformResolvers) { platformResolvers.register(new JavaPlatformResolver()); } @Model @Hidden JavaInstallationProbe javaInstallationProbe(ServiceRegistry serviceRegistry) { return serviceRegistry.get(JavaInstallationProbe.class); } @Defaults public void resolveJavaToolChains(final ModelMap<LocalJavaInstallation> installedJdks, ModelMap<LocalJava> localJavaInstalls, final JavaInstallationProbe probe) { File currentJavaHome = canonicalFile(Jvm.current().getJavaHome()); // TODO:Cedric The following validation should in theory happen in its own rule, but it is not possible now because // there's no way to iterate on the map as subject of a `@Validate` rule without Gradle thinking you're trying to mutate it validateNoDuplicate(localJavaInstalls); for (final LocalJava candidate : localJavaInstalls) { final File javaHome = canonicalFile(candidate.getPath()); final JavaInstallationProbe.ProbeResult probeResult = probe.checkJdk(javaHome); Class<? extends LocalJavaInstallation> clazz; switch (probeResult.getInstallType()) { case IS_JDK: clazz = InstalledJdkInternal.class; break; case IS_JRE: clazz = InstalledJre.class; break; case NO_SUCH_DIRECTORY: throw new InvalidModelException(String.format("Path to JDK '%s' doesn't exist: %s", candidate.getName(), javaHome)); case INVALID_JDK: default: throw new InvalidModelException(String.format("JDK '%s' is not a valid JDK installation: %s\n%s", candidate.getName(), javaHome, probeResult.getError())); } if (!javaHome.equals(currentJavaHome)) { installedJdks.create(candidate.getName(), clazz, new Action<LocalJavaInstallation>() { @Override public void execute(LocalJavaInstallation installedJdk) { installedJdk.setJavaHome(javaHome); probeResult.configure(installedJdk); } }); } } } @ComponentBinaries public void createBinaries(ModelMap<JarBinarySpec> binaries, PlatformResolvers platforms, final JvmLibrarySpecInternal jvmLibrary) { List<JavaPlatform> selectedPlatforms = resolvePlatforms(platforms, jvmLibrary); final Set<String> exportedPackages = exportedPackagesOf(jvmLibrary); final Collection<DependencySpec> apiDependencies = apiDependenciesOf(jvmLibrary); final Collection<DependencySpec> dependencies = componentDependenciesOf(jvmLibrary); for (final JavaPlatform platform : selectedPlatforms) { final BinaryNamingScheme namingScheme = namingSchemeFor(jvmLibrary, selectedPlatforms, platform); binaries.create(namingScheme.getBinaryName(), new Action<JarBinarySpec>() { @Override public void execute(JarBinarySpec jarBinarySpec) { JarBinarySpecInternal jarBinary = (JarBinarySpecInternal) jarBinarySpec; jarBinary.setNamingScheme(namingScheme); jarBinary.setTargetPlatform(platform); jarBinary.setExportedPackages(exportedPackages); jarBinary.setApiDependencies(apiDependencies); jarBinary.setDependencies(dependencies); } }); } } private static File canonicalFile(File f) { try { return f.getCanonicalFile(); } catch (IOException e) { return f; } } private List<JavaPlatform> resolvePlatforms(final PlatformResolvers platformResolver, JvmLibrarySpecInternal jvmLibrarySpec) { List<PlatformRequirement> targetPlatforms = jvmLibrarySpec.getTargetPlatforms(); if (targetPlatforms.isEmpty()) { targetPlatforms = Collections.singletonList( DefaultPlatformRequirement.create(DefaultJavaPlatform.current().getName())); } return CollectionUtils.collect(targetPlatforms, new Transformer<JavaPlatform, PlatformRequirement>() { @Override public JavaPlatform transform(PlatformRequirement platformRequirement) { return platformResolver.resolve(JavaPlatform.class, platformRequirement); } }); } private static Set<String> exportedPackagesOf(JvmLibrarySpecInternal jvmLibrary) { return jvmLibrary.getApi().getExports(); } private static Collection<DependencySpec> apiDependenciesOf(JvmLibrarySpecInternal jvmLibrary) { return jvmLibrary.getApi().getDependencies().getDependencies(); } private static Collection<DependencySpec> componentDependenciesOf(JvmLibrarySpecInternal jvmLibrary) { return jvmLibrary.getDependencies().getDependencies(); } private BinaryNamingScheme namingSchemeFor(JvmLibrarySpec jvmLibrary, List<JavaPlatform> selectedPlatforms, JavaPlatform platform) { return DefaultBinaryNamingScheme.component(jvmLibrary.getName()) .withBinaryType("Jar") .withRole("jar", true) .withVariantDimension(platform, selectedPlatforms); } @BinaryTasks public void createTasks(ModelMap<Task> tasks, final JarBinarySpecInternal binary, final @Path("buildDir") File buildDir) { final File runtimeJarDestDir = binary.getJarFile().getParentFile(); final String runtimeJarArchiveName = binary.getJarFile().getName(); final String createRuntimeJar = "create" + capitalize(binary.getProjectScopedName()); final JvmAssembly assembly = binary.getAssembly(); final JarFile runtimeJarFile = binary.getRuntimeJar(); tasks.create(createRuntimeJar, Jar.class, new Action<Jar>() { @Override public void execute(Jar jar) { jar.setDescription("Creates the binary file for " + binary + "."); jar.from(assembly.getClassDirectories()); jar.from(assembly.getResourceDirectories()); jar.setDestinationDir(runtimeJarDestDir); jar.setArchiveName(runtimeJarArchiveName); jar.dependsOn(assembly); runtimeJarFile.setBuildTask(jar); } }); final JarFile apiJarFile = binary.getApiJar(); final Set<String> exportedPackages = binary.getExportedPackages(); String apiJarTaskName = apiJarTaskName(binary); tasks.create(apiJarTaskName, ApiJar.class, new Action<ApiJar>() { @Override public void execute(ApiJar apiJarTask) { apiJarTask.setDescription("Creates the API binary file for " + binary + "."); apiJarTask.setOutputFile(apiJarFile.getFile()); apiJarTask.setExportedPackages(exportedPackages); configureApiJarInputs(apiJarTask, assembly); apiJarTask.dependsOn(assembly); apiJarFile.setBuildTask(apiJarTask); } }); } private void configureApiJarInputs(ApiJar apiJarTask, JvmAssembly assembly) { int counter = 0; for (File classDir : assembly.getClassDirectories()) { apiJarTask.getInputs().dir(classDir).withPropertyName("classes$" + (++counter)).skipWhenEmpty(); } } private String apiJarTaskName(JarBinarySpecInternal binary) { String binaryName = binary.getProjectScopedName(); String libName = binaryName.endsWith("Jar") ? binaryName.substring(0, binaryName.length() - 3) : binaryName; return libName + "ApiJar"; } private static void checkDuplicateForPath(ListMultimap<String, LocalJava> index, String path, List<String> errors) { List<LocalJava> localJavas = index.get(path); if (localJavas.size() > 1) { errors.add(String.format(" - %s are both pointing to the same JDK installation path: %s", Joiner.on(", ").join(Iterables.transform(localJavas, new Function<LocalJava, String>() { @Override public String apply(LocalJava input) { return "'" + input.getName() + "'"; } })), path)); } } private static ListMultimap<String, LocalJava> indexByPath(ModelMap<LocalJava> localJavaInstalls) { final ListMultimap<String, LocalJava> index = ArrayListMultimap.create(); for (LocalJava localJava : localJavaInstalls) { try { index.put(localJava.getPath().getCanonicalPath(), localJava); } catch (IOException e) { // ignore this installation for validation, it will be caught later } } return index; } private static List<LocalJava> toImmutableJdkList(ModelMap<LocalJava> jdks) { final List<LocalJava> asImmutable = Lists.newArrayList(); jdks.afterEach(new Action<LocalJava>() { @Override public void execute(LocalJava localJava) { asImmutable.add(localJava); } }); return asImmutable; } @Defaults void configureJvmBinaries(@Each JvmBinarySpecInternal jvmBinary, ProjectLayout projectLayout) { File buildDir = projectLayout.getBuildDir(); BinaryNamingScheme namingScheme = jvmBinary.getNamingScheme(); jvmBinary.setClassesDir(namingScheme.getOutputDirectory(buildDir, "classes")); jvmBinary.setResourcesDir(namingScheme.getOutputDirectory(buildDir, "resources")); } @Defaults void configureJarBinaries(@Each JarBinarySpecInternal jarBinary, ProjectLayout projectLayout, JavaToolChainRegistry toolChains) { String libraryName = jarBinary.getId().getLibraryName(); File jarsDir = jarBinary.getNamingScheme().getOutputDirectory(projectLayout.getBuildDir(), "jars"); jarBinary.setJarFile(new File(jarsDir, libraryName + ".jar")); jarBinary.setApiJarFile(new File(jarsDir, "api/" + libraryName + ".jar")); jarBinary.setToolChain(toolChains.getForPlatform(jarBinary.getTargetPlatform())); } } }