Support compile config for flavors and build types.
[android/platform/tools/build.git] / builder / src / main / java / com / android / builder / VariantConfiguration.java
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.builder;
18
19 import com.android.annotations.NonNull;
20 import com.android.annotations.Nullable;
21 import com.android.annotations.VisibleForTesting;
22 import com.google.common.collect.Lists;
23 import com.google.common.collect.Sets;
24
25 import java.io.File;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Set;
29
30 import static com.google.common.base.Preconditions.checkNotNull;
31 import static com.google.common.base.Preconditions.checkState;
32
33 /**
34  * A Variant configuration.
35  */
36 public class VariantConfiguration {
37
38     final static ManifestParser sManifestParser = new DefaultManifestParser();
39
40     private final ProductFlavor mDefaultConfig;
41     private final SourceProvider mDefaultSourceProvider;
42
43     private final BuildType mBuildType;
44     /** SourceProvider for the BuildType. Can be null */
45     private final SourceProvider mBuildTypeSourceProvider;
46
47     private final List<ProductFlavor> mFlavorConfigs = Lists.newArrayList();
48     private final List<SourceProvider> mFlavorSourceProviders = Lists.newArrayList();
49
50     private final Type mType;
51     /** Optional tested config in case type is Type#TEST */
52     private final VariantConfiguration mTestedConfig;
53     /** An optional output that is only valid if the type is Type#LIBRARY so that the test
54      * for the library can use the library as if it was a normal dependency. */
55     private AndroidDependency mOutput;
56
57     private ProductFlavor mMergedFlavor;
58
59     private final Set<JarDependency> mJars = Sets.newHashSet();
60
61     /** List of direct library dependencies. Each object defines its own dependencies. */
62     private final List<AndroidDependency> mDirectLibraries = Lists.newArrayList();
63
64     /** list of all library dependencies in a flat list.
65      * The order is based on the order needed to call aapt: earlier libraries override resources
66      * of latter ones. */
67     private final List<AndroidDependency> mFlatLibraries = Lists.newArrayList();
68
69     public static enum Type {
70         DEFAULT, LIBRARY, TEST;
71     }
72
73     /**
74      * Creates the configuration with the base source set.
75      *
76      * This creates a config with a {@link Type#DEFAULT} type.
77      *
78      * @param defaultConfig
79      * @param defaultSourceProvider
80      * @param buildType
81      * @param buildTypeSourceProvider
82      */
83     public VariantConfiguration(
84             @NonNull ProductFlavor defaultConfig, @NonNull SourceProvider defaultSourceProvider,
85             @NonNull BuildType buildType, @NonNull SourceProvider buildTypeSourceProvider) {
86         this(defaultConfig, defaultSourceProvider,
87                 buildType, buildTypeSourceProvider,
88                 Type.DEFAULT, null /*testedConfig*/);
89     }
90
91     /**
92      * Creates the configuration with the base source set for a given {@link Type}.
93      *
94      * @param defaultConfig
95      * @param defaultSourceProvider
96      * @param buildType
97      * @param buildTypeSourceProvider
98      * @param type
99      */
100     public VariantConfiguration(
101             @NonNull ProductFlavor defaultConfig, @NonNull SourceProvider defaultSourceProvider,
102             @NonNull BuildType buildType, @NonNull SourceProvider buildTypeSourceProvider,
103             @NonNull Type type) {
104         this(defaultConfig, defaultSourceProvider,
105                 buildType, buildTypeSourceProvider,
106                 type, null /*testedConfig*/);
107     }
108
109     /**
110      * Creates the configuration with the base source set, and whether it is a library.
111      * @param defaultConfig
112      * @param defaultSourceProvider
113      * @param buildType
114      * @param buildTypeSourceProvider
115      * @param type
116      * @param testedConfig
117      */
118     public VariantConfiguration(
119             @NonNull ProductFlavor defaultConfig, @NonNull SourceProvider defaultSourceProvider,
120             @NonNull BuildType buildType, SourceProvider buildTypeSourceProvider,
121             @NonNull Type type, @Nullable VariantConfiguration testedConfig) {
122         mDefaultConfig = checkNotNull(defaultConfig);
123         mDefaultSourceProvider = checkNotNull(defaultSourceProvider);
124         mBuildType = checkNotNull(buildType);
125         mBuildTypeSourceProvider = buildTypeSourceProvider;
126         mType = checkNotNull(type);
127         mTestedConfig = testedConfig;
128         checkState(mType != Type.TEST || mTestedConfig != null);
129
130         mMergedFlavor = mDefaultConfig;
131
132         if (testedConfig != null &&
133                 testedConfig.mType == Type.LIBRARY &&
134                 testedConfig.mOutput != null) {
135             mDirectLibraries.add(testedConfig.mOutput);
136         }
137
138         validate();
139     }
140
141     /**
142      * Add a new configured ProductFlavor.
143      *
144      * If multiple flavors are added, the priority follows the order they are added when it
145      * comes to resolving Android resources overlays (ie earlier added flavors supersedes
146      * latter added ones).
147      *
148      * @param sourceProvider the configured product flavor
149      */
150     public void addProductFlavor(@NonNull ProductFlavor productFlavor,
151                                  @NonNull SourceProvider sourceProvider) {
152         mFlavorConfigs.add(productFlavor);
153         mFlavorSourceProviders.add(sourceProvider);
154         mMergedFlavor = productFlavor.mergeOver(mMergedFlavor);
155     }
156
157     public void setJarDependencies(List<JarDependency> jars) {
158         mJars.addAll(jars);
159     }
160
161     public Collection<JarDependency> getJars() {
162         return mJars;
163     }
164
165     /**
166      * Set the Library Project dependencies.
167      * @param directLibraries list of direct dependencies. Each library object should contain
168      *            its own dependencies.
169      */
170     public void setAndroidDependencies(@NonNull List<AndroidDependency> directLibraries) {
171         if (directLibraries != null) {
172             mDirectLibraries.addAll(directLibraries);
173         }
174
175         resolveIndirectLibraryDependencies(mDirectLibraries, mFlatLibraries);
176     }
177
178     public void setOutput(AndroidDependency output) {
179         mOutput = output;
180     }
181
182     public ProductFlavor getDefaultConfig() {
183         return mDefaultConfig;
184     }
185
186     public SourceProvider getDefaultSourceSet() {
187         return mDefaultSourceProvider;
188     }
189
190     public ProductFlavor getMergedFlavor() {
191         return mMergedFlavor;
192     }
193
194     public BuildType getBuildType() {
195         return mBuildType;
196     }
197
198     /**
199      * The SourceProvider for the BuildType. Can be null.
200      */
201     public SourceProvider getBuildTypeSourceSet() {
202         return mBuildTypeSourceProvider;
203     }
204
205     public boolean hasFlavors() {
206         return !mFlavorConfigs.isEmpty();
207     }
208
209     public Iterable<ProductFlavor> getFlavorConfigs() {
210         return mFlavorConfigs;
211     }
212
213     public Iterable<SourceProvider> getFlavorSourceSets() {
214         return mFlavorSourceProviders;
215     }
216
217     public boolean hasLibraries() {
218         return !mDirectLibraries.isEmpty();
219     }
220
221     /**
222      * Returns the direct library dependencies
223      */
224     public List<AndroidDependency> getDirectLibraries() {
225         return mDirectLibraries;
226     }
227
228     /**
229      * Returns all the library dependencies, direct and transitive.
230      */
231     public List<AndroidDependency> getAllLibraries() {
232         return mFlatLibraries;
233     }
234
235     public List<File> getPackagedJars() {
236         List<File> jars = Lists.newArrayListWithCapacity(mJars.size() + mFlatLibraries.size());
237
238         for (JarDependency jar : mJars) {
239             File jarFile = new File(jar.getLocation());
240             if (jarFile.exists()) {
241                 jars.add(jarFile);
242             }
243         }
244
245         for (AndroidDependency androidDependency : mFlatLibraries) {
246             File libJar = androidDependency.getJarFile();
247             if (libJar.exists()) {
248                 jars.add(libJar);
249             }
250         }
251
252         return jars;
253     }
254
255     public Type getType() {
256         return mType;
257     }
258
259     VariantConfiguration getTestedConfig() {
260         return mTestedConfig;
261     }
262
263     /**
264      * Resolves a given list of libraries, finds out if they depend on other libraries, and
265      * returns a flat list of all the direct and indirect dependencies in the proper order (first
266      * is higher priority when calling aapt).
267      * @param directDependencies the libraries to resolve
268      * @param outFlatDependencies where to store all the libraries.
269      */
270     @VisibleForTesting
271     void resolveIndirectLibraryDependencies(List<AndroidDependency> directDependencies,
272                                             List<AndroidDependency> outFlatDependencies) {
273         if (directDependencies == null) {
274             return;
275         }
276         // loop in the inverse order to resolve dependencies on the libraries, so that if a library
277         // is required by two higher level libraries it can be inserted in the correct place
278         for (int i = directDependencies.size() - 1  ; i >= 0 ; i--) {
279             AndroidDependency library = directDependencies.get(i);
280
281             // get its libraries
282             List<AndroidDependency> dependencies = library.getDependencies();
283
284             // resolve the dependencies for those libraries
285             resolveIndirectLibraryDependencies(dependencies, outFlatDependencies);
286
287             // and add the current one (if needed) in front (higher priority)
288             if (outFlatDependencies.contains(library) == false) {
289                 outFlatDependencies.add(0, library);
290             }
291         }
292     }
293
294     /**
295      * Returns the package name for this variant. This could be coming from the manifest or
296      * could be overridden through the product flavors.
297      * @return the package
298      */
299     public String getPackageName() {
300         String packageName;
301
302         if (mType == Type.TEST) {
303             packageName = mMergedFlavor.getTestPackageName();
304             if (packageName == null) {
305                 String testedPackage = mTestedConfig.getPackageName();
306
307                 packageName = testedPackage + ".test";
308             }
309         } else {
310             packageName = getPackageOverride();
311             if (packageName == null) {
312                 packageName = getPackageFromManifest();
313             }
314         }
315
316         return packageName;
317     }
318
319     public String getTestedPackageName() {
320         if (mType == Type.TEST) {
321             if (mTestedConfig.mType == Type.LIBRARY) {
322                 return getPackageName();
323             } else {
324                 return mTestedConfig.getPackageName();
325             }
326         }
327
328         return null;
329     }
330
331     /**
332      * Returns the package override values coming from the Product Flavor. If the package is not
333      * overridden then this returns null.
334      * @return the package override or null
335      */
336     public String getPackageOverride() {
337
338         String packageName = mMergedFlavor.getPackageName();
339         String packageSuffix = mBuildType.getPackageNameSuffix();
340
341         if (packageSuffix != null && packageSuffix.length() > 0) {
342             if (packageName == null) {
343                 packageName = getPackageFromManifest();
344             }
345
346             if (packageSuffix.charAt(0) == '.') {
347                 packageName = packageName + packageSuffix;
348             } else {
349                 packageName = packageName + '.' + packageSuffix;
350             }
351         }
352
353         return packageName;
354     }
355
356     private final static String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner";
357
358     public String getInstrumentationRunner() {
359         String runner = mMergedFlavor.getTestInstrumentationRunner();
360         return runner != null ? runner : DEFAULT_TEST_RUNNER;
361     }
362
363     /**
364      * Reads the package name from the manifest.
365      */
366     public String getPackageFromManifest() {
367         File manifestLocation = mDefaultSourceProvider.getManifestFile();
368         return sManifestParser.getPackage(manifestLocation);
369     }
370
371     public int getMinSdkVersion() {
372         int minSdkVersion = mMergedFlavor.getMinSdkVersion();
373         if (minSdkVersion == -1) {
374             // read it from the main manifest
375             File manifestLocation = mDefaultSourceProvider.getManifestFile();
376             minSdkVersion = sManifestParser.getMinSdkVersion(manifestLocation);
377         }
378
379         return minSdkVersion;
380     }
381
382     /**
383      * Returns a list of object that represents the configuration. This can be used to compare
384      * 2 different list of config objects to know whether the build is up to date or not.
385      */
386     public Iterable<Object> getConfigObjects() {
387         List<Object> list = Lists.newArrayListWithExpectedSize(mFlavorConfigs.size() + 2);
388         list.add(mDefaultConfig);
389         list.add(mBuildType);
390         list.addAll(mFlavorConfigs);
391         // TODO: figure out the deps in here.
392 //        list.addAll(mFlatLibraries);
393
394         return list;
395     }
396
397     public List<File> getManifestInputs() {
398         List<File> inputs = Lists.newArrayList();
399
400         File defaultManifest = mDefaultSourceProvider.getManifestFile();
401         // this could not exist in a test project.
402         if (defaultManifest != null && defaultManifest.isFile()) {
403             inputs.add(defaultManifest);
404         }
405
406         if (mBuildTypeSourceProvider != null) {
407             File typeLocation = mBuildTypeSourceProvider.getManifestFile();
408             if (typeLocation != null && typeLocation.isFile()) {
409                 inputs.add(typeLocation);
410             }
411         }
412
413         for (SourceProvider sourceProvider : mFlavorSourceProviders) {
414             File f = sourceProvider.getManifestFile();
415             if (f != null && f.isFile()) {
416                 inputs.add(f);
417             }
418         }
419
420         List<AndroidDependency> libs = mDirectLibraries;
421         for (AndroidDependency lib : libs) {
422             File manifest = lib.getManifest();
423             if (manifest != null && manifest.isFile()) {
424                 inputs.add(manifest);
425             }
426         }
427
428         return inputs;
429     }
430
431     /**
432      * Returns the dynamic list of resource folders based on the configuration, its dependencies,
433      * as well as tested config if applicable (test of a library).
434      * @return a list of input resource folders.
435      */
436     public List<File> getResourceInputs() {
437         List<File> inputs = Lists.newArrayList();
438
439         if (mBuildTypeSourceProvider != null) {
440             File typeResLocation = mBuildTypeSourceProvider.getResourcesDir();
441             if (typeResLocation != null) {
442                 inputs.add(typeResLocation);
443             }
444         }
445
446         for (SourceProvider sourceProvider : mFlavorSourceProviders) {
447             File flavorResLocation = sourceProvider.getResourcesDir();
448             if (flavorResLocation != null) {
449                 inputs.add(flavorResLocation);
450             }
451         }
452
453         File mainResLocation = mDefaultSourceProvider.getResourcesDir();
454         if (mainResLocation != null) {
455             inputs.add(mainResLocation);
456         }
457
458         for (AndroidDependency dependency : mFlatLibraries) {
459             File resFolder = dependency.getResFolder();
460             if (resFolder != null) {
461                 inputs.add(resFolder);
462             }
463         }
464
465         return inputs;
466     }
467
468     /**
469      * Returns all the aidl import folder that are outside of the current project.
470      */
471     public List<File> getAidlImports() {
472         List<File> list = Lists.newArrayList();
473
474         for (AndroidDependency lib : mFlatLibraries) {
475             File aidlLib = lib.getAidlFolder();
476             if (aidlLib != null && aidlLib.isDirectory()) {
477                 list.add(aidlLib);
478             }
479         }
480
481         return list;
482     }
483
484     /**
485      * Returns the compile classpath for this config. If the config tests a library, this
486      * will include the classpath of the tested config
487      */
488     public Set<File> getCompileClasspath() {
489         Set<File> classpath = Sets.newHashSet();
490
491         for (AndroidDependency lib : mFlatLibraries) {
492             classpath.add(lib.getJarFile());
493         }
494
495         for (JarDependency jar : mJars) {
496             classpath.add(new File(jar.getLocation()));
497         }
498
499         return classpath;
500     }
501
502     public List<String> getBuildConfigLines() {
503         List<String> fullList = Lists.newArrayList();
504
505         List<String> list = mDefaultConfig.getBuildConfigLines();
506         if (!list.isEmpty()) {
507             fullList.add("// lines from default config.");
508             fullList.addAll(list);
509         }
510
511         list = mBuildType.getBuildConfigLines();
512         if (!list.isEmpty()) {
513             fullList.add("// lines from build type: " + mBuildType.getName());
514             fullList.addAll(list);
515         }
516
517         for (ProductFlavor flavor : mFlavorConfigs) {
518             list = flavor.getBuildConfigLines();
519             if (!list.isEmpty()) {
520                 fullList.add("// lines from product flavor: " + flavor.getName());
521                 fullList.addAll(list);
522             }
523         }
524
525         return fullList;
526     }
527
528     protected void validate() {
529         if (mType != Type.TEST) {
530             File manifest = mDefaultSourceProvider.getManifestFile();
531             if (!manifest.isFile()) {
532                 throw new IllegalArgumentException(
533                         "Main Manifest missing from " + manifest.getAbsolutePath());
534             }
535         }
536     }
537 }