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