package io.pry.gradle.offline_dependencies import io.pry.gradle.offline_dependencies.maven.PomDependencyModelResolver import io.pry.gradle.offline_dependencies.repackaged.org.apache.maven.model.Model import io.pry.gradle.offline_dependencies.repackaged.org.apache.maven.model.building.* import io.pry.gradle.offline_dependencies.repackaged.org.apache.maven.model.interpolation.StringSearchModelInterpolator import io.pry.gradle.offline_dependencies.repackaged.org.apache.maven.model.path.DefaultPathTranslator import io.pry.gradle.offline_dependencies.repackaged.org.apache.maven.model.path.DefaultUrlNormalizer import io.pry.gradle.offline_dependencies.repackaged.org.apache.maven.model.validation.DefaultModelValidator import org.gradle.api.DefaultTask import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.ExternalModuleDependency import org.gradle.api.artifacts.UnknownConfigurationException import org.gradle.api.artifacts.component.ComponentIdentifier import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.artifacts.result.UnresolvedArtifactResult import org.gradle.api.internal.artifacts.DefaultModuleIdentifier import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier import org.gradle.ivy.IvyDescriptorArtifact import org.gradle.ivy.IvyModule import org.gradle.jvm.JvmLibrary import org.gradle.language.base.artifact.SourcesArtifact import org.gradle.language.java.artifact.JavadocArtifact import org.gradle.maven.MavenModule import org.gradle.maven.MavenPomArtifact import org.gradle.util.GFileUtils import static io.pry.gradle.offline_dependencies.Utils.addToMultimap class UpdateOfflineRepositoryTask extends DefaultTask { def EMPTY_DEPENDENCIES_ARRAY = new Dependency[0] @Input GString root @Input Set configurationNames @Input Set buildscriptConfigurationNames @Input boolean includeSources @Input boolean includeJavadocs @Input boolean includePoms @Input boolean includeIvyXmls @Input boolean includeBuildscriptDependencies @TaskAction void run() { withRepositoryFiles { repositoryFiles -> // copy collected files to destination directory repositoryFiles.each { id, files -> def directory = moduleDirectory(id) GFileUtils.mkdirs(directory) files.each { File file -> GFileUtils.copyFile(file, new File(directory, file.name)) } } } } // configurations private Set getConfigurations() { Set configurations = [] if (this.getConfigurationNames()) { def configurationNames = this.getConfigurationNames() logger.trace("Trying to resolve the following project configurations: '${configurationNames.join(",")}'") configurationNames.each { name -> try { configurations.add(project.configurations.getByName(name)) } catch (UnknownConfigurationException e) { logger.warn("Unable to resolve project configuration with name '${name}'") } } } else { logger.trace("No project configurations specified, defaulting to all configurations") configurations.addAll(project.configurations) } if (this.getIncludeBuildscriptDependencies()) { if (this.getBuildscriptConfigurationNames()) { def configurationNames = this.getBuildscriptConfigurationNames() logger.trace("Trying to resolve the following buildscript configurations: '${configurationNames.join(",")}'") configurationNames.each { name -> try { configurations.add(project.buildscript.configurations.getByName(name)) } catch (UnknownConfigurationException e) { logger.warn("Unable to resolve buildscript configuration with name '${name}'") } } } else { logger.trace("No buildscript configurations specified, defaulting to all configurations") configurations.addAll(project.buildscript.configurations) } } else { logger.trace("Skipping buildscript configurations") } if (!configurations) { logger.warn('No configurations found. There are no dependencies to resolve.') } return configurations } // collect everything private Map> collectRepositoryFiles(Set configurations) { Set componentIds = [] Map> repositoryFiles = [:] for (configuration in configurations) { for (dependency in configuration.allDependencies) { if (dependency instanceof ExternalModuleDependency) { // create a detached configuration for each dependency to get all declared versions of a dependency. // resolution would fetch only the newest otherwise. // // see: // * http://stackoverflow.com/questions/29374885/multiple-version-of-dependencies-in-gradle // * https://discuss.gradle.org/t/how-to-get-multiple-versions-of-the-same-library/7400 def cfg = project.configurations.detachedConfiguration([dependency].toArray(EMPTY_DEPENDENCIES_ARRAY)) cfg.resolvedConfiguration.resolvedArtifacts.forEach({ artifact -> def componentId = DefaultModuleComponentIdentifier.newId(DefaultModuleIdentifier.newId(artifact.moduleVersion.id.group, artifact.moduleVersion.id.name), artifact.moduleVersion.id.version) componentIds.add(componentId) logger.trace("Adding artifact for component'{}' (location '{}')", componentId, artifact.file) addToMultimap(repositoryFiles, componentId, artifact.file) }); } } } // collect sources and javadocs if (this.getIncludeSources() || this.getIncludeJavadocs()) { def jvmArtifacts = project.dependencies.createArtifactResolutionQuery() .forComponents(componentIds) .withArtifacts(JvmLibrary, SourcesArtifact, JavadocArtifact) .execute() for (component in jvmArtifacts.resolvedComponents) { if (this.getIncludeSources()) { def sources = component.getArtifacts(SourcesArtifact) if (!sources?.empty) { sources*.file.each { File source -> logger.trace("Adding sources for component'{}' (location '{}')", component.id, source) addToMultimap(repositoryFiles, component.id, source) } } } if (this.getIncludeJavadocs()) { def javadocs = component.getArtifacts(JavadocArtifact) if (!javadocs?.empty) { javadocs*.file.each { File javadoc -> logger.trace("Adding javadocs for component'{}' (location '{}')", component.id, javadoc) addToMultimap(repositoryFiles, component.id, javadoc) } } } } } // collect maven poms (for immediate component ids and parents) if (this.getIncludePoms()) { collectPoms(componentIds, repositoryFiles) } // collect ivy xml files if (this.getIncludeIvyXmls()) { collectIvyXmls(componentIds, repositoryFiles) } return repositoryFiles } // adds pom artifacts and their parents for the givens component ids private void collectPoms(Set componentIds, Map> repositoryFiles) { logger.trace("Collecting pom files") def mavenArtifacts = project.dependencies.createArtifactResolutionQuery() .forComponents(componentIds) .withArtifacts(MavenModule, MavenPomArtifact) .execute() def pomModelResolver = new PomDependencyModelResolver(project) for (component in mavenArtifacts.resolvedComponents) { def poms = component.getArtifacts(MavenPomArtifact) if (poms?.empty) { continue } def pomArtifact = poms.first() if (pomArtifact instanceof UnresolvedArtifactResult) { logger.error("Resolver was unable to resolve artifact '{}'", pomArtifact.id, pomArtifact.getFailure()) continue } def pomFile = pomArtifact.file as File resolvePom(pomModelResolver, pomFile) logger.trace("Adding pom for component'{}' (location '{}')", component.id, pomFile) addToMultimap(repositoryFiles, component.id, pomFile) } pomModelResolver.componentCache().each { componentId, pomFile -> addToMultimap(repositoryFiles, componentId, pomFile) } } private Model resolvePom(PomDependencyModelResolver pomModelResolver, File pomFile) { def modelBuildingRequest = new DefaultModelBuildingRequest(); modelBuildingRequest.setSystemProperties(System.getProperties()) modelBuildingRequest.setModelSource(new FileModelSource(pomFile)) modelBuildingRequest.setModelResolver(pomModelResolver) try { def modelBuilder = new DefaultModelBuilderFactory().newInstance() def modelInterpolator = new StringSearchModelInterpolator() modelInterpolator.setUrlNormalizer(new DefaultUrlNormalizer()) modelInterpolator.setPathTranslator(new DefaultPathTranslator()) modelBuilder.setModelInterpolator(modelInterpolator) modelBuilder.setModelValidator(new DefaultModelValidator()) def result = modelBuilder.build(modelBuildingRequest) if (!result.problems.empty) { result.problems.each { this.logModelProblems(it) } } return result.effectiveModel } catch (ModelBuildingException e) { logger.error("${e.getMessage()}: ${e.problems}") } } protected void logModelProblems(ModelProblem problem) { def message = "$problem.modelId: $problem.message" switch (problem.severity) { case ModelProblem.Severity.WARNING: logger.info(message, problem.exception); break; case ModelProblem.Severity.ERROR: case ModelProblem.Severity.FATAL: logger.error(message, problem.exception); break; } } private void collectIvyXmls(Set componentIds, Map> repositoryFiles) { logger.trace("Collecting ivy xml files") def ivyArtifacts = project.dependencies.createArtifactResolutionQuery() .forComponents(componentIds) .withArtifacts(IvyModule, IvyDescriptorArtifact) .execute() for (component in ivyArtifacts.resolvedComponents) { def ivyXmls = component.getArtifacts(IvyDescriptorArtifact) if (ivyXmls?.empty) { continue } def ivyXmlArtifact = ivyXmls.first() if (ivyXmlArtifact instanceof UnresolvedArtifactResult) { logger.error("Resolver was unable to resolve artifact '{}'", ivyXmlArtifact.id, ivyXmlArtifact.getFailure()) continue } def ivyXml = ivyXmlArtifact.file as File logger.trace("Adding ivy artifact for component'{}' (location '{}')", component.id, ivyXml) addToMultimap(repositoryFiles, component.id, ivyXml) } } // Activate online repositories and collect dependencies. // Switch back to local repository afterwards. private def withRepositoryFiles(Closure>> callback) { def originalRepositories = project.repositories.collect() project.repositories.clear() OfflineDependenciesExtension extension = project.extensions.getByName(OfflineDependenciesPlugin.EXTENSION_NAME) as OfflineDependenciesExtension project.repositories.addAll(extension.repositoryHandler) def files = collectRepositoryFiles(getConfigurations()) project.repositories.clear() project.repositories.addAll(originalRepositories) callback(files) } // Return the offline-repository target directory for the given component (naming follows maven conventions) protected File moduleDirectory(ModuleComponentIdentifier ci) { new File("${getRoot()}".toString(), "${ci.group.tokenize(".").join("/")}/${ci.module}/${ci.version}") } }