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