/* * Copyright 2016 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.reporting.dependencies.internal; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import groovy.json.JsonBuilder; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.Transformer; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ModuleIdentifier; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.result.DependencyResult; import org.gradle.api.artifacts.result.ResolutionResult; import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionComparator; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency; import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableModuleResult; import org.gradle.api.tasks.diagnostics.internal.graph.nodes.UnresolvableConfigurationResult; import org.gradle.api.tasks.diagnostics.internal.insight.DependencyInsightReporter; import org.gradle.util.CollectionUtils; import org.gradle.util.GradleVersion; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Renderer that emits a JSON tree containing the HTML dependency report structure for a given project. The structure is the following: * * <pre> * { * "gradleVersion" : "...", * "generationDate" : "...", * "project" : { * "name" : "...", * "description : "...", (optional) * "configurations" : [ * "name" : "...", * "description" : "...", (optional) * "dependencies" : [ * { * "module" : "group:name" * "name" : "...", * "resolvable" : true|false, * "alreadyRendered" : true|false * "hasConflict" : true|false * "children" : [ * same array as configurations.dependencies.children * ] * }, * ... * ], * "moduleInsights : [ * { * "module" : "group:name" * "insight" : [ * { * "name" : "...", * "description" : "...", * "resolvable" : true|false, * "hasConflict" : true|false, * "children": [ * { * "name" : "...", * "resolvable" : "...", * "hasConflict" : true|false, * "alreadyRendered" : true|false * "isLeaf" : true|false * "children" : [ * same array as configurations.moduleInsights.insight.children * ] * }, * ... * ] * }, * ... * ] * } * , * ... * ] * ] * } * } * </pre> */ public class JsonProjectDependencyRenderer { public JsonProjectDependencyRenderer(VersionSelectorScheme versionSelectorScheme, VersionComparator versionComparator) { this.versionSelectorScheme = versionSelectorScheme; this.versionComparator = versionComparator; } /** * Generates the project dependency report structure * * @param project the project for which the report must be generated * @return the generated JSON, as a String */ public String render(Project project) { JsonBuilder json = new JsonBuilder(); renderProject(project, json); return json.toString(); } // Historic note: this class still uses the Groovy JsonBuilder, as it was originally developed as a Groovy class. private void renderProject(Project project, JsonBuilder json) { Map<String, Object> overall = Maps.newLinkedHashMap(); overall.put("gradleVersion", GradleVersion.current().toString()); overall.put("generationDate", new Date().toString()); Map<String, Object> projectOut = Maps.newLinkedHashMap(); projectOut.put("name", project.getName()); projectOut.put("description", project.getDescription()); projectOut.put("configurations", createConfigurations(project)); overall.put("project", projectOut); json.call(overall); } private List<Map> createConfigurations(Project project) { Iterable<Configuration> configurations = project.getConfigurations(); return CollectionUtils.collect(configurations, new Transformer<Map, Configuration>() { @Override public Map transform(Configuration configuration) { LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(4); map.put("name", configuration.getName()); map.put("description", configuration.getDescription()); map.put("dependencies", createDependencies(configuration)); map.put("moduleInsights", createModuleInsights(configuration)); return map; } }); } private List createDependencies(Configuration configuration) { if (configuration.isCanBeResolved()) { ResolutionResult result = configuration.getIncoming().getResolutionResult(); RenderableDependency root = new RenderableModuleResult(result.getRoot()); return createDependencyChildren(root, new HashSet<Object>()); } else { return createDependencyChildren(new UnresolvableConfigurationResult(configuration), new HashSet<Object>()); } } private List createDependencyChildren(RenderableDependency dependency, final Set<Object> visited) { Iterable<? extends RenderableDependency> children = dependency.getChildren(); return CollectionUtils.collect(children, new Transformer<Map, RenderableDependency>() { @Override public Map transform(RenderableDependency childDependency) { boolean alreadyVisited = !visited.add(childDependency.getId()); boolean alreadyRendered = alreadyVisited && !childDependency.getChildren().isEmpty(); String name = replaceArrow(childDependency.getName()); boolean hasConflict = !name.equals(childDependency.getName()); LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(6); ModuleIdentifier moduleIdentifier = getModuleIdentifier(childDependency); map.put("module", moduleIdentifier == null ? null : moduleIdentifier.toString()); map.put("name", name); map.put("resolvable", childDependency.getResolutionState()); map.put("hasConflict", hasConflict); map.put("alreadyRendered", alreadyRendered); map.put("children", Collections.emptyList()); if (!alreadyRendered) { map.put("children", createDependencyChildren(childDependency, visited)); } return map; } }); } private ModuleIdentifier getModuleIdentifier(RenderableDependency renderableDependency) { if (renderableDependency.getId() instanceof ModuleComponentIdentifier) { ModuleComponentIdentifier id = (ModuleComponentIdentifier) renderableDependency.getId(); return DefaultModuleIdentifier.newId(id.getGroup(), id.getModule()); } return null; } private List createModuleInsights(final Configuration configuration) { Iterable<ModuleIdentifier> modules = collectModules(configuration); return CollectionUtils.collect(modules, new Transformer<Object, ModuleIdentifier>() { @Override public Object transform(ModuleIdentifier moduleIdentifier) { return createModuleInsight(moduleIdentifier, configuration); } }); } private Set<ModuleIdentifier> collectModules(Configuration configuration) { RenderableDependency root; if (configuration.isCanBeResolved()) { ResolutionResult result = configuration.getIncoming().getResolutionResult(); root = new RenderableModuleResult(result.getRoot()); } else { root = new UnresolvableConfigurationResult(configuration); } Set<ModuleIdentifier> modules = Sets.newHashSet(); Set<ComponentIdentifier> visited = Sets.newHashSet(); populateModulesWithChildDependencies(root, visited, modules); return modules; } private void populateModulesWithChildDependencies(RenderableDependency dependency, Set<ComponentIdentifier> visited, Set<ModuleIdentifier> modules) { for (RenderableDependency childDependency : dependency.getChildren()) { ModuleIdentifier moduleId = getModuleIdentifier(childDependency); if (moduleId == null) { continue; } modules.add(moduleId); boolean alreadyVisited = !visited.add((ComponentIdentifier) childDependency.getId()); if (!alreadyVisited) { populateModulesWithChildDependencies(childDependency, visited, modules); } } } private Map createModuleInsight(ModuleIdentifier module, Configuration configuration) { LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(2); map.put("module", module.toString()); map.put("insight", createInsight(module, configuration)); return map; } private List createInsight(ModuleIdentifier module, final Configuration configuration) { final Spec<DependencyResult> dependencySpec = new StrictDependencyResultSpec(module); ResolutionResult result = configuration.getIncoming().getResolutionResult(); final Set<DependencyResult> selectedDependencies = new LinkedHashSet<DependencyResult>(); result.allDependencies(new Action<DependencyResult>() { @Override public void execute(DependencyResult it) { if (dependencySpec.isSatisfiedBy(it)) { selectedDependencies.add(it); } } }); Collection<RenderableDependency> sortedDeps = new DependencyInsightReporter().prepare(selectedDependencies, versionSelectorScheme, versionComparator); return CollectionUtils.collect(sortedDeps, new Transformer<Object, RenderableDependency>() { @Override public Object transform(RenderableDependency dependency) { String name = replaceArrow(dependency.getName()); LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(5); map.put("name", replaceArrow(dependency.getName())); map.put("description", dependency.getDescription()); map.put("resolvable", dependency.getResolutionState()); map.put("hasConflict", !name.equals(dependency.getName())); map.put("children", createInsightDependencyChildren(dependency, new HashSet<Object>(), configuration)); return map; } }); } private List createInsightDependencyChildren(RenderableDependency dependency, final Set<Object> visited, final Configuration configuration) { Iterable<? extends RenderableDependency> children = dependency.getChildren(); return CollectionUtils.collect(children, new Transformer<Object, RenderableDependency>() { @Override public Object transform(RenderableDependency childDependency) { boolean alreadyVisited = !visited.add(childDependency.getId()); boolean leaf = childDependency.getChildren().isEmpty(); boolean alreadyRendered = alreadyVisited && !leaf; String childName = replaceArrow(childDependency.getName()); boolean hasConflict = !childName.equals(childDependency.getName()); String name = leaf ? configuration.getName() : childName; LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(6); map.put("name", name); map.put("resolvable", childDependency.getResolutionState()); map.put("hasConflict", hasConflict); map.put("alreadyRendered", alreadyRendered); map.put("isLeaf", leaf); map.put("children", Collections.emptyList()); if (!alreadyRendered) { map.put("children", createInsightDependencyChildren(childDependency, visited, configuration)); } return map; } }); } private String replaceArrow(String name) { return name.replace(" -> ", " \u27A1 "); } private final VersionSelectorScheme versionSelectorScheme; private final VersionComparator versionComparator; }