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