org.gradle.api.internalivyservice.ivyresolve.parser.GradleModuleMetadataParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.5 API redistribution.
/*
* 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.internal.artifacts.ivyservice.ivyresolve.parser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import org.apache.commons.lang.StringUtils;
import org.gradle.api.artifacts.VersionConstraint;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.Category;
import org.gradle.api.capabilities.Capability;
import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory;
import org.gradle.api.internal.artifacts.ImmutableVersionConstraint;
import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint;
import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DefaultExcludeRuleConverter;
import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ExcludeRuleConverter;
import org.gradle.api.internal.artifacts.repositories.metadata.MavenImmutableAttributesFactory;
import org.gradle.api.internal.attributes.AttributeValue;
import org.gradle.api.internal.attributes.ImmutableAttributes;
import org.gradle.api.internal.attributes.ImmutableAttributesFactory;
import org.gradle.api.internal.model.NamedObjectInstantiator;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.internal.component.external.model.DefaultShadowedCapability;
import org.gradle.internal.component.external.model.ImmutableCapability;
import org.gradle.internal.component.external.model.MutableComponentVariant;
import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetadata;
import org.gradle.internal.component.model.DefaultIvyArtifactName;
import org.gradle.internal.component.model.ExcludeMetadata;
import org.gradle.internal.component.model.IvyArtifactName;
import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
import org.gradle.internal.snapshot.impl.CoercingStringValueSnapshot;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.google.gson.stream.JsonToken.BOOLEAN;
import static com.google.gson.stream.JsonToken.END_ARRAY;
import static com.google.gson.stream.JsonToken.END_OBJECT;
import static com.google.gson.stream.JsonToken.NULL;
import static com.google.gson.stream.JsonToken.NUMBER;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang.StringUtils.capitalize;
public class GradleModuleMetadataParser {
private final static Logger LOGGER = Logging.getLogger(GradleModuleMetadataParser.class);
public static final String FORMAT_VERSION = "1.1";
private final ImmutableAttributesFactory attributesFactory;
private final NamedObjectInstantiator instantiator;
private final ExcludeRuleConverter excludeRuleConverter;
public GradleModuleMetadataParser(ImmutableAttributesFactory attributesFactory, ImmutableModuleIdentifierFactory moduleIdentifierFactory, NamedObjectInstantiator instantiator) {
this.attributesFactory = attributesFactory;
this.instantiator = instantiator;
this.excludeRuleConverter = new DefaultExcludeRuleConverter(moduleIdentifierFactory);
}
public ImmutableAttributesFactory getAttributesFactory() {
return attributesFactory;
}
public NamedObjectInstantiator getInstantiator() {
return instantiator;
}
public void parse(final LocallyAvailableExternalResource resource, final MutableModuleComponentResolveMetadata metadata) {
resource.withContent(inputStream -> {
String version = null;
try {
JsonReader reader = new JsonReader(new InputStreamReader(inputStream, UTF_8));
reader.beginObject();
if (reader.peek() != JsonToken.NAME) {
throw new RuntimeException("Module metadata should contain a 'formatVersion' value.");
}
String name = reader.nextName();
if (!name.equals("formatVersion")) {
throw new RuntimeException(String.format("The 'formatVersion' value should be the first value in a module metadata. Found '%s' instead.", name));
}
if (reader.peek() != JsonToken.STRING) {
throw new RuntimeException("The 'formatVersion' value should have a string value.");
}
version = reader.nextString();
consumeTopLevelElements(reader, metadata);
File file = resource.getFile();
if (!FORMAT_VERSION.equals(version)) {
LOGGER.debug("Unrecognized metadata format version '{}' found in '{}'. Parsing succeeded but it may lead to unexpected resolution results. Try upgrading to a newer version of Gradle", version, file);
}
return null;
} catch (Exception e) {
if (version != null && !FORMAT_VERSION.equals(version)) {
throw new MetaDataParseException("module metadata", resource, String.format("unsupported format version '%s' specified in module metadata. This version of Gradle supports format version %s.", version, FORMAT_VERSION), e);
}
throw new MetaDataParseException("module metadata", resource, e);
}
});
maybeAddEnforcedPlatformVariant(metadata);
}
private void maybeAddEnforcedPlatformVariant(MutableModuleComponentResolveMetadata metadata) {
List variants = metadata.getMutableVariants();
if (variants == null || variants.isEmpty()) {
return;
}
for (MutableComponentVariant variant : ImmutableList.copyOf(variants)) {
AttributeValue entry = variant.getAttributes().findEntry(MavenImmutableAttributesFactory.CATEGORY_ATTRIBUTE);
if (entry.isPresent() && Category.REGULAR_PLATFORM.equals(entry.get()) && variant.getCapabilities().isEmpty()) {
// This generates a synthetic enforced platform variant with the same dependencies, similar to what the Maven variant derivation strategy does
ImmutableAttributes enforcedAttributes = attributesFactory.concat(variant.getAttributes(), MavenImmutableAttributesFactory.CATEGORY_ATTRIBUTE, new CoercingStringValueSnapshot(Category.ENFORCED_PLATFORM, instantiator));
Capability enforcedCapability = buildShadowPlatformCapability(metadata.getId());
metadata.addVariant(variant.copy("enforced" + capitalize(variant.getName()), enforcedAttributes, enforcedCapability));
}
}
}
private Capability buildShadowPlatformCapability(ModuleComponentIdentifier componentId) {
return new DefaultShadowedCapability(new ImmutableCapability(
componentId.getGroup(),
componentId.getModule(),
componentId.getVersion()
), "-derived-enforced-platform");
}
private void consumeTopLevelElements(JsonReader reader, MutableModuleComponentResolveMetadata metadata) throws IOException {
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "variants":
consumeVariants(reader, metadata);
break;
case "component":
consumeComponent(reader, metadata);
break;
default:
consumeAny(reader);
break;
}
}
}
private void consumeComponent(JsonReader reader, MutableModuleComponentResolveMetadata metadata) throws IOException {
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
//noinspection SwitchStatementWithTooFewBranches
switch (name) {
case "attributes":
metadata.setAttributes(consumeAttributes(reader));
break;
default:
consumeAny(reader);
break;
}
}
reader.endObject();
}
private void consumeVariants(JsonReader reader, MutableModuleComponentResolveMetadata metadata) throws IOException {
reader.beginArray();
while (reader.peek() != JsonToken.END_ARRAY) {
consumeVariant(reader, metadata);
}
reader.endArray();
}
private void consumeVariant(JsonReader reader, MutableModuleComponentResolveMetadata metadata) throws IOException {
String variantName = null;
ImmutableAttributes attributes = ImmutableAttributes.EMPTY;
List files = Collections.emptyList();
List dependencies = Collections.emptyList();
List dependencyConstraints = Collections.emptyList();
List capabilities = Collections.emptyList();
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "name":
variantName = reader.nextString();
break;
case "attributes":
attributes = consumeAttributes(reader);
break;
case "files":
files = consumeFiles(reader);
break;
case "dependencies":
dependencies = consumeDependencies(reader);
break;
case "dependencyConstraints":
dependencyConstraints = consumeDependencyConstraints(reader);
break;
case "capabilities":
capabilities = consumeCapabilities(reader, true);
break;
case "available-at":
// For now just collect this as another dependency
// TODO - collect this information and merge the metadata from the other module
dependencies = consumeVariantLocation(reader);
break;
default:
consumeAny(reader);
break;
}
}
assertDefined(reader, "name", variantName);
reader.endObject();
MutableComponentVariant variant = metadata.addVariant(variantName, attributes);
populateVariant(files, dependencies, dependencyConstraints, capabilities, variant);
}
private void populateVariant(List files, List dependencies, List dependencyConstraints, List capabilities, MutableComponentVariant variant) {
for (ModuleFile file : files) {
variant.addFile(file.name, file.uri);
}
for (ModuleDependency dependency : dependencies) {
variant.addDependency(dependency.group, dependency.module, dependency.versionConstraint, dependency.excludes, dependency.reason, dependency.attributes, dependency.requestedCapabilities, dependency.endorsing, dependency.artifact);
}
for (ModuleDependencyConstraint dependencyConstraint : dependencyConstraints) {
variant.addDependencyConstraint(dependencyConstraint.group, dependencyConstraint.module, dependencyConstraint.versionConstraint, dependencyConstraint.reason, dependencyConstraint.attributes);
}
for (VariantCapability capability : capabilities) {
variant.addCapability(capability.group, capability.name, capability.version);
}
}
private List consumeVariantLocation(JsonReader reader) throws IOException {
String url = null;
String group = null;
String module = null;
String version = null;
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "url":
url = reader.nextString();
break;
case "group":
group = reader.nextString();
break;
case "module":
module = reader.nextString();
break;
case "version":
version = reader.nextString();
break;
default:
consumeAny(reader);
break;
}
}
assertDefined(reader, "url", url);
assertDefined(reader, "group", group);
assertDefined(reader, "module", module);
assertDefined(reader, "version", version);
reader.endObject();
return ImmutableList.of(new ModuleDependency(group, module, new DefaultImmutableVersionConstraint(version), ImmutableList.of(), null, ImmutableAttributes.EMPTY, Collections.emptyList(), false, null));
}
private List consumeDependencies(JsonReader reader) throws IOException {
List dependencies = new ArrayList<>();
reader.beginArray();
while (reader.peek() != END_ARRAY) {
String group = null;
String module = null;
String reason = null;
ImmutableAttributes attributes = ImmutableAttributes.EMPTY;
VersionConstraint version = DefaultImmutableVersionConstraint.of();
ImmutableList excludes = ImmutableList.of();
List requestedCapabilities = ImmutableList.of();
IvyArtifactName artifactSelector = null;
boolean endorseStrictVersions = false;
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "group":
group = reader.nextString();
break;
case "module":
module = reader.nextString();
break;
case "version":
version = consumeVersion(reader);
break;
case "excludes":
excludes = consumeExcludes(reader);
break;
case "reason":
reason = reader.nextString();
break;
case "attributes":
attributes = consumeAttributes(reader);
break;
case "requestedCapabilities":
requestedCapabilities = consumeCapabilities(reader, false);
break;
case "endorseStrictVersions":
endorseStrictVersions = reader.nextBoolean();
break;
case "thirdPartyCompatibility":
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String compatibilityFeatureName = reader.nextName();
if (compatibilityFeatureName.equals("artifactSelector")) {
artifactSelector = consumeArtifactSelector(reader);
} else {
consumeAny(reader);
}
}
reader.endObject();
break;
default:
consumeAny(reader);
break;
}
}
assertDefined(reader, "group", group);
assertDefined(reader, "module", module);
reader.endObject();
dependencies.add(new ModuleDependency(group, module, version, excludes, reason, attributes, requestedCapabilities, endorseStrictVersions, artifactSelector));
}
reader.endArray();
return dependencies;
}
private IvyArtifactName consumeArtifactSelector(JsonReader reader) throws IOException {
reader.beginObject();
String artifactName = null;
String type = null;
String extension = null;
String classifier = null;
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "name":
artifactName = reader.nextString();
break;
case "type":
type = reader.nextString();
break;
case "extension":
extension = reader.nextString();
break;
case "classifier":
classifier = reader.nextString();
break;
default:
consumeAny(reader);
break;
}
}
assertDefined(reader, "name", artifactName);
assertDefined(reader, "type", type);
reader.endObject();
return new DefaultIvyArtifactName(artifactName, type, extension, classifier);
}
private List consumeCapabilities(JsonReader reader, boolean versionRequired) throws IOException {
ImmutableList.Builder capabilities = ImmutableList.builder();
reader.beginArray();
while (reader.peek() != END_ARRAY) {
String group = null;
String name = null;
String version = null;
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String val = reader.nextName();
switch (val) {
case "group":
group = reader.nextString();
break;
case "name":
name = reader.nextString();
break;
case "version":
if (reader.peek() == NULL) {
reader.nextNull();
} else {
version = reader.nextString();
}
break;
}
}
assertDefined(reader, "group", group);
assertDefined(reader, "name", name);
if (versionRequired) {
assertDefined(reader, "version", version);
}
reader.endObject();
capabilities.add(new VariantCapability(group, name, version));
}
reader.endArray();
return capabilities.build();
}
private List consumeDependencyConstraints(JsonReader reader) throws IOException {
List dependencies = new ArrayList<>();
reader.beginArray();
while (reader.peek() != END_ARRAY) {
String group = null;
String module = null;
String reason = null;
VersionConstraint version = null;
ImmutableAttributes attributes = ImmutableAttributes.EMPTY;
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "group":
group = reader.nextString();
break;
case "module":
module = reader.nextString();
break;
case "version":
version = consumeVersion(reader);
break;
case "reason":
reason = reader.nextString();
break;
case "attributes":
attributes = consumeAttributes(reader);
break;
default:
consumeAny(reader);
break;
}
}
assertDefined(reader, "group", group);
assertDefined(reader, "module", module);
reader.endObject();
dependencies.add(new ModuleDependencyConstraint(group, module, version, reason, attributes));
}
reader.endArray();
return dependencies;
}
private ImmutableVersionConstraint consumeVersion(JsonReader reader) throws IOException {
String requiredVersion = "";
String preferredVersion = "";
String strictVersion = "";
List rejects = Lists.newArrayList();
reader.beginObject();
while (reader.peek() != END_OBJECT) {
// At this stage, 'strictly' implies 'requires'.
String cst = reader.nextName();
switch (cst) {
case "prefers":
preferredVersion = reader.nextString();
break;
case "requires":
requiredVersion = reader.nextString();
break;
case "strictly":
strictVersion = reader.nextString();
break;
case "rejects":
reader.beginArray();
while (reader.peek() != END_ARRAY) {
rejects.add(reader.nextString());
}
reader.endArray();
break;
default:
consumeAny(reader);
break;
}
}
reader.endObject();
return DefaultImmutableVersionConstraint.of(preferredVersion, requiredVersion, strictVersion, rejects);
}
private ImmutableList consumeExcludes(JsonReader reader) throws IOException {
ImmutableList.Builder builder = new ImmutableList.Builder<>();
reader.beginArray();
while (reader.peek() != END_ARRAY) {
String group = null;
String module = null;
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "group":
group = reader.nextString();
break;
case "module":
module = reader.nextString();
break;
default:
consumeAny(reader);
break;
}
}
reader.endObject();
ExcludeMetadata exclude = excludeRuleConverter.createExcludeRule(group, module);
builder.add(exclude);
}
reader.endArray();
return builder.build();
}
private List consumeFiles(JsonReader reader) throws IOException {
List files = new ArrayList<>();
reader.beginArray();
while (reader.peek() != END_ARRAY) {
String fileName = null;
String fileUrl = null;
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String name = reader.nextName();
switch (name) {
case "name":
fileName = reader.nextString();
break;
case "url":
fileUrl = reader.nextString();
break;
default:
consumeAny(reader);
break;
}
}
assertDefined(reader, "name", fileName);
assertDefined(reader, "url", fileUrl);
reader.endObject();
files.add(new ModuleFile(fileName, fileUrl));
}
reader.endArray();
return files;
}
private ImmutableAttributes consumeAttributes(JsonReader reader) throws IOException {
ImmutableAttributes attributes = ImmutableAttributes.EMPTY;
reader.beginObject();
while (reader.peek() != END_OBJECT) {
String attrName = reader.nextName();
if (reader.peek() == BOOLEAN) {
boolean attrValue = reader.nextBoolean();
attributes = attributesFactory.concat(attributes, Attribute.of(attrName, Boolean.class), attrValue);
} else if (reader.peek() == NUMBER) {
Integer attrValue = reader.nextInt();
attributes = attributesFactory.concat(attributes, Attribute.of(attrName, Integer.class), attrValue);
} else {
String attrValue = reader.nextString();
attributes = attributesFactory.concat(attributes, Attribute.of(attrName, String.class), new CoercingStringValueSnapshot(attrValue, instantiator));
}
}
reader.endObject();
return attributes;
}
private void consumeAny(JsonReader reader) throws IOException {
reader.skipValue();
}
private void assertDefined(JsonReader reader, String attribute, String value) {
if (StringUtils.isEmpty(value)) {
String path = reader.getPath();
// remove leading '$', remove last child segment, use '/' as separator
throw new RuntimeException("missing '" + attribute + "' at " + path.substring(1, path.lastIndexOf('.')).replace('.', '/'));
}
}
private static class ModuleFile {
final String name;
final String uri;
ModuleFile(String name, String uri) {
this.name = name;
this.uri = uri;
}
}
private static class ModuleDependency {
final String group;
final String module;
final VersionConstraint versionConstraint;
final ImmutableList excludes;
final String reason;
final ImmutableAttributes attributes;
final List requestedCapabilities;
final boolean endorsing;
final IvyArtifactName artifact;
ModuleDependency(String group, String module, VersionConstraint versionConstraint, ImmutableList excludes, String reason, ImmutableAttributes attributes, List requestedCapabilities, boolean endorsing, IvyArtifactName artifact) {
this.group = group;
this.module = module;
this.versionConstraint = versionConstraint;
this.excludes = excludes;
this.reason = reason;
this.attributes = attributes;
this.requestedCapabilities = requestedCapabilities;
this.endorsing = endorsing;
this.artifact = artifact;
}
}
private static class ModuleDependencyConstraint {
final String group;
final String module;
final VersionConstraint versionConstraint;
final String reason;
final ImmutableAttributes attributes;
ModuleDependencyConstraint(String group, String module, VersionConstraint versionConstraint, String reason, ImmutableAttributes attributes) {
this.group = group;
this.module = module;
this.versionConstraint = versionConstraint;
this.reason = reason;
this.attributes = attributes;
}
}
private static class VariantCapability implements Capability {
final String group;
final String name;
final String version;
private VariantCapability(String group, String name, String version) {
this.group = group;
this.name = name;
this.version = version;
}
@Override
public String getGroup() {
return group;
}
@Override
public String getName() {
return name;
}
@Override
public String getVersion() {
return version;
}
}
}