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