Merge pull request #13349 from gradle/bamboo/cc/generateModuleMetadata/i

Extract serializable `ModuleMetadata` model from `ModuleMetadataJsonWriter`
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriter.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriter.java
index 5fc8026..85e7315 100644
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriter.java
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriter.java
@@ -17,11 +17,8 @@
 package org.gradle.api.publish.internal.metadata;
 
 import com.google.gson.stream.JsonWriter;
-import org.gradle.api.component.ComponentWithVariants;
-import org.gradle.api.component.SoftwareComponent;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser;
 import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver;
-import org.gradle.api.internal.component.SoftwareComponentInternal;
 import org.gradle.api.publish.internal.PublicationInternal;
 import org.gradle.internal.hash.ChecksumService;
 import org.gradle.internal.scopeids.id.BuildInvocationScopeId;
@@ -29,8 +26,6 @@
 import java.io.IOException;
 import java.io.Writer;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * <p>The Gradle module metadata file generator is responsible for generating a JSON file
@@ -58,15 +53,9 @@
     }
 
     public void writeTo(Writer writer, PublicationInternal<?> publication, Collection<? extends PublicationInternal<?>> publications) 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<>();
-        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<>();
-        collectOwners(publications, owners);
-
-        InvalidPublicationChecker checker = new InvalidPublicationChecker(publication.getName());
+        ModuleMetadata metadata = moduleMetadataFor(publication, publications);
+        String buildId = publication.isPublishBuildId() ? buildInvocationScopeId.getId().asString() : null;
 
         // Write the output
         JsonWriter jsonWriter = new JsonWriter(writer);
@@ -75,42 +64,24 @@
 
         new ModuleMetadataJsonWriter(
             jsonWriter,
-            checker,
-            checksumService,
-            projectDependencyResolver,
-            buildInvocationScopeId.getId().asString(),
-            publication,
-            publication.getComponent(),
-            coordinates,
-            owners
+            metadata,
+            buildId,
+            checksumService
         ).write();
 
         jsonWriter.flush();
         writer.append('\n');
+    }
 
+    private ModuleMetadata moduleMetadataFor(PublicationInternal<?> publication, Collection<? extends PublicationInternal<?>> publications) {
+        InvalidPublicationChecker checker = new InvalidPublicationChecker(publication.getName());
+        ModuleMetadata metadata = new ModuleMetadataBuilder(
+            publication,
+            publications,
+            checker,
+            projectDependencyResolver
+        ).build();
         checker.validate();
-    }
-
-    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) {
-            SoftwareComponentInternal component = publication.getComponent();
-            if (component != null) {
-                coordinates.put(
-                    component,
-                    new ComponentData(publication.getCoordinates(), publication.getAttributes())
-                );
-            }
-        }
+        return metadata;
     }
 }
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadata.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadata.java
new file mode 100644
index 0000000..7ae7d13
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadata.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2020 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.metadata;
+
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+class ModuleMetadata {
+
+    final Identity identity;
+    final List<Variant> variants;
+
+    ModuleMetadata(Identity identity, List<Variant> variants) {
+        this.identity = identity;
+        this.variants = variants;
+    }
+
+    static class Identity {
+
+        final ModuleVersionIdentifier coordinates;
+        final List<Attribute> attributes;
+        @Nullable
+        final String relativeUrl;
+
+        Identity(
+            ModuleVersionIdentifier coordinates,
+            List<Attribute> attributes,
+            @Nullable String relativeUrl
+        ) {
+            this.coordinates = coordinates;
+            this.attributes = attributes;
+            this.relativeUrl = relativeUrl;
+        }
+    }
+
+    static class LocalVariant extends Variant {
+
+        final String name;
+        final List<Attribute> attributes;
+        final List<Capability> capabilities;
+        final List<Dependency> dependencies;
+        final List<DependencyConstraint> dependencyConstraints;
+        final List<Artifact> artifacts;
+
+        LocalVariant(
+            String name,
+            List<Attribute> attributes,
+            List<Capability> capabilities,
+            List<Dependency> dependencies,
+            List<DependencyConstraint> dependencyConstraints,
+            List<Artifact> artifacts
+        ) {
+            this.name = name;
+            this.attributes = attributes;
+            this.capabilities = capabilities;
+            this.dependencies = dependencies;
+            this.dependencyConstraints = dependencyConstraints;
+            this.artifacts = artifacts;
+        }
+    }
+
+    static class RemoteVariant extends Variant {
+
+        final String name;
+        final List<Attribute> attributes;
+        final AvailableAt availableAt;
+        final List<Capability> capabilities;
+
+        RemoteVariant(
+            String name,
+            List<Attribute> attributes,
+            AvailableAt availableAt,
+            List<Capability> capabilities
+        ) {
+            this.name = name;
+            this.attributes = attributes;
+            this.availableAt = availableAt;
+            this.capabilities = capabilities;
+        }
+    }
+
+    static class Dependency {
+
+        final DependencyCoordinates coordinates;
+        final Set<ExcludeRule> excludeRules;
+        final List<Attribute> attributes;
+        final List<Capability> requestedCapabilities;
+        final boolean endorseStrictVersions;
+        final String reason;
+        final ArtifactSelector artifactSelector;
+
+        public Dependency(
+            DependencyCoordinates coordinates,
+            Set<ExcludeRule> excludeRules,
+            List<Attribute> attributes,
+            List<Capability> requestedCapabilities,
+            boolean endorseStrictVersions,
+            String reason,
+            ArtifactSelector artifactSelector
+        ) {
+            this.coordinates = coordinates;
+            this.excludeRules = excludeRules;
+            this.attributes = attributes;
+            this.requestedCapabilities = requestedCapabilities;
+            this.endorseStrictVersions = endorseStrictVersions;
+            this.reason = reason;
+            this.artifactSelector = artifactSelector;
+        }
+    }
+
+    static abstract class Variant {
+    }
+
+    static class Attribute {
+
+        final String name;
+        final Object value;
+
+        public Attribute(String name, Object value) {
+            this.name = name;
+            this.value = value;
+        }
+    }
+
+    static class Capability {
+
+        final String group;
+        final String name;
+        @Nullable
+        final String version;
+
+        public Capability(String group, String name, @Nullable String version) {
+            this.group = group;
+            this.name = name;
+            this.version = version;
+        }
+    }
+
+    static class Version {
+
+        @Nullable
+        final String requires;
+        @Nullable
+        final String strictly;
+        @Nullable
+        final String preferred;
+        final List<String> rejectedVersions;
+
+        public Version(
+            @Nullable String requires,
+            @Nullable String strictly,
+            @Nullable String preferred,
+            List<String> rejectedVersions
+        ) {
+            this.requires = requires;
+            this.strictly = strictly;
+            this.preferred = preferred;
+            this.rejectedVersions = rejectedVersions;
+        }
+    }
+
+    static class DependencyCoordinates {
+
+        final String group;
+        final String name;
+        final Version version;
+
+        public DependencyCoordinates(
+            String group, String name, Version version
+        ) {
+            this.group = group;
+            this.name = name;
+            this.version = version;
+        }
+    }
+
+    static class ArtifactSelector {
+
+        final String name;
+        final String type;
+        @Nullable
+        final String extension;
+        @Nullable
+        final String classifier;
+
+        public ArtifactSelector(
+            String name,
+            String type,
+            @Nullable String extension,
+            @Nullable String classifier
+        ) {
+            this.name = name;
+            this.type = type;
+            this.extension = extension;
+            this.classifier = classifier;
+        }
+    }
+
+    static class DependencyConstraint {
+
+        final String group;
+        final String module;
+        final Version version;
+        final List<Attribute> attributes;
+        final String reason;
+
+        public DependencyConstraint(
+            String group,
+            String module,
+            Version version,
+            List<Attribute> attributes,
+            String reason
+        ) {
+            this.group = group;
+            this.module = module;
+            this.version = version;
+            this.attributes = attributes;
+            this.reason = reason;
+        }
+    }
+
+    static class Artifact {
+
+        final String name;
+        final String uri;
+        final File file;
+
+        public Artifact(String name, String uri, File file) {
+            this.name = name;
+            this.uri = uri;
+            this.file = file;
+        }
+    }
+
+    static class AvailableAt {
+
+        final String url;
+        final ModuleVersionIdentifier coordinates;
+
+        public AvailableAt(String url, ModuleVersionIdentifier coordinates) {
+            this.url = url;
+            this.coordinates = coordinates;
+        }
+    }
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadataBuilder.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadataBuilder.java
new file mode 100644
index 0000000..b02bdfc
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadataBuilder.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright 2020 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.metadata;
+
+import com.google.common.collect.Sets;
+import org.gradle.api.Named;
+import org.gradle.api.artifacts.DependencyArtifact;
+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.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.ImmutableVersionConstraint;
+import org.gradle.api.internal.artifacts.PublishArtifactInternal;
+import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependencyConstraint;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver;
+import org.gradle.api.internal.attributes.AttributeContainerInternal;
+import org.gradle.api.internal.attributes.ImmutableAttributes;
+import org.gradle.api.internal.component.SoftwareComponentInternal;
+import org.gradle.api.internal.component.UsageContext;
+import org.gradle.api.publish.internal.PublicationInternal;
+import org.gradle.api.publish.internal.versionmapping.VariantVersionMappingStrategyInternal;
+import org.gradle.api.publish.internal.versionmapping.VersionMappingStrategyInternal;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+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;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.base.Strings.nullToEmpty;
+import static java.lang.String.format;
+import static java.util.Collections.emptyList;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+
+class ModuleMetadataBuilder {
+
+    private final PublicationInternal<?> publication;
+    private final ModuleVersionIdentifier publicationCoordinates;
+    private final SoftwareComponentInternal component;
+    private final Collection<? extends PublicationInternal<?>> publications;
+    private final Map<SoftwareComponent, ComponentData> componentCoordinates = new HashMap<>();
+    private final ProjectDependencyPublicationResolver projectDependencyResolver;
+    private final InvalidPublicationChecker checker;
+
+    public ModuleMetadataBuilder(
+        PublicationInternal<?> publication,
+        Collection<? extends PublicationInternal<?>> publications,
+        InvalidPublicationChecker checker,
+        ProjectDependencyPublicationResolver projectDependencyResolver
+    ) {
+        this.component = publication.getComponent();
+        this.publicationCoordinates = publication.getCoordinates();
+        this.publication = publication;
+        this.publications = publications;
+        this.checker = checker;
+        this.projectDependencyResolver = projectDependencyResolver;
+        // Collect a map from component to coordinates. This might be better to move to the component or some publications model
+        collectCoordinates(componentCoordinates);
+    }
+
+    ModuleMetadata build() {
+        return new ModuleMetadata(identity(), variants());
+    }
+
+    private ModuleMetadata.Identity identity() {
+        // 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<>();
+        collectOwners(publications, owners);
+
+        SoftwareComponent owner = owners.get(component);
+        ComponentData ownerData = owner == null ? null : componentCoordinates.get(owner);
+        ComponentData componentData = componentCoordinates.get(component);
+
+        return ownerData != null
+            ? identityFor(ownerData, relativeUrlTo(componentData.coordinates, ownerData.coordinates))
+            : identityFor(componentData, null);
+    }
+
+    private ModuleMetadata.Identity identityFor(ComponentData componentData, String relativeUrl) {
+        return new ModuleMetadata.Identity(
+            componentData.coordinates,
+            attributesFor(componentData.attributes),
+            relativeUrl
+        );
+    }
+
+    private List<ModuleMetadata.Variant> variants() {
+        ArrayList<ModuleMetadata.Variant> variants = new ArrayList<>();
+        for (UsageContext variant : component.getUsages()) {
+            checkVariant(variant);
+            variants.add(
+                new ModuleMetadata.LocalVariant(
+                    variant.getName(),
+                    attributesFor(variant.getAttributes()),
+                    capabilitiesFor(variant.getCapabilities()),
+                    dependenciesOf(variant),
+                    dependencyConstraintsFor(variant),
+                    artifactsOf(variant)
+                )
+            );
+        }
+        if (component instanceof ComponentWithVariants) {
+            for (SoftwareComponent childComponent : ((ComponentWithVariants) component).getVariants()) {
+                ModuleVersionIdentifier childCoordinates = coordinatesOf(childComponent);
+                assert childCoordinates != null;
+                if (childComponent instanceof SoftwareComponentInternal) {
+                    for (UsageContext variant : ((SoftwareComponentInternal) childComponent).getUsages()) {
+                        checkVariant(variant);
+                        variants.add(
+                            new ModuleMetadata.RemoteVariant(
+                                variant.getName(),
+                                attributesFor(variant.getAttributes()),
+                                availableAt(publicationCoordinates, childCoordinates),
+                                capabilitiesFor(variant.getCapabilities())
+                            )
+                        );
+                    }
+                }
+            }
+        }
+        return variants;
+    }
+
+    private List<ModuleMetadata.Artifact> artifactsOf(UsageContext variant) {
+        if (variant.getArtifacts().isEmpty()) {
+            return emptyList();
+        }
+        ArrayList<ModuleMetadata.Artifact> artifacts = new ArrayList<>();
+        for (PublishArtifact artifact : variant.getArtifacts()) {
+            ModuleMetadata.Artifact metadataArtifact = artifactFor(artifact);
+            if (metadataArtifact != null) {
+                artifacts.add(metadataArtifact);
+            }
+        }
+        return artifacts;
+    }
+
+    @Nullable
+    private ModuleMetadata.Artifact artifactFor(PublishArtifact artifact) {
+        if (shouldNotBePublished(artifact)) {
+            return null;
+        }
+        PublicationInternal.PublishedFile publishedFile = publication.getPublishedFile(artifact);
+        return new ModuleMetadata.Artifact(
+            publishedFile.getName(),
+            publishedFile.getUri(),
+            artifact.getFile()
+        );
+    }
+
+    private boolean shouldNotBePublished(PublishArtifact artifact) {
+        return artifact instanceof PublishArtifactInternal
+            && !((PublishArtifactInternal) artifact).shouldBePublished();
+    }
+
+    private ModuleMetadata.AvailableAt availableAt(ModuleVersionIdentifier coordinates, ModuleVersionIdentifier targetCoordinates) {
+        return new ModuleMetadata.AvailableAt(
+            relativeUrlTo(coordinates, targetCoordinates),
+            targetCoordinates
+        );
+    }
+
+    private ModuleMetadata.Dependency dependencyFor(
+        ModuleDependency dependency,
+        Set<ExcludeRule> additionalExcludes,
+        VariantVersionMappingStrategyInternal versionMappingStrategy,
+        DependencyArtifact dependencyArtifact
+    ) {
+        return new ModuleMetadata.Dependency(
+            dependencyCoordinatesFor(dependency, versionMappingStrategy),
+            excludedRulesFor(dependency, additionalExcludes),
+            attributesFor(dependency.getAttributes()),
+            capabilitiesFor(dependency.getRequestedCapabilities()),
+            dependency.isEndorsingStrictVersions(),
+            isNotEmpty(dependency.getReason()) ? dependency.getReason() : null,
+            dependencyArtifact != null ? artifactSelectorFor(dependencyArtifact) : null
+        );
+    }
+
+    private ModuleMetadata.DependencyCoordinates dependencyCoordinatesFor(
+        ModuleDependency dependency,
+        VariantVersionMappingStrategyInternal versionMappingStrategy
+    ) {
+        return dependency instanceof ProjectDependency
+            ? projectDependencyCoordinatesFor((ProjectDependency) dependency, versionMappingStrategy)
+            : moduleDependencyCoordinatesFor(dependency, versionMappingStrategy);
+    }
+
+    private ModuleMetadata.ArtifactSelector artifactSelectorFor(DependencyArtifact dependencyArtifact) {
+        return new ModuleMetadata.ArtifactSelector(
+            dependencyArtifact.getName(),
+            dependencyArtifact.getType(),
+            isNullOrEmpty(dependencyArtifact.getExtension()) ? null : dependencyArtifact.getExtension(),
+            isNullOrEmpty(dependencyArtifact.getClassifier()) ? null : dependencyArtifact.getClassifier()
+        );
+    }
+
+    private List<ModuleMetadata.Capability> capabilitiesFor(Collection<? extends Capability> capabilities) {
+        if (capabilities.isEmpty()) {
+            return emptyList();
+        }
+
+        ArrayList<ModuleMetadata.Capability> metadataCapabilities = new ArrayList<>();
+        for (Capability capability : capabilities) {
+            metadataCapabilities.add(
+                new ModuleMetadata.Capability(
+                    capability.getGroup(),
+                    capability.getName(),
+                    isNotEmpty(capability.getVersion()) ? capability.getVersion() : null
+                )
+            );
+        }
+        return metadataCapabilities;
+    }
+
+    private List<ModuleMetadata.Attribute> attributesFor(AttributeContainer attributes) {
+        if (attributes.isEmpty()) {
+            return emptyList();
+        }
+
+        ArrayList<ModuleMetadata.Attribute> metadataAttributes = new ArrayList<>();
+        for (Attribute<?> attribute : sorted(attributes).values()) {
+            String name = attribute.getName();
+            Object value = attributes.getAttribute(attribute);
+            Object effectiveValue = attributeValueFor(value);
+            if (effectiveValue == null) {
+                throw new IllegalArgumentException(
+                    format("Cannot write attribute %s with unsupported value %s of type %s.", name, value, value.getClass().getName())
+                );
+            }
+            metadataAttributes.add(
+                new ModuleMetadata.Attribute(name, effectiveValue)
+            );
+        }
+        return metadataAttributes;
+    }
+
+    private Object attributeValueFor(Object value) {
+        if (value instanceof Boolean || value instanceof Integer || value instanceof String) {
+            return value;
+        } else if (value instanceof Named) {
+            return ((Named) value).getName();
+        } else if (value instanceof Enum) {
+            return ((Enum<?>) value).name();
+        } else {
+            return null;
+        }
+    }
+
+    private ModuleMetadata.DependencyCoordinates moduleDependencyCoordinatesFor(
+        ModuleDependency dependency,
+        VariantVersionMappingStrategyInternal versionMappingStrategy
+    ) {
+        String group = dependency.getGroup();
+        String name = dependency.getName();
+        String resolvedVersion = null;
+        if (versionMappingStrategy != null) {
+            ModuleVersionIdentifier resolvedVersionId = versionMappingStrategy.maybeResolveVersion(group, name);
+            if (resolvedVersionId != null) {
+                group = resolvedVersionId.getGroup();
+                name = resolvedVersionId.getName();
+                resolvedVersion = resolvedVersionId.getVersion();
+            }
+        }
+        return new ModuleMetadata.DependencyCoordinates(
+            group,
+            name,
+            versionFor(versionConstraintFor(dependency), resolvedVersion)
+        );
+    }
+
+    private ModuleMetadata.DependencyCoordinates projectDependencyCoordinatesFor(
+        ProjectDependency projectDependency,
+        VariantVersionMappingStrategyInternal versionMappingStrategy
+    ) {
+        String resolvedVersion = null;
+        ModuleVersionIdentifier identifier = moduleIdentifierFor(projectDependency);
+        if (versionMappingStrategy != null) {
+            ModuleVersionIdentifier resolved =
+                versionMappingStrategy.maybeResolveVersion(
+                    identifier.getGroup(),
+                    identifier.getName()
+                );
+            if (resolved != null) {
+                identifier = resolved;
+                resolvedVersion = identifier.getVersion();
+            }
+        }
+        return new ModuleMetadata.DependencyCoordinates(
+            identifier.getGroup(),
+            identifier.getName(),
+            versionFor(
+                DefaultImmutableVersionConstraint.of(identifier.getVersion()),
+                resolvedVersion
+            )
+        );
+    }
+
+    private List<ModuleMetadata.Dependency> dependenciesOf(UsageContext variant) {
+        if (variant.getDependencies().isEmpty()) {
+            return emptyList();
+        }
+        ArrayList<ModuleMetadata.Dependency> dependencies = new ArrayList<>();
+        Set<ExcludeRule> additionalExcludes = variant.getGlobalExcludes();
+        VariantVersionMappingStrategyInternal versionMappingStrategy = versionMappingStrategyFor(variant);
+        for (ModuleDependency moduleDependency : variant.getDependencies()) {
+            if (moduleDependency.getArtifacts().isEmpty()) {
+                dependencies.add(
+                    dependencyFor(
+                        moduleDependency,
+                        additionalExcludes,
+                        versionMappingStrategy,
+                        null
+                    )
+                );
+            } else {
+                for (DependencyArtifact dependencyArtifact : moduleDependency.getArtifacts()) {
+                    dependencies.add(
+                        dependencyFor(
+                            moduleDependency,
+                            additionalExcludes,
+                            versionMappingStrategy,
+                            dependencyArtifact
+                        )
+                    );
+                }
+            }
+        }
+        return dependencies;
+    }
+
+    private List<ModuleMetadata.DependencyConstraint> dependencyConstraintsFor(UsageContext variant) {
+        if (variant.getDependencyConstraints().isEmpty()) {
+            return emptyList();
+        }
+        VariantVersionMappingStrategyInternal versionMappingStrategy = versionMappingStrategyFor(variant);
+        ArrayList<ModuleMetadata.DependencyConstraint> dependencyConstraints = new ArrayList<>();
+        for (DependencyConstraint dependencyConstraint : variant.getDependencyConstraints()) {
+            dependencyConstraints.add(
+                dependencyConstraintFor(dependencyConstraint, versionMappingStrategy)
+            );
+        }
+        return dependencyConstraints;
+    }
+
+    private ModuleMetadata.DependencyConstraint dependencyConstraintFor(DependencyConstraint dependencyConstraint, VariantVersionMappingStrategyInternal variantVersionMappingStrategy) {
+        String group;
+        String module;
+        String resolvedVersion = null;
+        if (dependencyConstraint instanceof DefaultProjectDependencyConstraint) {
+            DefaultProjectDependencyConstraint dependency = (DefaultProjectDependencyConstraint) dependencyConstraint;
+            ProjectDependency projectDependency = dependency.getProjectDependency();
+            ModuleVersionIdentifier identifier = moduleIdentifierFor(projectDependency);
+            group = identifier.getGroup();
+            module = identifier.getName();
+            resolvedVersion = identifier.getVersion();
+        } else {
+            group = dependencyConstraint.getGroup();
+            module = dependencyConstraint.getName();
+        }
+        ModuleVersionIdentifier resolvedVersionId = variantVersionMappingStrategy != null
+            ? variantVersionMappingStrategy.maybeResolveVersion(group, module)
+            : null;
+        String effectiveGroup = resolvedVersionId != null ? resolvedVersionId.getGroup() : group;
+        String effectiveModule = resolvedVersionId != null ? resolvedVersionId.getName() : module;
+        String effectiveVersion = resolvedVersionId != null ? resolvedVersionId.getVersion() : resolvedVersion;
+        return new ModuleMetadata.DependencyConstraint(
+            effectiveGroup,
+            effectiveModule,
+            versionFor(
+                DefaultImmutableVersionConstraint.of(dependencyConstraint.getVersionConstraint()),
+                effectiveVersion
+            ),
+            attributesFor(dependencyConstraint.getAttributes()),
+            isNotEmpty(dependencyConstraint.getReason()) ? dependencyConstraint.getReason() : null
+        );
+    }
+
+    @Nullable
+    private ModuleMetadata.Version versionFor(
+        ImmutableVersionConstraint versionConstraint,
+        @Nullable String resolvedVersion
+    ) {
+        checker.sawDependencyOrConstraint();
+        if (resolvedVersion == null && isEmpty(versionConstraint)) {
+            return null;
+        }
+        checker.sawVersion();
+
+        boolean isStrict = !versionConstraint.getStrictVersion().isEmpty();
+        String version;
+        String preferred;
+        if (resolvedVersion != null) {
+            version = resolvedVersion;
+            preferred = null;
+        } else {
+            version = isStrict
+                ? versionConstraint.getStrictVersion()
+                : !versionConstraint.getRequiredVersion().isEmpty()
+                ? versionConstraint.getRequiredVersion()
+                : null;
+            preferred = !versionConstraint.getPreferredVersion().isEmpty()
+                ? versionConstraint.getPreferredVersion()
+                : null;
+        }
+        return new ModuleMetadata.Version(
+            version,
+            isStrict ? version : null,
+            preferred,
+            versionConstraint.getRejectedVersions()
+        );
+    }
+
+    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(Map<SoftwareComponent, ComponentData> coordinates) {
+        for (PublicationInternal<?> publication : publications) {
+            SoftwareComponentInternal component = publication.getComponent();
+            if (component != null) {
+                coordinates.put(
+                    component,
+                    new ComponentData(publication.getCoordinates(), publication.getAttributes())
+                );
+            }
+        }
+    }
+
+    private void checkVariant(UsageContext usageContext) {
+        checker.registerVariant(
+            usageContext.getName(),
+            usageContext.getAttributes(),
+            usageContext.getCapabilities()
+        );
+    }
+
+    private ImmutableVersionConstraint versionConstraintFor(ModuleDependency dependency) {
+        return dependency instanceof ExternalDependency
+            ? DefaultImmutableVersionConstraint.of(((ExternalDependency) dependency).getVersionConstraint())
+            : DefaultImmutableVersionConstraint.of(nullToEmpty(dependency.getVersion()));
+    }
+
+    private Set<ExcludeRule> excludedRulesFor(ModuleDependency moduleDependency, Set<ExcludeRule> additionalExcludes) {
+        return moduleDependency.isTransitive()
+            ? Sets.union(additionalExcludes, moduleDependency.getExcludeRules())
+            : Collections.singleton(new DefaultExcludeRule(null, null));
+    }
+
+    private Map<String, Attribute<?>> sorted(AttributeContainer attributes) {
+        Map<String, Attribute<?>> sortedAttributes = new TreeMap<>();
+        for (Attribute<?> attribute : attributes.keySet()) {
+            sortedAttributes.put(attribute.getName(), attribute);
+        }
+        return sortedAttributes;
+    }
+
+    private ModuleVersionIdentifier coordinatesOf(SoftwareComponent childComponent) {
+        if (childComponent instanceof ComponentWithCoordinates) {
+            return ((ComponentWithCoordinates) childComponent).getCoordinates();
+        }
+        ComponentData componentData = componentCoordinates.get(childComponent);
+        if (componentData != null) {
+            return componentData.coordinates;
+        }
+        return null;
+    }
+
+    private ModuleVersionIdentifier moduleIdentifierFor(ProjectDependency projectDependency) {
+        return projectDependencyResolver.resolve(ModuleVersionIdentifier.class, projectDependency);
+    }
+
+    private VariantVersionMappingStrategyInternal versionMappingStrategyFor(UsageContext variant) {
+        VersionMappingStrategyInternal versionMappingStrategy = publication.getVersionMappingStrategy();
+        return versionMappingStrategy != null
+            ? versionMappingStrategy.findStrategyForVariant(immutableAttributesOf(variant))
+            : null;
+    }
+
+    private ImmutableAttributes immutableAttributesOf(UsageContext variant) {
+        return ((AttributeContainerInternal) variant.getAttributes()).asImmutable();
+    }
+
+    private boolean isEmpty(ImmutableVersionConstraint versionConstraint) {
+        return DefaultImmutableVersionConstraint.of().equals(versionConstraint);
+    }
+
+    public static String relativeUrlTo(
+        @SuppressWarnings("unused") 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();
+    }
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadataJsonWriter.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadataJsonWriter.java
index fc4265b..5d21d6e 100644
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadataJsonWriter.java
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/metadata/ModuleMetadataJsonWriter.java
@@ -16,97 +16,44 @@
 
 package org.gradle.api.publish.internal.metadata;
 
-import com.google.common.collect.Sets;
 import com.google.gson.stream.JsonWriter;
-import org.gradle.api.Named;
-import org.gradle.api.artifacts.DependencyArtifact;
-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.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.ImmutableVersionConstraint;
-import org.gradle.api.internal.artifacts.PublishArtifactInternal;
-import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependencyConstraint;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser;
-import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver;
-import org.gradle.api.internal.attributes.AttributeContainerInternal;
-import org.gradle.api.internal.attributes.ImmutableAttributes;
-import org.gradle.api.internal.component.SoftwareComponentInternal;
-import org.gradle.api.internal.component.UsageContext;
-import org.gradle.api.publish.internal.PublicationInternal;
-import org.gradle.api.publish.internal.versionmapping.VariantVersionMappingStrategyInternal;
-import org.gradle.api.publish.internal.versionmapping.VersionMappingStrategyInternal;
 import org.gradle.internal.hash.ChecksumService;
 import org.gradle.util.GradleVersion;
 
 import javax.annotation.Nullable;
 import java.io.File;
 import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static com.google.common.base.Strings.nullToEmpty;
-import static java.lang.String.format;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
 import static org.gradle.util.GUtil.elvis;
 
-
 class ModuleMetadataJsonWriter extends JsonWriterScope {
 
+    private final ModuleMetadata metadata;
+    @Nullable
     private final String buildId;
-    private final PublicationInternal<?> publication;
-    private final SoftwareComponentInternal component;
-    private final Map<SoftwareComponent, ComponentData> componentCoordinates;
-    private final Map<SoftwareComponent, SoftwareComponent> owners;
-    private final InvalidPublicationChecker checker;
-    private final ProjectDependencyPublicationResolver projectDependencyResolver;
     private final ChecksumService checksumService;
 
     public ModuleMetadataJsonWriter(
         JsonWriter jsonWriter,
-        InvalidPublicationChecker checker,
-        ChecksumService checksumService,
-        ProjectDependencyPublicationResolver projectDependencyResolver,
-        String buildId,
-        PublicationInternal<?> publication,
-        SoftwareComponentInternal component,
-        Map<SoftwareComponent, ComponentData> componentCoordinates,
-        Map<SoftwareComponent, SoftwareComponent> owners
+        ModuleMetadata metadata,
+        @Nullable String buildId,
+        ChecksumService checksumService
     ) {
         super(jsonWriter);
+        this.metadata = metadata;
         this.buildId = buildId;
-        this.publication = publication;
-        this.component = component;
-        this.componentCoordinates = componentCoordinates;
-        this.owners = owners;
-        this.checker = checker;
-        this.projectDependencyResolver = projectDependencyResolver;
         this.checksumService = checksumService;
     }
 
     void write() throws IOException {
-        SoftwareComponent owner = owners.get(component);
-        ComponentData ownerData = owner == null ? null : componentCoordinates.get(owner);
-        ComponentData componentData = componentCoordinates.get(component);
         writeObject(() -> {
             writeFormat();
-            writeIdentity(componentData, ownerData);
+            writeIdentity();
             writeCreator();
             writeVariants();
         });
@@ -116,23 +63,22 @@
         write("formatVersion", GradleModuleMetadataParser.FORMAT_VERSION);
     }
 
-    private void writeIdentity(
-        ComponentData component,
-        @Nullable ComponentData owner
-    ) throws IOException {
-        if (owner != null) {
-            String relativeUrl = relativeUrlTo(component.coordinates, owner.coordinates);
-            writeComponentRef(owner, relativeUrl);
-        } else {
-            writeComponentRef(component, null);
-        }
+    private void writeIdentity() throws IOException {
+        writeObject("component", () -> {
+            ModuleMetadata.Identity identity = metadata.identity;
+            if (identity.relativeUrl != null) {
+                write("url", identity.relativeUrl);
+            }
+            writeCoordinates(identity.coordinates);
+            writeAttributes(identity.attributes);
+        });
     }
 
     private void writeCreator() throws IOException {
         writeObject("createdBy", () ->
             writeObject("gradle", () -> {
                 write("version", GradleVersion.current().getVersion());
-                if (publication.isPublishBuildId()) {
+                if (buildId != null) {
                     write("buildId", buildId);
                 }
             })
@@ -140,121 +86,86 @@
     }
 
     private void writeVariants() throws IOException {
-        boolean started = false;
-        for (UsageContext usageContext : component.getUsages()) {
-            checkVariant(usageContext);
-            if (!started) {
-                beginArray("variants");
-                started = true;
-            }
-            writeVariantHostedInThisModule(usageContext);
-        }
-        if (component instanceof ComponentWithVariants) {
-            for (SoftwareComponent childComponent : ((ComponentWithVariants) component).getVariants()) {
-                ModuleVersionIdentifier childCoordinates = coordinatesOf(childComponent);
-                assert childCoordinates != null;
-                if (childComponent instanceof SoftwareComponentInternal) {
-                    for (UsageContext usageContext : ((SoftwareComponentInternal) childComponent).getUsages()) {
-                        checkVariant(usageContext);
-                        if (!started) {
-                            beginArray("variants");
-                            started = true;
-                        }
-                        writeVariantHostedInAnotherModule(publication.getCoordinates(), childCoordinates, usageContext);
-                    }
-                }
-            }
-        }
-        if (started) {
-            endArray();
-        }
-    }
-
-    private void writeVariantHostedInThisModule(UsageContext variant) throws IOException {
-        writeObject(() -> {
-            write("name", variant.getName());
-            writeAttributes(variant.getAttributes());
-            writeDependencies(variant);
-            writeDependencyConstraints(variant);
-            writeArtifacts(publication, variant);
-            writeCapabilities("capabilities", variant.getCapabilities());
-        });
-    }
-
-    private void writeVariantHostedInAnotherModule(ModuleVersionIdentifier coordinates, ModuleVersionIdentifier targetCoordinates, UsageContext variant) throws IOException {
-        writeObject(() -> {
-            write("name", variant.getName());
-            writeAttributes(variant.getAttributes());
-            writeAvailableAt(coordinates, targetCoordinates);
-            writeCapabilities("capabilities", variant.getCapabilities());
-        });
-    }
-
-    private void writeAttributes(AttributeContainer attributes) throws IOException {
-        if (attributes.isEmpty()) {
+        List<ModuleMetadata.Variant> variants = metadata.variants;
+        if (variants.isEmpty()) {
             return;
         }
-        writeObject("attributes", () -> {
-            for (Attribute<?> attribute : sorted(attributes).values()) {
-                String name = attribute.getName();
-                Object value = attributes.getAttribute(attribute);
-                if (!writeAttribute(name, value)) {
-                    throw new IllegalArgumentException(
-                        format("Cannot write attribute %s with unsupported value %s of type %s.", name, value, value.getClass().getName())
-                    );
+        writeArray("variants", () -> {
+            for (ModuleMetadata.Variant variant : variants) {
+                if (variant instanceof ModuleMetadata.LocalVariant) {
+                    ModuleMetadata.LocalVariant local = (ModuleMetadata.LocalVariant) variant;
+                    writeObject(() -> {
+                        write("name", local.name);
+                        writeAttributes(local.attributes);
+                        writeDependencies(local.dependencies);
+                        writeDependencyConstraints(local.dependencyConstraints);
+                        writeArtifacts(local.artifacts);
+                        writeCapabilities("capabilities", local.capabilities);
+                    });
+                    continue;
                 }
+                if (variant instanceof ModuleMetadata.RemoteVariant) {
+                    ModuleMetadata.RemoteVariant remote = (ModuleMetadata.RemoteVariant) variant;
+                    writeObject(() -> {
+                        write("name", remote.name);
+                        writeAttributes(remote.attributes);
+                        writeAvailableAt(remote.availableAt);
+                        writeCapabilities("capabilities", remote.capabilities);
+                    });
+                    continue;
+                }
+                throw new IllegalStateException("Unknown variant type: " + variant);
             }
         });
     }
 
-    private boolean writeAttribute(String name, Object value) throws IOException {
+    private void writeNonEmptyAttributes(List<ModuleMetadata.Attribute> attributes) throws IOException {
+        if (!attributes.isEmpty()) {
+            writeAttributes(attributes);
+        }
+    }
+
+    private void writeAttributes(List<ModuleMetadata.Attribute> attributes) throws IOException {
+        writeObject("attributes", () -> {
+            for (ModuleMetadata.Attribute attribute : attributes) {
+                writeAttribute(attribute.name, attribute.value);
+            }
+        });
+    }
+
+    private void writeAttribute(String name, Object value) throws IOException {
         if (value instanceof Boolean) {
             write(name, (Boolean) value);
         } else if (value instanceof Integer) {
             write(name, (Integer) value);
         } else if (value instanceof String) {
             write(name, (String) value);
-        } else if (value instanceof Named) {
-            write(name, ((Named) value).getName());
-        } else if (value instanceof Enum) {
-            write(name, ((Enum<?>) value).name());
         } else {
-            return false;
+            throw new IllegalArgumentException("value");
         }
-        return true;
     }
 
-    private void writeCapabilities(String key, Collection<? extends Capability> capabilities) throws IOException {
+    private void writeCapabilities(String key, List<ModuleMetadata.Capability> capabilities) throws IOException {
         if (capabilities.isEmpty()) {
             return;
         }
         writeArray(key, () -> {
-            for (Capability capability : capabilities) {
+            for (ModuleMetadata.Capability capability : capabilities) {
                 writeObject(() -> {
-                    write("group", capability.getGroup());
-                    write("name", capability.getName());
-                    if (isNotEmpty(capability.getVersion())) {
-                        write("version", capability.getVersion());
+                    write("group", capability.group);
+                    write("name", capability.name);
+                    if (capability.version != null) {
+                        write("version", capability.version);
                     }
                 });
             }
         });
     }
 
-    private void writeAvailableAt(ModuleVersionIdentifier coordinates, ModuleVersionIdentifier targetCoordinates) throws IOException {
+    private void writeAvailableAt(ModuleMetadata.AvailableAt availableAt) throws IOException {
         writeObject("available-at", () -> {
-            write("url", relativeUrlTo(coordinates, targetCoordinates));
-            writeCoordinates(targetCoordinates);
-        });
-    }
-
-    private void writeComponentRef(ComponentData data, @Nullable String relativeUrl) throws IOException {
-        writeObject("component", () -> {
-            if (relativeUrl != null) {
-                write("url", relativeUrl);
-            }
-            writeCoordinates(data.coordinates);
-            writeAttributes(data.attributes);
+            write("url", availableAt.url);
+            writeCoordinates(availableAt.coordinates);
         });
     }
 
@@ -264,230 +175,109 @@
         write("version", coordinates.getVersion());
     }
 
-    private void writeArtifacts(PublicationInternal<?> publication, UsageContext variant) throws IOException {
-        if (variant.getArtifacts().isEmpty()) {
+    private void writeArtifacts(List<ModuleMetadata.Artifact> artifacts) throws IOException {
+        if (artifacts.isEmpty()) {
             return;
         }
         writeArray("files", () -> {
-            for (PublishArtifact artifact : variant.getArtifacts()) {
-                writeArtifact(publication, artifact);
+            for (ModuleMetadata.Artifact artifact : artifacts) {
+                writeObject(() -> {
+                    write("name", artifact.name);
+                    write("url", artifact.uri);
+                    File file = artifact.file;
+                    write("size", file.length());
+                    write("sha512", sha512(file));
+                    write("sha256", sha256(file));
+                    write("sha1", sha1(file));
+                    write("md5", md5(file));
+                });
             }
         });
     }
 
-    private void writeArtifact(PublicationInternal<?> publication, PublishArtifact artifact) throws IOException {
-        if (artifact instanceof PublishArtifactInternal) {
-            if (!((PublishArtifactInternal) artifact).shouldBePublished()) {
-                return;
-            }
-        }
-        PublicationInternal.PublishedFile publishedFile = publication.getPublishedFile(artifact);
-        File artifactFile = artifact.getFile();
-
-        writeObject(() -> {
-            write("name", publishedFile.getName());
-            write("url", publishedFile.getUri());
-            write("size", artifactFile.length());
-            writeChecksumsOf(artifactFile);
-        });
-    }
-
-    private void writeChecksumsOf(File artifactFile) throws IOException {
-        write("sha512", checksumService.sha512(artifactFile).toString());
-        write("sha256", checksumService.sha256(artifactFile).toString());
-        write("sha1", checksumService.sha1(artifactFile).toString());
-        write("md5", checksumService.md5(artifactFile).toString());
-    }
-
-    private void writeDependencies(UsageContext variant) throws IOException {
-        if (variant.getDependencies().isEmpty()) {
+    private void writeDependencies(List<ModuleMetadata.Dependency> dependencies) throws IOException {
+        if (dependencies.isEmpty()) {
             return;
         }
         writeArray("dependencies", () -> {
-            Set<ExcludeRule> additionalExcludes = variant.getGlobalExcludes();
-            VariantVersionMappingStrategyInternal variantVersionMappingStrategy = findVariantVersionMappingStrategy(variant);
-            for (ModuleDependency moduleDependency : variant.getDependencies()) {
-                if (moduleDependency.getArtifacts().isEmpty()) {
-                    writeDependency(moduleDependency, additionalExcludes, variantVersionMappingStrategy, null);
-                } else {
-                    for (DependencyArtifact dependencyArtifact : moduleDependency.getArtifacts()) {
-                        writeDependency(moduleDependency, additionalExcludes, variantVersionMappingStrategy, dependencyArtifact);
+            for (ModuleMetadata.Dependency moduleDependency : dependencies) {
+                writeObject(() -> {
+                    ModuleMetadata.DependencyCoordinates identifier = moduleDependency.coordinates;
+                    write("group", identifier.group);
+                    write("module", identifier.name);
+                    writeVersionConstraint(identifier.version);
+                    writeExcludes(moduleDependency.excludeRules);
+                    writeNonEmptyAttributes(moduleDependency.attributes);
+                    writeCapabilities("requestedCapabilities", moduleDependency.requestedCapabilities);
+                    if (moduleDependency.endorseStrictVersions) {
+                        write("endorseStrictVersions", true);
                     }
+                    if (moduleDependency.reason != null) {
+                        write("reason", moduleDependency.reason);
+                    }
+                    if (moduleDependency.artifactSelector != null) {
+                        writeDependencyArtifact(moduleDependency.artifactSelector);
+                    }
+                });
+            }
+        });
+    }
+
+    private void writeVersionConstraint(@Nullable ModuleMetadata.Version version) throws IOException {
+        if (version == null) {
+            return;
+        }
+        writeObject("version", () -> {
+            if (version.strictly != null) {
+                write("strictly", version.strictly);
+            }
+            if (version.requires != null) {
+                write("requires", version.requires);
+            }
+            if (version.preferred != null) {
+                write("prefers", version.preferred);
+            }
+            if (!version.rejectedVersions.isEmpty()) {
+                writeArray("rejects", version.rejectedVersions);
+            }
+        });
+    }
+
+    private void writeDependencyArtifact(ModuleMetadata.ArtifactSelector artifactSelector) throws IOException {
+        writeObject("thirdPartyCompatibility", () ->
+            writeObject("artifactSelector", () -> {
+                write("name", artifactSelector.name);
+                write("type", artifactSelector.type);
+                if (artifactSelector.extension != null) {
+                    write("extension", artifactSelector.extension);
                 }
-            }
-        });
-    }
-
-    private void writeDependency(ModuleDependency dependency, Set<ExcludeRule> additionalExcludes, VariantVersionMappingStrategyInternal variantVersionMappingStrategy, DependencyArtifact dependencyArtifact) throws IOException {
-        writeObject(() -> {
-            if (dependency instanceof ProjectDependency) {
-                writeProjectDependency((ProjectDependency) dependency, variantVersionMappingStrategy);
-            } else {
-                writeModuleDependency(dependency, variantVersionMappingStrategy);
-            }
-            writeExcludes(dependency, additionalExcludes);
-            writeAttributes(dependency.getAttributes());
-            writeCapabilities("requestedCapabilities", dependency.getRequestedCapabilities());
-
-            boolean endorsing = dependency.isEndorsingStrictVersions();
-            if (endorsing) {
-                write("endorseStrictVersions", true);
-            }
-            String reason = dependency.getReason();
-            if (isNotEmpty(reason)) {
-                write("reason", reason);
-            }
-            if (dependencyArtifact != null) {
-                writeDependencyArtifact(dependencyArtifact);
-            }
-        });
-    }
-
-    private void writeModuleDependency(ModuleDependency dependency, VariantVersionMappingStrategyInternal variantVersionMappingStrategy) throws IOException {
-        String group = dependency.getGroup();
-        String name = dependency.getName();
-        String resolvedVersion = null;
-        if (variantVersionMappingStrategy != null) {
-            ModuleVersionIdentifier resolvedVersionId = variantVersionMappingStrategy.maybeResolveVersion(group, name);
-            if (resolvedVersionId != null) {
-                group = resolvedVersionId.getGroup();
-                name = resolvedVersionId.getName();
-                resolvedVersion = resolvedVersionId.getVersion();
-            }
-        }
-        write("group", group);
-        write("module", name);
-        writeVersionConstraint(versionConstraintFor(dependency), resolvedVersion);
-    }
-
-    private void writeProjectDependency(
-        ProjectDependency projectDependency,
-        VariantVersionMappingStrategyInternal variantVersionMappingStrategy
-    ) throws IOException {
-        String resolvedVersion = null;
-        ModuleVersionIdentifier identifier = projectDependencyResolver.resolve(ModuleVersionIdentifier.class, projectDependency);
-        if (variantVersionMappingStrategy != null) {
-            ModuleVersionIdentifier resolved = variantVersionMappingStrategy.maybeResolveVersion(
-                identifier.getGroup(), identifier.getName()
-            );
-            if (resolved != null) {
-                identifier = resolved;
-                resolvedVersion = identifier.getVersion();
-            }
-        }
-        write("group", identifier.getGroup());
-        write("module", identifier.getName());
-        writeVersionConstraint(
-            DefaultImmutableVersionConstraint.of(identifier.getVersion()), resolvedVersion
+                if (artifactSelector.classifier != null) {
+                    write("classifier", artifactSelector.classifier);
+                }
+            })
         );
     }
 
-    private void writeVersionConstraint(
-        ImmutableVersionConstraint versionConstraint,
-        @Nullable String resolvedVersion
-    ) throws IOException {
-        checker.sawDependencyOrConstraint();
-        if (resolvedVersion == null && isEmpty(versionConstraint)) {
-            return;
-        }
-        checker.sawVersion();
-
-        writeObject("version", () -> {
-            boolean isStrict = !versionConstraint.getStrictVersion().isEmpty();
-            String version;
-            String preferred;
-            if (resolvedVersion != null) {
-                version = resolvedVersion;
-                preferred = null;
-            } else {
-                version = isStrict
-                    ? versionConstraint.getStrictVersion()
-                    : !versionConstraint.getRequiredVersion().isEmpty()
-                    ? versionConstraint.getRequiredVersion()
-                    : null;
-                preferred = !versionConstraint.getPreferredVersion().isEmpty()
-                    ? versionConstraint.getPreferredVersion()
-                    : null;
-            }
-            List<String> rejectedVersions = versionConstraint.getRejectedVersions();
-            if (version != null) {
-                if (isStrict) {
-                    write("strictly", version);
-                }
-                write("requires", version);
-            }
-            if (preferred != null) {
-                write("prefers", preferred);
-            }
-            if (!rejectedVersions.isEmpty()) {
-                writeArray("rejects", rejectedVersions);
-            }
-        });
-    }
-
-    private void writeDependencyArtifact(DependencyArtifact dependencyArtifact) throws IOException {
-        writeObject("thirdPartyCompatibility", () -> {
-            writeObject("artifactSelector", () -> {
-                write("name", dependencyArtifact.getName());
-                write("type", dependencyArtifact.getType());
-                if (!isNullOrEmpty(dependencyArtifact.getExtension())) {
-                    write("extension", dependencyArtifact.getExtension());
-                }
-                if (!isNullOrEmpty(dependencyArtifact.getClassifier())) {
-                    write("classifier", dependencyArtifact.getClassifier());
-                }
-            });
-        });
-    }
-
-    private void writeDependencyConstraints(UsageContext variant) throws IOException {
-        if (variant.getDependencyConstraints().isEmpty()) {
+    private void writeDependencyConstraints(List<ModuleMetadata.DependencyConstraint> constraints) throws IOException {
+        if (constraints.isEmpty()) {
             return;
         }
         writeArray("dependencyConstraints", () -> {
-            VariantVersionMappingStrategyInternal mappingStrategy = findVariantVersionMappingStrategy(variant);
-            for (DependencyConstraint dependencyConstraint : variant.getDependencyConstraints()) {
-                writeDependencyConstraint(dependencyConstraint, mappingStrategy);
+            for (ModuleMetadata.DependencyConstraint constraint : constraints) {
+                writeObject(() -> {
+                    write("group", constraint.group);
+                    write("module", constraint.module);
+                    writeVersionConstraint(constraint.version);
+                    writeNonEmptyAttributes(constraint.attributes);
+                    if (constraint.reason != null) {
+                        write("reason", constraint.reason);
+                    }
+                });
             }
         });
     }
 
-    private void writeDependencyConstraint(DependencyConstraint dependencyConstraint, VariantVersionMappingStrategyInternal variantVersionMappingStrategy) throws IOException {
-        writeObject(() -> {
-            String group;
-            String module;
-            String resolvedVersion = null;
-            if (dependencyConstraint instanceof DefaultProjectDependencyConstraint) {
-                DefaultProjectDependencyConstraint dependency = (DefaultProjectDependencyConstraint) dependencyConstraint;
-                ProjectDependency projectDependency = dependency.getProjectDependency();
-                ModuleVersionIdentifier identifier = projectDependencyResolver.resolve(ModuleVersionIdentifier.class, projectDependency);
-                group = identifier.getGroup();
-                module = identifier.getName();
-                resolvedVersion = identifier.getVersion();
-            } else {
-                group = dependencyConstraint.getGroup();
-                module = dependencyConstraint.getName();
-            }
-            ModuleVersionIdentifier resolvedVersionId = variantVersionMappingStrategy != null ? variantVersionMappingStrategy.maybeResolveVersion(group, module) : null;
-            String effectiveGroup = resolvedVersionId != null ? resolvedVersionId.getGroup() : group;
-            String effectiveModule = resolvedVersionId != null ? resolvedVersionId.getName() : module;
-            String effectiveVersion = resolvedVersionId != null ? resolvedVersionId.getVersion() : resolvedVersion;
-            write("group", effectiveGroup);
-            write("module", effectiveModule);
-            writeVersionConstraint(
-                DefaultImmutableVersionConstraint.of(dependencyConstraint.getVersionConstraint()),
-                effectiveVersion
-            );
-            writeAttributes(dependencyConstraint.getAttributes());
-            String reason = dependencyConstraint.getReason();
-            if (isNotEmpty(reason)) {
-                write("reason", reason);
-            }
-        });
-    }
-
-    private void writeExcludes(ModuleDependency moduleDependency, Set<ExcludeRule> additionalExcludes) throws IOException {
-        Set<ExcludeRule> excludeRules = excludedRulesFor(moduleDependency, additionalExcludes);
+    private void writeExcludes(Set<ExcludeRule> excludeRules) throws IOException {
         if (excludeRules.isEmpty()) {
             return;
         }
@@ -501,73 +291,19 @@
         });
     }
 
-    private ImmutableVersionConstraint versionConstraintFor(ModuleDependency dependency) {
-        return dependency instanceof ExternalDependency
-            ? DefaultImmutableVersionConstraint.of(((ExternalDependency) dependency).getVersionConstraint())
-            : DefaultImmutableVersionConstraint.of(nullToEmpty(dependency.getVersion()));
+    private String md5(File file) {
+        return checksumService.md5(file).toString();
     }
 
-    private Set<ExcludeRule> excludedRulesFor(ModuleDependency moduleDependency, Set<ExcludeRule> additionalExcludes) {
-        return moduleDependency.isTransitive()
-            ? Sets.union(additionalExcludes, moduleDependency.getExcludeRules())
-            : Collections.singleton(new DefaultExcludeRule(null, null));
+    private String sha1(File file) {
+        return checksumService.sha1(file).toString();
     }
 
-    private void checkVariant(UsageContext usageContext) {
-        checker.registerVariant(
-            usageContext.getName(),
-            usageContext.getAttributes(),
-            usageContext.getCapabilities()
-        );
+    private String sha256(File file) {
+        return checksumService.sha256(file).toString();
     }
 
-    private ModuleVersionIdentifier coordinatesOf(SoftwareComponent childComponent) {
-        if (childComponent instanceof ComponentWithCoordinates) {
-            return ((ComponentWithCoordinates) childComponent).getCoordinates();
-        }
-        ComponentData componentData = componentCoordinates.get(childComponent);
-        if (componentData != null) {
-            return componentData.coordinates;
-        }
-        return null;
-    }
-
-    private VariantVersionMappingStrategyInternal findVariantVersionMappingStrategy(UsageContext variant) {
-        VersionMappingStrategyInternal versionMappingStrategy = publication.getVersionMappingStrategy();
-        if (versionMappingStrategy != null) {
-            ImmutableAttributes attributes = ((AttributeContainerInternal) variant.getAttributes()).asImmutable();
-            return versionMappingStrategy.findStrategyForVariant(attributes);
-        }
-        return null;
-    }
-
-    private boolean isEmpty(ImmutableVersionConstraint versionConstraint) {
-        return DefaultImmutableVersionConstraint.of().equals(versionConstraint);
-    }
-
-    private String relativeUrlTo(
-        @SuppressWarnings("unused") 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 Map<String, Attribute<?>> sorted(AttributeContainer attributes) {
-        Map<String, Attribute<?>> sortedAttributes = new TreeMap<>();
-        for (Attribute<?> attribute : attributes.keySet()) {
-            sortedAttributes.put(attribute.getName(), attribute);
-        }
-        return sortedAttributes;
+    private String sha512(File file) {
+        return checksumService.sha512(file).toString();
     }
 }
diff --git a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriterTest.groovy b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriterTest.groovy
index c2cdecb..3398f0e 100644
--- a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriterTest.groovy
+++ b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/metadata/GradleModuleMetadataWriterTest.groovy
@@ -642,7 +642,6 @@
             getVersion() >> '1'
         }
 
-
         def v1 = Stub(UsageContext)
         v1.name >> "v1"
         v1.attributes >> attributes(usage: "compile")