Java tutorial
/* * Copyright 2017 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.api.publish.internal; import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.gson.stream.JsonWriter; import org.apache.commons.lang.StringUtils; import org.gradle.api.Named; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.DependencyConstraint; import org.gradle.api.artifacts.ExcludeRule; import org.gradle.api.artifacts.ExternalDependency; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.artifacts.VersionConstraint; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.capabilities.Capability; import org.gradle.api.component.ComponentWithCoordinates; import org.gradle.api.component.ComponentWithVariants; import org.gradle.api.component.SoftwareComponent; import org.gradle.api.internal.artifacts.DefaultExcludeRule; import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser; import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.component.SoftwareComponentInternal; import org.gradle.api.internal.component.UsageContext; import org.gradle.internal.hash.HashUtil; import org.gradle.internal.scopeids.id.BuildInvocationScopeId; import org.gradle.util.GUtil; import org.gradle.util.GradleVersion; import java.io.IOException; import java.io.Writer; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * <p>The Gradle module metadata file generator is responsible for generating a JSON file * describing module metadata. In particular, this file format is capable of handling different * variants with different dependency sets.</p> * * <p>Whenever you change this class, make sure you also:</p> * * <ul> * <li>Update the corresponding {@link ModuleMetadataParser module metadata parser}</li> * <li>Update the module metadata specification (subprojects/docs/src/docs/design/gradle-module-metadata-specification.md)</li> * <li>Update {@link org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleMetadataSerializer the module metadata serializer} </li> * <li>Add a sample for the module metadata serializer test, to make sure that serialized metadata is idempotent</li> * </ul> */ public class ModuleMetadataFileGenerator { private final BuildInvocationScopeId buildInvocationScopeId; private final ProjectDependencyPublicationResolver projectDependencyResolver; public ModuleMetadataFileGenerator(BuildInvocationScopeId buildInvocationScopeId, ProjectDependencyPublicationResolver projectDependencyResolver) { this.buildInvocationScopeId = buildInvocationScopeId; this.projectDependencyResolver = projectDependencyResolver; } public void generateTo(PublicationInternal publication, Collection<? extends PublicationInternal> publications, Writer writer) throws IOException { // Collect a map from component to coordinates. This might be better to move to the component or some publications model Map<SoftwareComponent, ComponentData> coordinates = new HashMap<SoftwareComponent, ComponentData>(); collectCoordinates(publications, coordinates); // Collect a map from component to its owning component. This might be better to move to the component or some publications model Map<SoftwareComponent, SoftwareComponent> owners = new HashMap<SoftwareComponent, SoftwareComponent>(); collectOwners(publications, owners); // Write the output JsonWriter jsonWriter = new JsonWriter(writer); jsonWriter.setHtmlSafe(false); jsonWriter.setIndent(" "); writeComponentWithVariants(publication, publication.getComponent(), coordinates, owners, jsonWriter); jsonWriter.flush(); writer.append('\n'); } private void collectOwners(Collection<? extends PublicationInternal> publications, Map<SoftwareComponent, SoftwareComponent> owners) { for (PublicationInternal publication : publications) { if (publication.getComponent() instanceof ComponentWithVariants) { ComponentWithVariants componentWithVariants = (ComponentWithVariants) publication.getComponent(); for (SoftwareComponent child : componentWithVariants.getVariants()) { owners.put(child, publication.getComponent()); } } } } private void collectCoordinates(Collection<? extends PublicationInternal> publications, Map<SoftwareComponent, ComponentData> coordinates) { for (PublicationInternal publication : publications) { if (publication.getComponent() != null) { ModuleVersionIdentifier moduleVersionIdentifier = publication.getCoordinates(); ImmutableAttributes attributes = publication.getAttributes(); coordinates.put(publication.getComponent(), new ComponentData(moduleVersionIdentifier, attributes)); } } } private void writeComponentWithVariants(PublicationInternal publication, SoftwareComponent component, Map<SoftwareComponent, ComponentData> componentCoordinates, Map<SoftwareComponent, SoftwareComponent> owners, JsonWriter jsonWriter) throws IOException { jsonWriter.beginObject(); writeFormat(jsonWriter); writeIdentity(publication.getCoordinates(), publication.getAttributes(), component, componentCoordinates, owners, jsonWriter); writeCreator(jsonWriter); writeVariants(publication, component, componentCoordinates, jsonWriter); jsonWriter.endObject(); } private void writeVersionConstraint(VersionConstraint versionConstraint, JsonWriter jsonWriter) throws IOException { if (DefaultImmutableVersionConstraint.of().equals(versionConstraint)) { return; } jsonWriter.name("version"); jsonWriter.beginObject(); // For now, 'requires' implies 'prefers', and 'strictly' implies 'requires' // Only publish the defining constraint. if (!versionConstraint.getStrictVersion().isEmpty()) { jsonWriter.name("strictly"); jsonWriter.value(versionConstraint.getStrictVersion()); } else if (!versionConstraint.getRequiredVersion().isEmpty()) { jsonWriter.name("requires"); jsonWriter.value(versionConstraint.getRequiredVersion()); } else if (!versionConstraint.getPreferredVersion().isEmpty()) { jsonWriter.name("prefers"); jsonWriter.value(versionConstraint.getPreferredVersion()); } List<String> rejectedVersions = versionConstraint.getRejectedVersions(); if (!rejectedVersions.isEmpty()) { jsonWriter.name("rejects"); jsonWriter.beginArray(); for (String reject : rejectedVersions) { jsonWriter.value(reject); } jsonWriter.endArray(); } jsonWriter.endObject(); } private void writeIdentity(ModuleVersionIdentifier coordinates, ImmutableAttributes attributes, SoftwareComponent component, Map<SoftwareComponent, ComponentData> componentCoordinates, Map<SoftwareComponent, SoftwareComponent> owners, JsonWriter jsonWriter) throws IOException { SoftwareComponent owner = owners.get(component); if (owner == null) { jsonWriter.name("component"); jsonWriter.beginObject(); jsonWriter.name("group"); jsonWriter.value(coordinates.getGroup()); jsonWriter.name("module"); jsonWriter.value(coordinates.getName()); jsonWriter.name("version"); jsonWriter.value(coordinates.getVersion()); writeAttributes(attributes, jsonWriter); jsonWriter.endObject(); } else { ComponentData componentData = componentCoordinates.get(owner); ModuleVersionIdentifier ownerCoordinates = componentData.coordinates; jsonWriter.name("component"); jsonWriter.beginObject(); jsonWriter.name("url"); jsonWriter.value(relativeUrlTo(coordinates, ownerCoordinates)); jsonWriter.name("group"); jsonWriter.value(ownerCoordinates.getGroup()); jsonWriter.name("module"); jsonWriter.value(ownerCoordinates.getName()); jsonWriter.name("version"); jsonWriter.value(ownerCoordinates.getVersion()); writeAttributes(componentData.attributes, jsonWriter); jsonWriter.endObject(); } } private void writeVariants(PublicationInternal publication, SoftwareComponent component, Map<SoftwareComponent, ComponentData> componentCoordinates, JsonWriter jsonWriter) throws IOException { boolean started = false; for (UsageContext usageContext : ((SoftwareComponentInternal) component).getUsages()) { if (!started) { jsonWriter.name("variants"); jsonWriter.beginArray(); started = true; } writeVariantHostedInThisModule(publication, usageContext, jsonWriter); } if (component instanceof ComponentWithVariants) { for (SoftwareComponent childComponent : ((ComponentWithVariants) component).getVariants()) { ModuleVersionIdentifier childCoordinates; if (childComponent instanceof ComponentWithCoordinates) { childCoordinates = ((ComponentWithCoordinates) childComponent).getCoordinates(); } else { ComponentData componentData = componentCoordinates.get(childComponent); childCoordinates = componentData == null ? null : componentData.coordinates; } assert childCoordinates != null; if (childComponent instanceof SoftwareComponentInternal) { for (UsageContext usageContext : ((SoftwareComponentInternal) childComponent).getUsages()) { if (!started) { jsonWriter.name("variants"); jsonWriter.beginArray(); started = true; } writeVariantHostedInAnotherModule(publication.getCoordinates(), childCoordinates, usageContext, jsonWriter); } } } } if (started) { jsonWriter.endArray(); } } private void writeCreator(JsonWriter jsonWriter) throws IOException { jsonWriter.name("createdBy"); jsonWriter.beginObject(); jsonWriter.name("gradle"); jsonWriter.beginObject(); jsonWriter.name("version"); jsonWriter.value(GradleVersion.current().getVersion()); jsonWriter.name("buildId"); jsonWriter.value(buildInvocationScopeId.getId().asString()); jsonWriter.endObject(); jsonWriter.endObject(); } private void writeFormat(JsonWriter jsonWriter) throws IOException { jsonWriter.name("formatVersion"); jsonWriter.value(ModuleMetadataParser.FORMAT_VERSION); } private void writeVariantHostedInAnotherModule(ModuleVersionIdentifier coordinates, ModuleVersionIdentifier targetCoordinates, UsageContext variant, JsonWriter jsonWriter) throws IOException { jsonWriter.beginObject(); jsonWriter.name("name"); jsonWriter.value(variant.getName()); writeAttributes(variant.getAttributes(), jsonWriter); jsonWriter.name("available-at"); jsonWriter.beginObject(); jsonWriter.name("url"); jsonWriter.value(relativeUrlTo(coordinates, targetCoordinates)); jsonWriter.name("group"); jsonWriter.value(targetCoordinates.getGroup()); jsonWriter.name("module"); jsonWriter.value(targetCoordinates.getName()); jsonWriter.name("version"); jsonWriter.value(targetCoordinates.getVersion()); jsonWriter.endObject(); jsonWriter.endObject(); } private String relativeUrlTo(ModuleVersionIdentifier from, ModuleVersionIdentifier to) { // TODO - do not assume Maven layout StringBuilder path = new StringBuilder(); path.append("../../"); path.append(to.getName()); path.append("/"); path.append(to.getVersion()); path.append("/"); path.append(to.getName()); path.append("-"); path.append(to.getVersion()); path.append(".module"); return path.toString(); } private void writeVariantHostedInThisModule(PublicationInternal publication, UsageContext variant, JsonWriter jsonWriter) throws IOException { jsonWriter.beginObject(); jsonWriter.name("name"); jsonWriter.value(variant.getName()); writeAttributes(variant.getAttributes(), jsonWriter); writeDependencies(variant, jsonWriter); writeDependencyConstraints(variant, jsonWriter); writeArtifacts(publication, variant, jsonWriter); writeCapabilities(variant, jsonWriter); jsonWriter.endObject(); } private void writeAttributes(AttributeContainer attributes, JsonWriter jsonWriter) throws IOException { if (attributes.isEmpty()) { return; } jsonWriter.name("attributes"); jsonWriter.beginObject(); Map<String, Attribute<?>> sortedAttributes = new TreeMap<String, Attribute<?>>(); for (Attribute<?> attribute : attributes.keySet()) { sortedAttributes.put(attribute.getName(), attribute); } for (Attribute<?> attribute : sortedAttributes.values()) { jsonWriter.name(attribute.getName()); Object value = attributes.getAttribute(attribute); if (value instanceof Boolean) { Boolean b = (Boolean) value; jsonWriter.value(b); } else if (value instanceof String) { String s = (String) value; jsonWriter.value(s); } else if (value instanceof Named) { Named named = (Named) value; jsonWriter.value(named.getName()); } else if (value instanceof Enum) { Enum<?> enumValue = (Enum<?>) value; jsonWriter.value(enumValue.name()); } else { throw new IllegalArgumentException( String.format("Cannot write attribute %s with unsupported value %s of type %s.", attribute.getName(), value, value.getClass().getName())); } } jsonWriter.endObject(); } private void writeArtifacts(PublicationInternal publication, UsageContext variant, JsonWriter jsonWriter) throws IOException { if (variant.getArtifacts().isEmpty()) { return; } jsonWriter.name("files"); jsonWriter.beginArray(); for (PublishArtifact artifact : variant.getArtifacts()) { writeArtifact(publication, artifact, jsonWriter); } jsonWriter.endArray(); } private void writeArtifact(PublicationInternal publication, PublishArtifact artifact, JsonWriter jsonWriter) throws IOException { PublicationInternal.PublishedFile publishedFile = publication.getPublishedFile(artifact); jsonWriter.beginObject(); jsonWriter.name("name"); jsonWriter.value(publishedFile.getName()); jsonWriter.name("url"); jsonWriter.value(publishedFile.getUri()); jsonWriter.name("size"); jsonWriter.value(artifact.getFile().length()); jsonWriter.name("sha1"); jsonWriter.value(HashUtil.sha1(artifact.getFile()).asHexString()); jsonWriter.name("md5"); jsonWriter.value(HashUtil.createHash(artifact.getFile(), "md5").asHexString()); jsonWriter.endObject(); } private void writeDependencies(UsageContext variant, JsonWriter jsonWriter) throws IOException { if (variant.getDependencies().isEmpty()) { return; } jsonWriter.name("dependencies"); jsonWriter.beginArray(); Set<ExcludeRule> additionalExcludes = variant.getGlobalExcludes(); for (ModuleDependency moduleDependency : variant.getDependencies()) { writeDependency(moduleDependency, additionalExcludes, jsonWriter); } jsonWriter.endArray(); } private void writeDependency(Dependency dependency, Set<ExcludeRule> additionalExcludes, JsonWriter jsonWriter) throws IOException { jsonWriter.beginObject(); if (dependency instanceof ProjectDependency) { ProjectDependency projectDependency = (ProjectDependency) dependency; ModuleVersionIdentifier identifier = projectDependencyResolver.resolve(ModuleVersionIdentifier.class, projectDependency); jsonWriter.name("group"); jsonWriter.value(identifier.getGroup()); jsonWriter.name("module"); jsonWriter.value(identifier.getName()); writeVersionConstraint(DefaultImmutableVersionConstraint.of(identifier.getVersion()), jsonWriter); } else { jsonWriter.name("group"); jsonWriter.value(dependency.getGroup()); jsonWriter.name("module"); jsonWriter.value(dependency.getName()); VersionConstraint vc; if (dependency instanceof ExternalDependency) { vc = ((ExternalDependency) dependency).getVersionConstraint(); } else { vc = DefaultImmutableVersionConstraint.of(Strings.nullToEmpty(dependency.getVersion())); } writeVersionConstraint(vc, jsonWriter); } if (dependency instanceof ModuleDependency) { writeExcludes((ModuleDependency) dependency, additionalExcludes, jsonWriter); writeAttributes(((ModuleDependency) dependency).getAttributes(), jsonWriter); } String reason = dependency.getReason(); if (StringUtils.isNotEmpty(reason)) { jsonWriter.name("reason"); jsonWriter.value(reason); } jsonWriter.endObject(); } private void writeDependencyConstraints(UsageContext variant, JsonWriter jsonWriter) throws IOException { if (variant.getDependencyConstraints().isEmpty()) { return; } jsonWriter.name("dependencyConstraints"); jsonWriter.beginArray(); for (DependencyConstraint dependencyConstraint : variant.getDependencyConstraints()) { writeDependencyConstraint(dependencyConstraint, jsonWriter); } jsonWriter.endArray(); } private void writeDependencyConstraint(DependencyConstraint dependencyConstraint, JsonWriter jsonWriter) throws IOException { jsonWriter.beginObject(); jsonWriter.name("group"); jsonWriter.value(dependencyConstraint.getGroup()); jsonWriter.name("module"); jsonWriter.value(dependencyConstraint.getName()); writeVersionConstraint(dependencyConstraint.getVersionConstraint(), jsonWriter); writeAttributes(dependencyConstraint.getAttributes(), jsonWriter); String reason = dependencyConstraint.getReason(); if (StringUtils.isNotEmpty(reason)) { jsonWriter.name("reason"); jsonWriter.value(reason); } jsonWriter.endObject(); } private void writeExcludes(ModuleDependency moduleDependency, Set<ExcludeRule> additionalExcludes, JsonWriter jsonWriter) throws IOException { Set<ExcludeRule> excludeRules; if (!moduleDependency.isTransitive()) { excludeRules = Collections.<ExcludeRule>singleton(new DefaultExcludeRule(null, null)); } else { excludeRules = Sets.union(additionalExcludes, moduleDependency.getExcludeRules()); } if (excludeRules.isEmpty()) { return; } jsonWriter.name("excludes"); jsonWriter.beginArray(); for (ExcludeRule excludeRule : excludeRules) { jsonWriter.beginObject(); jsonWriter.name("group"); String group = GUtil.elvis(excludeRule.getGroup(), "*"); jsonWriter.value(group); jsonWriter.name("module"); String module = GUtil.elvis(excludeRule.getModule(), "*"); jsonWriter.value(module); jsonWriter.endObject(); } jsonWriter.endArray(); } private void writeCapabilities(UsageContext variant, JsonWriter jsonWriter) throws IOException { Set<? extends Capability> capabilities = variant.getCapabilities(); if (!capabilities.isEmpty()) { jsonWriter.name("capabilities"); jsonWriter.beginArray(); for (Capability capability : capabilities) { jsonWriter.beginObject(); jsonWriter.name("group").value(capability.getGroup()); jsonWriter.name("name").value(capability.getName()); jsonWriter.name("version").value(capability.getVersion()); jsonWriter.endObject(); } jsonWriter.endArray(); } } private static class ComponentData { private final ModuleVersionIdentifier coordinates; private final ImmutableAttributes attributes; private ComponentData(ModuleVersionIdentifier coordinates, ImmutableAttributes attributes) { this.coordinates = coordinates; this.attributes = attributes; } } }