Fix support for local jars in library projects.
[android/platform/tools/build.git] / gradle / src / main / groovy / com / android / build / gradle / internal / tasks / DependencyBasedCompileTask.groovy
1 /*
2  * Copyright (C) 2012 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 com.android.build.gradle.internal.tasks
18
19 import com.android.annotations.NonNull
20 import com.android.builder.compiling.DependencyFileProcessor
21 import com.android.builder.internal.incremental.DependencyData
22 import com.android.builder.internal.incremental.DependencyDataStore
23 import com.android.builder.internal.util.concurrent.WaitableExecutor
24 import com.android.builder.resources.FileStatus
25 import com.google.common.collect.Lists
26 import com.google.common.collect.Multimap
27 import org.gradle.api.tasks.OutputDirectory
28
29 import java.util.concurrent.Callable
30
31 /**
32  * Base task for source generators that generate and use dependency files.
33  */
34 public abstract class DependencyBasedCompileTask extends IncrementalTask {
35
36     private static final String DEPENDENCY_STORE = "dependency.store";
37
38     // ----- PUBLIC TASK API -----
39
40     @OutputDirectory
41     File sourceOutputDir
42
43     // ----- PRIVATE TASK API -----
44
45     private static class DepFileProcessor implements DependencyFileProcessor {
46
47         List<DependencyData> dependencyDataList = Lists.newArrayList()
48
49         List<DependencyData> getDependencyDataList() {
50             return dependencyDataList
51         }
52
53         @Override
54         boolean processFile(@NonNull File dependencyFile) {
55             DependencyData data = DependencyData.parseDependencyFile(dependencyFile)
56             if (data != null) {
57                 dependencyDataList.add(data)
58             }
59
60             return true;
61         }
62     }
63
64     /**
65      * Action methods to compile all the files.
66      *
67      * The method receives a {@link DependencyFileProcessor} to be used by the
68      * {@link com.android.builder.internal.compiler.SourceSearcher.SourceFileProcessor} during
69      * the compilation.
70      *
71      * @param dependencyFileProcessor a DependencyFileProcessor
72      */
73     protected abstract void compileAllFiles(DependencyFileProcessor dependencyFileProcessor)
74
75     /**
76      * Setup call back used once before calling multiple
77      * {@link #compileSingleFile(java.io.File, java.lang.Object, com.android.builder.compiling.DependencyFileProcessor)}
78      * during incremental compilation. The result object is passed back to the compileSingleFile
79      * method
80      *
81      * @return an object or null.
82      */
83     protected abstract Object incrementalSetup()
84
85     /**
86      * Returns whether each changed file can be processed in parallel.
87      */
88     protected abstract boolean supportsParallelization()
89
90     /**
91      * Compiles a single file.
92      * @param file the file to compile.
93      * @param data the data returned by {@link #incrementalSetup()}
94      * @param dependencyFileProcessor a DependencyFileProcessor
95      *
96      * @see #incrementalSetup()
97      */
98     protected abstract void compileSingleFile(File file, Object data,
99                                               DependencyFileProcessor dependencyFileProcessor)
100
101     /**
102      * Small wrapper around an optional WaitableExecutor.
103      */
104     private static final class ExecutorWrapper {
105         WaitableExecutor executor = null
106
107         ExecutorWrapper(boolean useExecutor) {
108             if (useExecutor) {
109                 executor = new WaitableExecutor();
110             }
111         }
112
113         void execute(Callable callable) throws Exception {
114             if (executor != null) {
115                 executor.execute(callable)
116             } else {
117                 callable.call()
118             }
119         }
120
121         void waitForTasks() {
122             if (executor != null) {
123                 executor.waitForTasks()
124             }
125         }
126     }
127
128     @Override
129     protected void doFullTaskAction() {
130         // this is full run, clean the previous output
131         File destinationDir = getSourceOutputDir()
132         emptyFolder(destinationDir)
133
134         DepFileProcessor processor = new DepFileProcessor()
135
136         compileAllFiles(processor)
137
138         List<DependencyData> dataList = processor.getDependencyDataList()
139
140         DependencyDataStore store = new DependencyDataStore()
141         store.addData(dataList)
142
143         store.saveTo(new File(getIncrementalFolder(), DEPENDENCY_STORE))
144     }
145
146     @Override
147     protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
148
149         File incrementalData = new File(getIncrementalFolder(), DEPENDENCY_STORE)
150         DependencyDataStore store = new DependencyDataStore()
151         Multimap<String, DependencyData> inputMap
152         try {
153             inputMap = store.loadFrom(incrementalData)
154         } catch (Exception e) {
155             project.logger.info(
156                     "Failed to read dependency store: full task run!")
157             doFullTaskAction()
158             return
159         }
160
161         final Object incrementalObject = incrementalSetup()
162         final DepFileProcessor processor = new DepFileProcessor()
163
164         // use an executor to parallelize the compilation of multiple files.
165         ExecutorWrapper executor = new ExecutorWrapper(supportsParallelization())
166
167         Map<String,DependencyData> mainFileMap = store.getMainFileMap()
168
169         for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
170             FileStatus status = entry.getValue()
171
172             switch (status) {
173                 case FileStatus.NEW:
174                     executor.execute(new Callable() {
175                         @Override
176                         Object call() throws Exception {
177                             compileSingleFile(entry.getKey(), incrementalObject, processor)
178                         }
179                     })
180                     break;
181                 case FileStatus.CHANGED:
182                     List<DependencyData> impactedData = inputMap.get(entry.getKey().absolutePath)
183                     if (impactedData != null) {
184                         for (final DependencyData data : impactedData) {
185                             executor.execute(new Callable() {
186                                 @Override
187                                 Object call() throws Exception {
188                                     compileSingleFile(new File(data.getMainFile()),
189                                             incrementalObject, processor)
190                                 }
191                             })
192                         }
193                     }
194                     break;
195                 case FileStatus.REMOVED:
196                     final DependencyData data = mainFileMap.get(entry.getKey().absolutePath)
197                     if (data != null) {
198                         executor.execute(new Callable() {
199                             @Override
200                             Object call() throws Exception {
201                                 cleanUpOutputFrom(data)
202                             }
203                         })
204                         store.remove(data)
205                     }
206                     break;
207             }
208         }
209
210         executor.waitForTasks()
211
212         // get all the update data for the recompiled objects
213         store.updateAll(processor.getDependencyDataList())
214
215         store.saveTo(incrementalData);
216     }
217
218     private static void cleanUpOutputFrom(DependencyData dependencyData) {
219         List<String> outputs = dependencyData.getOutputFiles();
220
221         for (String output : outputs) {
222             new File(output).delete()
223         }
224     }
225 }