1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.databinding 18 19 import groovy.io.FileType 20 import org.apache.maven.repository.internal.MavenRepositorySystemUtils 21 import org.eclipse.aether.DefaultRepositorySystemSession 22 import org.eclipse.aether.RepositorySystem 23 import org.eclipse.aether.RepositorySystemSession 24 import org.eclipse.aether.artifact.Artifact 25 import org.eclipse.aether.artifact.DefaultArtifact 26 import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory 27 import org.eclipse.aether.graph.Dependency 28 import org.eclipse.aether.impl.DefaultServiceLocator 29 import org.eclipse.aether.repository.LocalRepository 30 import org.eclipse.aether.repository.RemoteRepository 31 import org.eclipse.aether.resolution.ArtifactDescriptorRequest 32 import org.eclipse.aether.resolution.ArtifactDescriptorResult 33 import org.eclipse.aether.resolution.ArtifactRequest 34 import org.eclipse.aether.spi.connector.RepositoryConnectorFactory 35 import org.eclipse.aether.spi.connector.transport.TransporterFactory 36 import org.eclipse.aether.transport.file.FileTransporterFactory 37 import org.eclipse.aether.transport.http.HttpTransporterFactory 38 import org.gradle.api.DefaultTask 39 import org.gradle.api.artifacts.Configuration 40 import org.gradle.api.artifacts.ModuleVersionIdentifier 41 import org.gradle.api.tasks.TaskAction 42 43 class LocalizeDependenciesTask extends DefaultTask { 44 45 private Set<String> ids = new HashSet<>(); 46 47 private Set<String> fetchTestDependencies = new HashSet<>(); 48 49 // force download these if they are seen as a dependency 50 private Set<String> wildCard = new HashSet<>(); 51 { 52 wildCard.add("kotlin-gradle-plugin-core") 53 } 54 55 List<Artifact> artifactsToResolve = new LinkedList<>(); 56 57 Set<String> resolvedArtifacts = new HashSet<>(); 58 59 Set<String> failed = new HashSet<>(); 60 61 HashMap<String, Object> licenses = new HashMap<>(); 62 63 Set<String> missingLicenses = new HashSet<>(); 64 65 File localRepoDir; 66 67 @TaskAction 68 doIt() { 69 println(ids) 70 LocalizePluginExtension extension = project.extensions. 71 getByName(MavenDependencyCollectorPlugin.EXTENSION_NAME) 72 if (extension.localRepoDir == null || extension.otherRepoDirs == null) { 73 74 def msg = "you must configure " + 75 "${MavenDependencyCollectorPlugin.EXTENSION_NAME} with localRepoDir and" + 76 " otherRepoDirs. localRepoDir: " + extension.localRepoDir + 77 "\notherRepoDir:" + extension.otherRepoDirs; 78 println(msg) 79 println("skipping ${project}") 80 return 81 } 82 localRepoDir = extension.localRepoDir 83 downloadAll(extension.localRepoDir, extension.otherRepoDirs) 84 85 if (!missingLicenses.isEmpty()) { 86 throw new RuntimeException("Missing licenses for $missingLicenses") 87 } 88 println("List of new licenses:") 89 println(ExportLicensesTask.buildNotice(licenses)) 90 } 91 92 public void add(MavenDependencyCollectorTask task, ModuleVersionIdentifier id, Configuration conf) { 93 def key = toStringIdentifier(id) 94 ids.add(key) 95 println("adding $key in $conf by $task") 96 } 97 98 public static String toStringIdentifier(ModuleVersionIdentifier id) { 99 return id.group + ":" + id.name + ":" + id.version; 100 } 101 102 private static String artifactKey(Artifact artifact) { 103 return artifact.groupId + ":" + artifact.artifactId + ":" + artifact.version; 104 } 105 106 public downloadAll(File localRepoDir, List<String> otherRepoDirs) { 107 println("downloading all dependencies to $localRepoDir") 108 def mavenCentral = new RemoteRepository.Builder("central", "default", 109 "http://central.maven.org/maven2/").build(); 110 def system = newRepositorySystem() 111 localRepoDir = localRepoDir.canonicalFile 112 List<File> otherRepos = new ArrayList<>() 113 otherRepoDirs.each { 114 def repo = new File(it).getCanonicalFile() 115 if (repo.exists() && !repo.equals(localRepoDir)) { 116 otherRepos.add(repo) 117 } 118 } 119 def session = newRepositorySystemSession(system, localRepoDir) 120 ids.each { 121 def artifact = new DefaultArtifact(it) 122 artifactsToResolve.add(artifact) 123 } 124 125 while (!artifactsToResolve.isEmpty()) { 126 println("remaining artifacts to resolve ${artifactsToResolve.size()}") 127 Artifact artifact = artifactsToResolve.remove(0) 128 println(" handling artifact ${artifact.getArtifactId()}") 129 if (shouldSkip(artifact, otherRepos)) { 130 println("skipping $artifact") 131 continue 132 } 133 resolveArtifactWithDependencies(system, session, Arrays.asList(mavenCentral), artifact); 134 } 135 } 136 137 public static boolean shouldSkip(Artifact artifact, List<File> otherRepos) { 138 if (artifact.groupId.startsWith('com.android.databinding') || 139 artifact.groupId.startsWith('com.android.support') || 140 artifact.groupId.equals("jdk")){ 141 return true 142 } 143 String targetPath = artifact.groupId.replaceAll("\\.", "/") + "/" + artifact.artifactId + 144 "/" + artifact.version 145 for (File repo : otherRepos) { 146 File f = new File(repo, targetPath) 147 if (f.exists()) { 148 println("skipping ${artifact} because it exists in $repo") 149 return true 150 } 151 } 152 return false 153 } 154 155 def boolean isInGit(File file) { 156 if (!file.getCanonicalPath().startsWith(localRepoDir.getCanonicalPath())) { 157 println("$file is in another git repo, ignore for license") 158 return false 159 } 160 def gitSt = ["git", "status", "--porcelain", file.getCanonicalPath()]. 161 execute([], localRepoDir) 162 gitSt.waitFor() 163 if (gitSt.exitValue() != 0) { 164 throw new RuntimeException("unable to get git status for $file. ${gitSt.err.text}") 165 } 166 return gitSt.text.trim().isEmpty() 167 } 168 169 public void resolveArtifactWithDependencies(RepositorySystem system, 170 RepositorySystemSession session, List<RemoteRepository> remoteRepositories, 171 Artifact artifact) { 172 def key = artifactKey(artifact) 173 if (resolvedArtifacts.contains(key) || failed.contains(key)) { 174 return 175 } 176 resolvedArtifacts.add(key) 177 ArtifactRequest artifactRequest = new ArtifactRequest(); 178 artifactRequest.setArtifact(artifact); 179 artifactRequest.setRepositories(remoteRepositories); 180 def resolved; 181 try { 182 resolved = system.resolveArtifact(session, artifactRequest); 183 } catch (Throwable ignored) { 184 println("cannot find $key, skipping") 185 failed.add(key) 186 return 187 } 188 def alreadyInGit = isInGit(resolved.artifact.file) 189 println(" |-> resolved ${resolved.artifact.file}. Already in git? $alreadyInGit") 190 191 192 193 if (!alreadyInGit) { 194 def license = ExportLicensesTask.findLicenseFor(resolved.artifact.artifactId) 195 if (license == null) { 196 missingLicenses.add(artifactKey(artifact)) 197 } else { 198 licenses.put(resolved.artifact.artifactId, license) 199 } 200 } 201 202 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 203 descriptorRequest.setArtifact(artifact); 204 descriptorRequest.setRepositories(remoteRepositories); 205 206 ArtifactDescriptorResult descriptorResult = system. 207 readArtifactDescriptor(session, descriptorRequest); 208 for (Dependency dependency : descriptorResult.getDependencies()) { 209 println("dependency $dependency for $artifact . scope: ${dependency.scope}") 210 if ("provided".equals(dependency.scope)) { 211 println("skipping $dependency because provided") 212 continue 213 } 214 if ("optional".equals(dependency.scope)) { 215 println("skipping $dependency because optional") 216 continue 217 } 218 if ("test".equals(dependency.scope)) { 219 if (wildCard.contains(dependency.artifact.getArtifactId()) || fetchTestDependencies.contains(key)) { 220 println("${dependency} is test scope but including because $key is in direct dependencies") 221 } else { 222 println("skipping $dependency because test and $key is not first level dependency. artifact id: ${dependency.artifact.getArtifactId()}") 223 continue 224 } 225 } 226 227 228 def dependencyKey = artifactKey(dependency.artifact) 229 if (resolvedArtifacts.contains(dependencyKey)) { 230 println("skipping $dependency because is already resolved as ${dependencyKey}") 231 continue 232 } 233 println("adding to the list ${dependency.artifact}") 234 artifactsToResolve.add(dependency.artifact) 235 } 236 File unwanted = new File(resolved.artifact.file.getParentFile(), "_remote.repositories") 237 if (unwanted.exists()) { 238 unwanted.delete() 239 } 240 } 241 242 public static DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system, 243 File localRepoDir) { 244 DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); 245 LocalRepository localRepo = new LocalRepository(localRepoDir); 246 session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo)); 247 return session; 248 } 249 250 public static RepositorySystem newRepositorySystem() { 251 DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); 252 locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); 253 locator.addService(TransporterFactory.class, FileTransporterFactory.class); 254 locator.addService(TransporterFactory.class, HttpTransporterFactory.class); 255 256 return locator.getService(RepositorySystem.class); 257 } 258 } 259