Change the way AndroidBuilder is configured.
Xavier Ducrohet [Fri, 24 Aug 2012 22:50:38 +0000 (15:50 -0700)]
In order to build test apps and library apps we need
to seriously change the configuration of the builder.

It now receives a VariantConfiguration that contains
all that is needed. If the builder builds a test app
it also receives a VariantConfiguration for the tested
app.
This is critical for building apps that test libraries
as they will embed the library.

Also, the plugin now creates all tasks in afterEvaluate
as it is a lot simpler to loop on all build types and
product flavors. It also allow conditionally creating
some tasks based on the configuration of the project
flavors and build types.

Made a copy of the basic testapps where the build
is configured for multi-flavors.

Change-Id: Ib9a70b6fa79dec6cf939e1d4bd2e75559529e820

44 files changed:
builder/src/main/java/com/android/builder/AndroidBuilder.java
builder/src/main/java/com/android/builder/BuildConfigGenerator.java
builder/src/main/java/com/android/builder/BuildType.java
builder/src/main/java/com/android/builder/BuildTypeHolder.java [deleted file]
builder/src/main/java/com/android/builder/DefaultManifestParser.java
builder/src/main/java/com/android/builder/ManifestParser.java
builder/src/main/java/com/android/builder/ProductFlavorHolder.java [deleted file]
builder/src/main/java/com/android/builder/SourceSet.java [moved from builder/src/main/java/com/android/builder/PathProvider.java with 87% similarity]
builder/src/main/java/com/android/builder/TemplateProcessor.java [new file with mode: 0644]
builder/src/main/java/com/android/builder/TestManifestGenerator.java [new file with mode: 0644]
builder/src/main/java/com/android/builder/VariantConfiguration.java [new file with mode: 0644]
builder/src/main/resources/com/android/builder/AndroidManifest.template [new file with mode: 0644]
builder/src/test/java/com/android/builder/AndroidBuilderTest.java [deleted file]
builder/src/test/java/com/android/builder/BuildTypeHolderMock.java [deleted file]
builder/src/test/java/com/android/builder/BuildTypeTest.java
builder/src/test/java/com/android/builder/MockSourceSet.java [moved from builder/src/test/java/com/android/builder/samples/DefaultPathProvider.java with 79% similarity]
builder/src/test/java/com/android/builder/ProductFlavorHolderMock.java [deleted file]
builder/src/test/java/com/android/builder/VariantConfigurationTest.java [new file with mode: 0644]
builder/src/test/java/com/android/builder/samples/Main.java [deleted file]
gradle/src/main/groovy/com/android/build/gradle/AndroidBasePlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidExtension.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidLibraryPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidLogger.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/BaseAndroidExtension.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/AndroidSourceSet.groovy [moved from gradle/src/main/groovy/com/android/build/gradle/internal/BaseDimension.groovy with 64% similarity]
gradle/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.groovy [moved from gradle/src/main/groovy/com/android/build/gradle/internal/BuildTypeDimension.groovy with 57% similarity]
gradle/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.groovy [moved from gradle/src/main/groovy/com/android/build/gradle/internal/ProductFlavorDimension.groovy with 50% similarity]
gradle/src/main/groovy/com/android/build/gradle/internal/ProductionAppVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/TestAppVariant.groovy
testapps/basic/build.gradle
testapps/flavored/build.gradle [new file with mode: 0644]
testapps/flavored/debug.keystore [new file with mode: 0644]
testapps/flavored/src/main/AndroidManifest.xml [new file with mode: 0644]
testapps/flavored/src/main/java/org/gradle/sample/BuildType.java [new file with mode: 0644]
testapps/flavored/src/main/java/org/gradle/sample/MainActivity.java [new file with mode: 0644]
testapps/flavored/src/main/java/org/gradle/sample/Person.java [new file with mode: 0644]
testapps/flavored/src/main/java/org/gradle/sample/ShowPeopleActivity.java [new file with mode: 0644]
testapps/flavored/src/main/res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
testapps/flavored/src/main/res/drawable-ldpi/ic_launcher.png [new file with mode: 0644]
testapps/flavored/src/main/res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
testapps/flavored/src/main/res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
testapps/flavored/src/main/res/layout/main.xml [new file with mode: 0644]
testapps/flavored/src/main/res/values/strings.xml [new file with mode: 0644]

index 4d4e35b..bdf065d 100644 (file)
@@ -25,6 +25,7 @@ import com.android.builder.packaging.Packager;
 import com.android.builder.packaging.PackagerException;
 import com.android.builder.packaging.SealedPackageException;
 import com.android.builder.signing.DebugKeyHelper;
+import com.android.builder.signing.KeystoreHelper;
 import com.android.builder.signing.KeytoolException;
 import com.android.builder.signing.SigningInfo;
 import com.android.manifmerger.ManifestMerger;
@@ -50,14 +51,11 @@ import java.util.Set;
  * To use:
  * create a builder with {@link #AndroidBuilder(SdkParser, ILogger, boolean)},
  * configure compile target with {@link #setTarget(String)}
- * configure build variant with {@link #setBuildVariant(ProductFlavorHolder, BuildTypeHolder)},
- * optionally add flavors with {@link #addProductFlavor(ProductFlavorHolder)},
- * configure dependencies with {@link #setAndroidDependencies(List)} and
- *     {@link #setJarDependencies(List)},
+ * configure build variant with {@link #setBuildVariant(VariantConfiguration, VariantConfiguration)}
  *
  * then build steps can be done with
  * {@link #generateBuildConfig(String, java.util.List)}
- * {@link #mergeLibraryManifests(File, List, File)}
+ * {@link #processManifest(String)}
  * {@link #processResources(String, String, String, String, String, AaptOptions)}
  * {@link #convertBytecode(java.util.List, String, DexOptions)}
  * {@link #packageApk(String, String, String, String)}
@@ -69,26 +67,14 @@ public class AndroidBuilder {
 
     private final SdkParser mSdkParser;
     private final ILogger mLogger;
-    private final ManifestParser mManifestParser;
     private final CommandLineRunner mCmdLineRunner;
     private final boolean mVerboseExec;
 
     private IAndroidTarget mTarget;
 
-    private BuildTypeHolder mBuildTypeHolder;
-    private ProductFlavorHolder mMainFlavorHolder;
-    private List<ProductFlavorHolder> mFlavorHolderList;
-    private ProductFlavor mMergedFlavor;
-
-    private List<JarDependency> mJars;
-
-    /** List of direct library project dependencies. Each object defines its own dependencies. */
-    private final List<AndroidDependency> mDirectLibraryProjects =
-            new ArrayList<AndroidDependency>();
-    /** list of all library project dependencies in the flat list.
-     * The order is based on the order needed to call aapt: earlier libraries override resources
-     * of latter ones. */
-    private final List<AndroidDependency> mFlatLibraryProjects = new ArrayList<AndroidDependency>();
+    // config for the main app. In case of a test app this is used to
+    private VariantConfiguration mVariant;
+    private VariantConfiguration mTestedVariant;
 
     /**
      * Creates an AndroidBuilder
@@ -107,7 +93,6 @@ public class AndroidBuilder {
         mSdkParser = sdkParser;
         mLogger = logger;
         mVerboseExec = verboseExec;
-        mManifestParser = new DefaultManifestParser();
         mCmdLineRunner = new CommandLineRunner(mLogger);
     }
 
@@ -121,7 +106,6 @@ public class AndroidBuilder {
         mSdkParser = sdkParser;
         mLogger = logger;
         mVerboseExec = verboseExec;
-        mManifestParser = manifestParser;
         mCmdLineRunner = cmdLineRunner;
     }
 
@@ -141,39 +125,17 @@ public class AndroidBuilder {
     }
 
     /**
-     * Sets the initial build variant by providing the main flavor and the build type.
-     * @param mainFlavorHolder the main ProductFlavor
-     * @param buildTypeHolder the Build Type.
-     */
-    public void setBuildVariant(
-            @NonNull ProductFlavorHolder mainFlavorHolder,
-            @NonNull BuildTypeHolder buildTypeHolder) {
-        mMainFlavorHolder = mainFlavorHolder;
-        mBuildTypeHolder = buildTypeHolder;
-        mMergedFlavor = mMainFlavorHolder.getProductFlavor();
-        validateMainFlavor();
-    }
-
-    /**
-     * Add a new ProductFlavor.
+     * Sets the build variant.
      *
-     * If multiple flavors are added, the priority follows the order they are added when it
-     * comes to resolving Android resources overlays (ie earlier added flavors supersedes
-     * latter added ones).
+     * @param variant the configuration of the variant
+     * @param testedVariant the configuration of the tested variant. only applicable if
+     *                      the main config is a test variant.
      *
-     * @param productFlavorHolder the ProductFlavorHolder
      */
-    public void addProductFlavor(ProductFlavorHolder productFlavorHolder) {
-        if (mMainFlavorHolder == null) {
-            throw new IllegalArgumentException(
-                    "main flavor is null. setBuildVariant must be called first");
-        }
-        if (mFlavorHolderList == null) {
-            mFlavorHolderList = new ArrayList<ProductFlavorHolder>();
-        }
-
-        mFlavorHolderList.add(productFlavorHolder);
-        mMergedFlavor = productFlavorHolder.getProductFlavor().mergeOver(mMergedFlavor);
+    public void setBuildVariant(@NonNull VariantConfiguration variant,
+                                @Nullable VariantConfiguration testedVariant) {
+        mVariant = variant;
+        mTestedVariant = testedVariant;
     }
 
     /**
@@ -204,20 +166,6 @@ public class AndroidBuilder {
         return classpath;
     }
 
-    public void setJarDependencies(List<JarDependency> jars) {
-        mJars = jars;
-    }
-
-    /**
-     * Set the Library Project dependencies.
-     * @param directLibraryProjects list of direct dependencies. Each library object should contain
-     *            its own dependencies.
-     */
-    public void setAndroidDependencies(List<AndroidDependency> directLibraryProjects) {
-        mDirectLibraryProjects.addAll(directLibraryProjects);
-        resolveIndirectLibraryDependencies(directLibraryProjects, mFlatLibraryProjects);
-    }
-
     /**
      * Generate the BuildConfig class for the project.
      * @param sourceOutputDir directory where to put this. This is the source folder, not the
@@ -228,23 +176,19 @@ public class AndroidBuilder {
     public void generateBuildConfig(
             @NonNull String sourceOutputDir,
             @Nullable List<String> additionalLines) throws IOException {
-        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
-            throw new IllegalArgumentException("No Product Flavor or Build Type set.");
+        if (mVariant == null) {
+            throw new IllegalArgumentException("No Variant Configuration has been set.");
         }
         if (mTarget == null) {
             throw new IllegalArgumentException("Target not set.");
         }
 
-        File manifest = mMainFlavorHolder.getAndroidManifest();
-        String manifestLocation = manifest.getAbsolutePath();
+        File manifest = mVariant.getDefaultSourceSet().getAndroidManifest();
 
-        String packageName = getPackageOverride(manifestLocation);
-        if (packageName == null) {
-            packageName = getPackageFromManifest(manifestLocation);
-        }
+        String packageName = mVariant.getPackageName(mTestedVariant);
 
         BuildConfigGenerator generator = new BuildConfigGenerator(
-                sourceOutputDir, packageName, mBuildTypeHolder.getBuildType().isDebuggable());
+                sourceOutputDir, packageName, mVariant.getBuildType().isDebuggable());
         generator.generate(additionalLines);
     }
 
@@ -259,8 +203,8 @@ public class AndroidBuilder {
      */
     public void preprocessResources(@NonNull String resOutputDir)
             throws IOException, InterruptedException {
-        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
-            throw new IllegalArgumentException("No Product Flavor or Build Type set.");
+        if (mVariant == null) {
+            throw new IllegalArgumentException("No Variant Configuration has been set.");
         }
         if (mTarget == null) {
             throw new IllegalArgumentException("Target not set.");
@@ -279,21 +223,21 @@ public class AndroidBuilder {
             command.add("-v");
         }
 
-        File typeResLocation = mBuildTypeHolder.getAndroidResources();
+        File typeResLocation = mVariant.getBuildTypeSourceSet().getAndroidResources();
         if (typeResLocation != null && typeResLocation.isDirectory()) {
             command.add("-S");
             command.add(typeResLocation.getAbsolutePath());
         }
 
-        for (ProductFlavorHolder holder : mFlavorHolderList) {
-            File flavorResLocation = holder.getAndroidResources();
+        for (SourceSet sourceSet : mVariant.getFlavorSourceSets()) {
+            File flavorResLocation = sourceSet.getAndroidResources();
             if (flavorResLocation != null && flavorResLocation.isDirectory()) {
                 command.add("-S");
                 command.add(flavorResLocation.getAbsolutePath());
             }
         }
 
-        File mainResLocation = mMainFlavorHolder.getAndroidResources();
+        File mainResLocation = mVariant.getDefaultSourceSet().getAndroidResources();
         if (mainResLocation != null && mainResLocation.isDirectory()) {
             command.add("-S");
             command.add(mainResLocation.getAbsolutePath());
@@ -312,35 +256,42 @@ public class AndroidBuilder {
      *
      * @param outManifestLocation the output location for the merged manifest
      */
-    public void mergeManifest(@NonNull String outManifestLocation) {
-        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
-            throw new IllegalArgumentException("No Product Flavor or Build Type set.");
+    public void processManifest(@NonNull String outManifestLocation) {
+        if (mVariant == null) {
+            throw new IllegalArgumentException("No Variant Configuration has been set.");
         }
         if (mTarget == null) {
             throw new IllegalArgumentException("Target not set.");
         }
 
+        if (mTestedVariant != null) {
+
+        } else {
+            mergeManifest(outManifestLocation);
+        }
+    }
+
+    private void mergeManifest(String outManifestLocation) {
         try {
-            File mainLocation = mMainFlavorHolder.getAndroidManifest();
-            File typeLocation = mBuildTypeHolder.getAndroidManifest();
+            File mainLocation = mVariant.getDefaultSourceSet().getAndroidManifest();
+            File typeLocation = mVariant.getBuildTypeSourceSet().getAndroidManifest();
             if (typeLocation != null && typeLocation.isDirectory() == false) {
                 typeLocation = null;
             }
 
             List<File> flavorManifests = new ArrayList<File>();
-            for (ProductFlavorHolder holder : mFlavorHolderList) {
-                File f = holder.getAndroidManifest();
+            for (SourceSet sourceSet : mVariant.getFlavorSourceSets()) {
+                File f = sourceSet.getAndroidManifest();
                 if (f != null && f.isDirectory()) {
                     flavorManifests.add(f);
                 }
             }
 
             // if no manifest to merge, just copy to location
-            if (typeLocation == null && flavorManifests.isEmpty() &&
-                    mFlatLibraryProjects.isEmpty()) {
+            if (typeLocation == null && flavorManifests.isEmpty() && !mVariant.hasLibraries()) {
                 new FileOp().copyFile(mainLocation, new File(outManifestLocation));
             } else {
-                if (mFlatLibraryProjects.isEmpty()) {
+                if (!mVariant.hasLibraries()) {
 
                     File appMergeOut = new File(outManifestLocation);
 
@@ -364,7 +315,7 @@ public class AndroidBuilder {
 
                     // recursively merge all manifests starting with the leaves and up toward the
                     // root (the app)
-                    mergeLibraryManifests(appMergeOut, mDirectLibraryProjects,
+                    mergeLibraryManifests(appMergeOut, mVariant.getDirectLibraries(),
                             new File(outManifestLocation));
                     }
             }
@@ -375,11 +326,11 @@ public class AndroidBuilder {
 
     private void mergeLibraryManifests(
             File mainManifest,
-            List<AndroidDependency> libraries,
+            Iterable<AndroidDependency> directLibraries,
             File outManifest) throws IOException {
 
         List<File> manifests = new ArrayList<File>();
-        for (AndroidDependency library : libraries) {
+        for (AndroidDependency library : directLibraries) {
             List<AndroidDependency> subLibraries = library.getDependencies();
             if (subLibraries == null || subLibraries.size() == 0) {
                 manifests.add(new File(library.getManifest()));
@@ -410,8 +361,8 @@ public class AndroidBuilder {
             @Nullable String resPackageOutput,
             @Nullable String proguardOutput,
             @NonNull AaptOptions options) throws IOException, InterruptedException {
-        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
-            throw new IllegalArgumentException("No Product Flavor or Build Type set.");
+        if (mVariant == null) {
+            throw new IllegalArgumentException("No Variant Configuration has been set.");
         }
         if (mTarget == null) {
             throw new IllegalArgumentException("Target not set.");
@@ -453,15 +404,16 @@ public class AndroidBuilder {
             useOverlay = true;
         }
 
-        File typeResLocation = mBuildTypeHolder.getAndroidResources();
+        File typeResLocation = mVariant.getBuildTypeSourceSet().getAndroidResources();
         if (typeResLocation != null && typeResLocation.isDirectory()) {
             command.add("-S");
             command.add(typeResLocation.getAbsolutePath());
             useOverlay = true;
         }
 
-        for (ProductFlavorHolder holder : mFlavorHolderList) {
-            File flavorResLocation = holder.getAndroidResources();
+
+        for (SourceSet sourceSet : mVariant.getFlavorSourceSets()) {
+            File flavorResLocation = sourceSet.getAndroidResources();
             if (flavorResLocation != null && typeResLocation.isDirectory()) {
                 command.add("-S");
                 command.add(flavorResLocation.getAbsolutePath());
@@ -469,7 +421,7 @@ public class AndroidBuilder {
             }
         }
 
-        File mainResLocation = mMainFlavorHolder.getAndroidResources();
+        File mainResLocation = mVariant.getDefaultSourceSet().getAndroidResources();
         command.add("-S");
         command.add(mainResLocation.getAbsolutePath());
 
@@ -488,7 +440,7 @@ public class AndroidBuilder {
 //            command.add(flavorAssetsLocation);
 //        }
 
-        File mainAssetsLocation = mMainFlavorHolder.getAndroidAssets();
+        File mainAssetsLocation = mVariant.getDefaultSourceSet().getAndroidAssets();
         if (mainAssetsLocation != null && mainAssetsLocation.isDirectory()) {
             command.add("-A");
             command.add(mainAssetsLocation.getAbsolutePath());
@@ -514,55 +466,60 @@ public class AndroidBuilder {
 
         // options controlled by build variants
 
-        if (mBuildTypeHolder.getBuildType().isDebuggable()) {
+        if (mVariant.getBuildType().isDebuggable()) {
             command.add("--debug-mode");
         }
 
-        String packageOverride = getPackageOverride(manifestFile);
-        if (packageOverride != null) {
-            command.add("--rename-manifest-package");
-            command.add(packageOverride);
-            mLogger.verbose("Inserting package '%s' in AndroidManifest.xml", packageOverride);
-        }
+        if (mVariant.getType() == VariantConfiguration.Type.DEFAULT) {
+            String packageOverride = mVariant.getPackageOverride();
+            if (packageOverride != null) {
+                command.add("--rename-manifest-package");
+                command.add(packageOverride);
+                mLogger.verbose("Inserting package '%s' in AndroidManifest.xml", packageOverride);
+            }
 
-        boolean forceErrorOnReplace = false;
+            boolean forceErrorOnReplace = false;
 
-        int versionCode = mMergedFlavor.getVersionCode();
-        if (versionCode != -1) {
-            command.add("--version-code");
-            command.add(Integer.toString(versionCode));
-            mLogger.verbose("Inserting versionCode '%d' in AndroidManifest.xml", versionCode);
-            forceErrorOnReplace = true;
-        }
+            ProductFlavor mergedFlavor = mVariant.getMergedFlavor();
 
-        String versionName = mMergedFlavor.getVersionName();
-        if (versionName != null) {
-            command.add("--version-name");
-            command.add(versionName);
-            mLogger.verbose("Inserting versionName '%s' in AndroidManifest.xml", versionName);
-            forceErrorOnReplace = true;
-        }
+            int versionCode = mergedFlavor.getVersionCode();
+            if (versionCode != -1) {
+                command.add("--version-code");
+                command.add(Integer.toString(versionCode));
+                mLogger.verbose("Inserting versionCode '%d' in AndroidManifest.xml", versionCode);
+                forceErrorOnReplace = true;
+            }
 
-        int minSdkVersion = mMergedFlavor.getMinSdkVersion();
-        if (minSdkVersion != -1) {
-            command.add("--min-sdk-version");
-            command.add(Integer.toString(minSdkVersion));
-            mLogger.verbose("Inserting minSdkVersion '%d' in AndroidManifest.xml", minSdkVersion);
-            forceErrorOnReplace = true;
-        }
+            String versionName = mergedFlavor.getVersionName();
+            if (versionName != null) {
+                command.add("--version-name");
+                command.add(versionName);
+                mLogger.verbose("Inserting versionName '%s' in AndroidManifest.xml", versionName);
+                forceErrorOnReplace = true;
+            }
 
-        int targetSdkVersion = mMergedFlavor.getTargetSdkVersion();
-        if (targetSdkVersion != -1) {
-            command.add("--target-sdk-version");
-            command.add(Integer.toString(targetSdkVersion));
-            mLogger.verbose("Inserting targetSdkVersion '%d' in AndroidManifest.xml",
-                    targetSdkVersion);
-            forceErrorOnReplace = true;
-        }
+            int minSdkVersion = mergedFlavor.getMinSdkVersion();
+            if (minSdkVersion != -1) {
+                command.add("--min-sdk-version");
+                command.add(Integer.toString(minSdkVersion));
+                mLogger.verbose("Inserting minSdkVersion '%d' in AndroidManifest.xml",
+                        minSdkVersion);
+                forceErrorOnReplace = true;
+            }
+
+            int targetSdkVersion = mergedFlavor.getTargetSdkVersion();
+            if (targetSdkVersion != -1) {
+                command.add("--target-sdk-version");
+                command.add(Integer.toString(targetSdkVersion));
+                mLogger.verbose("Inserting targetSdkVersion '%d' in AndroidManifest.xml",
+                        targetSdkVersion);
+                forceErrorOnReplace = true;
+            }
 
-        if (forceErrorOnReplace) {
-            // TODO: force aapt to fail if replace of versionCode/Name or min/targetSdkVersion fails
-            // Need to add the options to aapt first.
+            if (forceErrorOnReplace) {
+                // TODO: force aapt to fail if replace of versionCode/Name or min/targetSdkVersion fails
+                // Need to add the options to aapt first.
+            }
         }
 
         // AAPT options
@@ -586,8 +543,8 @@ public class AndroidBuilder {
             @NonNull List<String> classesLocation,
             @NonNull String outDexFile,
             @NonNull DexOptions dexOptions) throws IOException, InterruptedException {
-        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
-            throw new IllegalArgumentException("No Product Flavor or Build Type set.");
+        if (mVariant == null) {
+            throw new IllegalArgumentException("No Variant Configuration has been set.");
         }
         if (mTarget == null) {
             throw new IllegalArgumentException("Target not set.");
@@ -631,16 +588,18 @@ public class AndroidBuilder {
             @NonNull String classesDexLocation,
             @Nullable String jniLibsLocation,
             @NonNull String outApkLocation) throws DuplicateFileException {
-        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
-            throw new IllegalArgumentException("No Product Flavor or Build Type set.");
+        if (mVariant == null) {
+            throw new IllegalArgumentException("No Variant Configuration has been set.");
         }
         if (mTarget == null) {
             throw new IllegalArgumentException("Target not set.");
         }
 
+        BuildType buildType = mVariant.getBuildType();
+
         SigningInfo signingInfo = null;
-        if (mBuildTypeHolder.getBuildType().isDebugSigned()) {
-            try {
+        try {
+            if (buildType.isDebugSigned()) {
                 String storeLocation = DebugKeyHelper.defaultDebugKeyStoreLocation();
                 File storeFile = new File(storeLocation);
                 if (storeFile.isDirectory()) {
@@ -656,17 +615,22 @@ public class AndroidBuilder {
 
                 // load the key
                 signingInfo = DebugKeyHelper.getDebugKey(storeLocation, null /*storeStype*/);
-
-            } catch (AndroidLocationException e) {
-                throw new RuntimeException(e);
-            } catch (KeytoolException e) {
-                throw new RuntimeException(e);
-            } catch (FileNotFoundException e) {
-                // this shouldn't happen as we have checked ahead of calling getDebugKey.
-                throw new RuntimeException(e);
+            } else if (mVariant.getMergedFlavor().isSigningReady()) {
+                ProductFlavor flavor = mVariant.getMergedFlavor();
+                signingInfo = KeystoreHelper.getSigningInfo(
+                        flavor.getSigningStoreLocation(),
+                        flavor.getSigningStorePassword(),
+                        null, /*storeStype*/
+                        flavor.getSigningKeyAlias(),
+                        flavor.getSigningKeyPassword());
             }
-        } else {
-            // todo: get the signing info from the flavor.
+        } catch (AndroidLocationException e) {
+            throw new RuntimeException(e);
+        } catch (KeytoolException e) {
+            throw new RuntimeException(e);
+        } catch (FileNotFoundException e) {
+            // this shouldn't happen as we have checked ahead of calling getDebugKey.
+            throw new RuntimeException(e);
         }
 
         try {
@@ -674,21 +638,21 @@ public class AndroidBuilder {
                     outApkLocation, androidResPkgLocation, classesDexLocation,
                     signingInfo, mLogger);
 
-            packager.setDebugJniMode(mBuildTypeHolder.getBuildType().isDebugJniBuild());
+            packager.setDebugJniMode(buildType.isDebugJniBuild());
 
             // figure out conflicts!
             JavaResourceProcessor resProcessor = new JavaResourceProcessor(packager);
 
-            Set<File> buildTypeJavaResLocations = mBuildTypeHolder.getJavaResources();
+            Set<File> buildTypeJavaResLocations = mVariant.getBuildTypeSourceSet().getJavaResources();
             for (File buildTypeJavaResLocation : buildTypeJavaResLocations) {
                 if (buildTypeJavaResLocation != null && buildTypeJavaResLocation.isDirectory()) {
                     resProcessor.addSourceFolder(buildTypeJavaResLocation.getAbsolutePath());
                 }
             }
 
-            for (ProductFlavorHolder holder : mFlavorHolderList) {
+            for (SourceSet sourceSet : mVariant.getFlavorSourceSets()) {
 
-                Set<File> flavorJavaResLocations = holder.getJavaResources();
+                Set<File> flavorJavaResLocations = sourceSet.getJavaResources();
                 for (File flavorJavaResLocation : flavorJavaResLocations) {
                     if (flavorJavaResLocation != null && flavorJavaResLocation.isDirectory()) {
                         resProcessor.addSourceFolder(flavorJavaResLocation.getAbsolutePath());
@@ -696,7 +660,7 @@ public class AndroidBuilder {
                 }
             }
 
-            Set<File> mainJavaResLocations = mMainFlavorHolder.getJavaResources();
+            Set<File> mainJavaResLocations = mVariant.getDefaultSourceSet().getJavaResources();
             for (File mainJavaResLocation : mainJavaResLocations) {
                 if (mainJavaResLocation != null && mainJavaResLocation.isDirectory()) {
                     resProcessor.addSourceFolder(mainJavaResLocation.getAbsolutePath());
@@ -715,66 +679,4 @@ public class AndroidBuilder {
             throw new RuntimeException(e);
         }
     }
-
-    @VisibleForTesting
-    String getPackageOverride(@NonNull String manifestLocation) {
-        String packageName = mMergedFlavor.getPackageName();
-        String packageSuffix = mBuildTypeHolder.getBuildType().getPackageNameSuffix();
-
-        if (packageSuffix != null) {
-            if (packageName == null) {
-                packageName = getPackageFromManifest(manifestLocation);
-            }
-
-            if (packageSuffix.charAt(0) == '.') {
-                packageName = packageName + packageSuffix;
-            } else {
-                packageName = packageName + '.' + packageSuffix;
-            }
-        }
-
-        return packageName;
-    }
-
-    @VisibleForTesting
-    String getPackageFromManifest(@NonNull String manifestLocation) {
-        return mManifestParser.getPackage(manifestLocation);
-    }
-
-    /**
-     * Resolves a given list of libraries, finds out if they depend on other libraries, and
-     * returns a flat list of all the direct and indirect dependencies in the proper order (first
-     * is higher priority when calling aapt).
-     * @param directDependencies the libraries to resolve
-     * @param outFlatDependencies where to store all the libraries.
-     */
-    @VisibleForTesting
-    void resolveIndirectLibraryDependencies(List<AndroidDependency> directDependencies,
-            List<AndroidDependency> outFlatDependencies) {
-        // loop in the inverse order to resolve dependencies on the libraries, so that if a library
-        // is required by two higher level libraries it can be inserted in the correct place
-        for (int i = directDependencies.size() - 1  ; i >= 0 ; i--) {
-            AndroidDependency library = directDependencies.get(i);
-
-            // get its libraries
-            List<AndroidDependency> dependencies = library.getDependencies();
-
-            // resolve the dependencies for those libraries
-            resolveIndirectLibraryDependencies(dependencies, outFlatDependencies);
-
-            // and add the current one (if needed) in front (higher priority)
-            if (outFlatDependencies.contains(library) == false) {
-                outFlatDependencies.add(0, library);
-            }
-        }
-    }
-
-    protected void validateMainFlavor() {
-        File manifest = mMainFlavorHolder.getAndroidManifest();
-        if (!manifest.isFile()) {
-            throw new IllegalArgumentException(
-                    "Main Manifest missing from " + manifest.getAbsolutePath());
-        }
-    }
-
 }
index 3f5fa11..9d62313 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.builder;
 
 import com.android.annotations.Nullable;
 
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 /**
  * Class able to generate a BuildConfig class in Android project.
@@ -36,8 +29,7 @@ import java.util.Map.Entry;
  */
 class BuildConfigGenerator {
 
-    public final static String BUILD_CONFIG_NAME = "BuildConfig.java";
-
+    private final static String TEMPLATE = "BuildConfig.template";
     private final static String PH_PACKAGE = "#PACKAGE#";
     private final static String PH_DEBUG = "#DEBUG#";
     private final static String PH_LINES = "#ADDITIONAL_LINES#";
@@ -46,6 +38,8 @@ class BuildConfigGenerator {
     private final String mAppPackage;
     private final boolean mDebug;
 
+    public final static String BUILD_CONFIG_NAME = "BuildConfig.java";
+
     /**
      * Creates a generator
      * @param genFolder the gen folder of the project
@@ -76,8 +70,6 @@ class BuildConfigGenerator {
      * @param additionalLines a list of additional lines to be added to the class.
      */
     public void generate(@Nullable List<String> additionalLines) throws IOException {
-        String template = readEmbeddedTextFile("BuildConfig.template");
-
         Map<String, String> map = new HashMap<String, String>();
         map.put(PH_PACKAGE, mAppPackage);
         map.put(PH_DEBUG, Boolean.toString(mDebug));
@@ -93,76 +85,17 @@ class BuildConfigGenerator {
             map.put(PH_LINES, "");
         }
 
-        String content = replaceParameters(template, map);
-
         File pkgFolder = getFolderPath();
         if (pkgFolder.isDirectory() == false) {
             pkgFolder.mkdirs();
         }
 
         File buildConfigJava = new File(pkgFolder, BUILD_CONFIG_NAME);
-        writeFile(buildConfigJava, content);
-    }
-
-    /**
-     * Reads and returns the content of a text file embedded in the jar file.
-     * @param filepath the file path to the text file
-     * @return null if the file could not be read
-     * @throws IOException
-     */
-    private String readEmbeddedTextFile(String filepath) throws IOException {
-        InputStream is = BuildConfigGenerator.class.getResourceAsStream(filepath);
-        if (is != null) {
-            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
-
-            String line;
-            StringBuilder total = new StringBuilder(reader.readLine());
-            while ((line = reader.readLine()) != null) {
-                total.append('\n');
-                total.append(line);
-            }
-
-            return total.toString();
-        }
 
-        // this really shouldn't happen unless the sdklib packaging is broken.
-        throw new IOException("BuildConfig template is missing!");
-    }
-
-    private void writeFile(File file, String content) throws IOException {
-        FileOutputStream fos = null;
-        try {
-            fos = new FileOutputStream(file);
-            InputStream source = new ByteArrayInputStream(content.getBytes("UTF-8"));
-
-            byte[] buffer = new byte[1024];
-            int count = 0;
-            while ((count = source.read(buffer)) != -1) {
-                fos.write(buffer, 0, count);
-            }
-        } finally {
-            if (fos != null) {
-                fos.close();
-            }
-        }
-    }
-
-    /**
-     * Replaces placeholders found in a string with values.
-     *
-     * @param str the string to search for placeholders.
-     * @param parameters a map of <placeholder, Value> to search for in the string
-     * @return A new String object with the placeholder replaced by the values.
-     */
-    private String replaceParameters(String str, Map<String, String> parameters) {
-
-        for (Entry<String, String> entry : parameters.entrySet()) {
-            String value = entry.getValue();
-            if (value != null) {
-                str = str.replaceAll(entry.getKey(), value);
-            }
-        }
+        TemplateProcessor processor = new TemplateProcessor(
+                BuildConfigGenerator.class.getResourceAsStream(TEMPLATE),
+                map);
 
-        return str;
+        processor.generate(buildConfigJava);
     }
 }
index de796e4..51e5679 100644 (file)
@@ -107,7 +107,21 @@ public class BuildType {
         return mZipAlign;
     }
 
+    @Override
+    public String toString() {
+        return "BuildType{" +
+                "name='" + mName + '\'' +
+                ", debuggable=" + mDebuggable +
+                ", debugJniBuild=" + mDebugJniBuild +
+                ", debugSigned=" + mDebugSigned +
+                ", packageNameSuffix='" + mPackageNameSuffix + '\'' +
+                ", runProguard=" + mRunProguard +
+                ", zipAlign=" + mZipAlign +
+                '}';
+    }
+
+
     /*
 Buildconfig: DEBUG flag + other custom properties?
-     */
+    */
 }
diff --git a/builder/src/main/java/com/android/builder/BuildTypeHolder.java b/builder/src/main/java/com/android/builder/BuildTypeHolder.java
deleted file mode 100644 (file)
index e74dc92..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.builder;
-
-/**
- * Holds a BuildType and gives access to its various paths
- */
-public interface BuildTypeHolder extends PathProvider {
-
-    BuildType getBuildType();
-}
index 74bf0d8..3fec389 100644 (file)
 package com.android.builder;
 
 import com.android.xml.AndroidXPathFactory;
-
 import org.xml.sax.InputSource;
 
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathExpressionException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 
 public class DefaultManifestParser implements ManifestParser {
 
     @Override
-    public String getPackage(String manifestFile) {
+    public String getPackage(File manifestFile) {
         XPath xpath = AndroidXPathFactory.newXPath();
 
         try {
index 47d16fd..2cf0925 100644 (file)
@@ -18,7 +18,9 @@ package com.android.builder;
 
 import com.android.annotations.NonNull;
 
+import java.io.File;
+
 public interface ManifestParser {
 
-    String getPackage(@NonNull String manifestFile);
+    String getPackage(@NonNull File manifestFile);
 }
diff --git a/builder/src/main/java/com/android/builder/ProductFlavorHolder.java b/builder/src/main/java/com/android/builder/ProductFlavorHolder.java
deleted file mode 100644 (file)
index a95930d..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.builder;
-
-/**
- * Holds a ProductFlavor and gives access to its various paths
- */
-public interface ProductFlavorHolder extends PathProvider {
-
-    ProductFlavor getProductFlavor();
-}
@@ -19,11 +19,10 @@ import java.io.File;
 import java.util.Set;
 
 /**
- * Provides paths for configuration (either ProductFlavor or BuildType)
+ * Represent an Android SourceSet for a given configuration.
  */
-public interface PathProvider {
+public interface SourceSet {
 
-    Set<File> getJavaSource();
     Set<File> getJavaResources();
 
     File getAndroidResources();
diff --git a/builder/src/main/java/com/android/builder/TemplateProcessor.java b/builder/src/main/java/com/android/builder/TemplateProcessor.java
new file mode 100644 (file)
index 0000000..591d9a9
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Processes a template to generate a file somewhere.
+ */
+class TemplateProcessor {
+
+    private final InputStream mTemplateStream;
+    private final Map<String, String> mPlaceHolderMap;
+
+    /**
+     * Creates a processor
+     * @param templateStream the stream to read the template file from
+     * @param placeHolderMap
+     */
+    public TemplateProcessor(InputStream templateStream, Map<String, String> placeHolderMap) {
+        mTemplateStream = templateStream;
+        mPlaceHolderMap = placeHolderMap;
+    }
+
+    /**
+     * Generates the file from the template.
+     * @param outputFile the file to create
+     */
+    public void generate(File outputFile) throws IOException {
+        String template = readEmbeddedTextFile(mTemplateStream);
+
+        String content = replaceParameters(template, mPlaceHolderMap);
+
+        writeFile(outputFile, content);
+    }
+
+    /**
+     * Reads and returns the content of a text file embedded in the jar file.
+     * @param templateStream the stream to read the template file from
+     * @return null if the file could not be read
+     * @throws java.io.IOException
+     */
+    private String readEmbeddedTextFile(InputStream templateStream) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(templateStream));
+
+        String line;
+        StringBuilder total = new StringBuilder(reader.readLine());
+        while ((line = reader.readLine()) != null) {
+            total.append('\n');
+            total.append(line);
+        }
+
+        return total.toString();
+    }
+
+    private void writeFile(File file, String content) throws IOException {
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(file);
+            InputStream source = new ByteArrayInputStream(content.getBytes("UTF-8"));
+
+            byte[] buffer = new byte[1024];
+            int count = 0;
+            while ((count = source.read(buffer)) != -1) {
+                fos.write(buffer, 0, count);
+            }
+        } finally {
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    /**
+     * Replaces placeholders found in a string with values.
+     *
+     * @param str the string to search for placeholders.
+     * @param parameters a map of <placeholder, Value> to search for in the string
+     * @return A new String object with the placeholder replaced by the values.
+     */
+    private String replaceParameters(String str, Map<String, String> parameters) {
+
+        for (Entry<String, String> entry : parameters.entrySet()) {
+            String value = entry.getValue();
+            if (value != null) {
+                str = str.replaceAll(entry.getKey(), value);
+            }
+        }
+
+        return str;
+    }
+}
diff --git a/builder/src/main/java/com/android/builder/TestManifestGenerator.java b/builder/src/main/java/com/android/builder/TestManifestGenerator.java
new file mode 100644 (file)
index 0000000..fa1805b
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.builder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Generate an AndroidManifest.xml file for test projects.
+ */
+public class TestManifestGenerator {
+
+    private final static String TEMPLATE = "BuildConfig.template";
+    private final static String PH_PACKAGE = "#PACKAGE#";
+    private final static String PH_TEST_PACKAGE = "#TESTPACKAGE#";
+    private final static String PH_TEST_RUNNER = "#TESTRUNNER#";
+
+    private final static String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner";
+
+    private final String mOutputFile;
+    private final String mPackageName;
+    private final String mTestPackageName;
+    private final String mTestRunnerName;
+
+    TestManifestGenerator(String outputFile,
+                          String packageName,
+                          String testPackageName,
+                          String testRunnerName) {
+        mOutputFile = outputFile;
+        mPackageName = packageName;
+        mTestPackageName = testPackageName;
+        mTestRunnerName = testRunnerName != null ? testRunnerName : DEFAULT_TEST_RUNNER;
+    }
+
+    public void generate() throws IOException {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put(PH_PACKAGE, mPackageName);
+        map.put(PH_TEST_PACKAGE, mTestPackageName);
+        map.put(PH_TEST_RUNNER, mTestRunnerName);
+
+        TemplateProcessor processor = new TemplateProcessor(
+                TestManifestGenerator.class.getResourceAsStream(TEMPLATE),
+                map);
+
+        processor.generate(new File(mOutputFile));
+
+    }
+}
diff --git a/builder/src/main/java/com/android/builder/VariantConfiguration.java b/builder/src/main/java/com/android/builder/VariantConfiguration.java
new file mode 100644 (file)
index 0000000..0d211fa
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Variant configuration.
+ */
+public class VariantConfiguration {
+
+    private final static ManifestParser sManifestParser = new DefaultManifestParser();
+
+    private final ProductFlavor mDefaultConfig;
+    private final SourceSet mDefaultSourceSet;
+
+    private final BuildType mBuildType;
+    private final SourceSet mBuildTypeSourceSet;
+
+    private final List<ProductFlavor> mFlavorConfigs = new ArrayList<ProductFlavor>();
+    private final List<SourceSet> mFlavorSourceSets = new ArrayList<SourceSet>();
+
+    private final Type mType;
+    private ProductFlavor mMergedFlavor;
+
+    private List<JarDependency> mJars;
+
+    /** List of direct library project dependencies. Each object defines its own dependencies. */
+    private final List<AndroidDependency> mDirectLibraryProjects =
+            new ArrayList<AndroidDependency>();
+    /** list of all library project dependencies in the flat list.
+     * The order is based on the order needed to call aapt: earlier libraries override resources
+     * of latter ones. */
+    private final List<AndroidDependency> mFlatLibraryProjects = new ArrayList<AndroidDependency>();
+
+
+    public static enum Type {
+        DEFAULT, LIBRARY, TEST;
+    }
+
+    /**
+     * Creates the configuration with the base source set, and whether it is a library.
+     * @param defaultConfig
+     * @param defaultSourceSet
+     * @param buildType
+     * @param buildTypeSourceSet
+     * @param type
+     */
+    public VariantConfiguration(
+            @NonNull ProductFlavor defaultConfig, @NonNull SourceSet defaultSourceSet,
+            @NonNull BuildType buildType, @NonNull SourceSet buildTypeSourceSet,
+            @NonNull Type type) {
+        mDefaultConfig = defaultConfig;
+        mDefaultSourceSet = defaultSourceSet;
+        mBuildType = buildType;
+        mBuildTypeSourceSet = buildTypeSourceSet;
+        mType = type;
+
+        mMergedFlavor = mDefaultConfig;
+
+        validate();
+    }
+
+    /**
+     * Add a new configured ProductFlavor.
+     *
+     * If multiple flavors are added, the priority follows the order they are added when it
+     * comes to resolving Android resources overlays (ie earlier added flavors supersedes
+     * latter added ones).
+     *
+     * @param sourceSet the configured product flavor
+     */
+    public void addProductFlavor(@NonNull ProductFlavor productFlavor, @NonNull SourceSet sourceSet) {
+        mFlavorConfigs.add(productFlavor);
+        mFlavorSourceSets.add(sourceSet);
+        mMergedFlavor = productFlavor.mergeOver(mMergedFlavor);
+    }
+
+    public void setJarDependencies(List<JarDependency> jars) {
+        mJars = jars;
+    }
+
+    /**
+     * Set the Library Project dependencies.
+     * @param directLibraryProjects list of direct dependencies. Each library object should contain
+     *            its own dependencies.
+     */
+    public void setAndroidDependencies(List<AndroidDependency> directLibraryProjects) {
+        mDirectLibraryProjects.addAll(directLibraryProjects);
+        resolveIndirectLibraryDependencies(directLibraryProjects, mFlatLibraryProjects);
+    }
+
+    public ProductFlavor getDefaultConfig() {
+        return mDefaultConfig;
+    }
+
+    public SourceSet getDefaultSourceSet() {
+        return mDefaultSourceSet;
+    }
+
+    public ProductFlavor getMergedFlavor() {
+        return mMergedFlavor;
+    }
+
+    public BuildType getBuildType() {
+        return mBuildType;
+    }
+
+    public SourceSet getBuildTypeSourceSet() {
+        return mBuildTypeSourceSet;
+    }
+
+    public boolean hasFlavors() {
+        return !mFlavorConfigs.isEmpty();
+    }
+
+    /**
+     * @Deprecated this is only valid until we move to more than one flavor
+     */
+    @Deprecated
+    public ProductFlavor getFirstFlavor() {
+        return mFlavorConfigs.get(0);
+    }
+
+    /**
+     * @Deprecated this is only valid until we move to more than one flavor
+     */
+    @Deprecated
+    public SourceSet getFirstFlavorSourceSet() {
+        return mFlavorSourceSets.get(0);
+    }
+
+    public Iterable<ProductFlavor> getFlavorConfigs() {
+        return mFlavorConfigs;
+    }
+
+    public Iterable<SourceSet> getFlavorSourceSets() {
+        return mFlavorSourceSets;
+    }
+
+    public boolean hasLibraries() {
+        return !mDirectLibraryProjects.isEmpty();
+    }
+
+    public Iterable<AndroidDependency> getDirectLibraries() {
+        return mDirectLibraryProjects;
+    }
+
+    public Iterable<AndroidDependency> getFlatLibraries() {
+        return mFlatLibraryProjects;
+    }
+
+    public Type getType() {
+        return mType;
+    }
+
+    /**
+     * Resolves a given list of libraries, finds out if they depend on other libraries, and
+     * returns a flat list of all the direct and indirect dependencies in the proper order (first
+     * is higher priority when calling aapt).
+     * @param directDependencies the libraries to resolve
+     * @param outFlatDependencies where to store all the libraries.
+     */
+    @VisibleForTesting
+    void resolveIndirectLibraryDependencies(List<AndroidDependency> directDependencies,
+                                            List<AndroidDependency> outFlatDependencies) {
+        // loop in the inverse order to resolve dependencies on the libraries, so that if a library
+        // is required by two higher level libraries it can be inserted in the correct place
+        for (int i = directDependencies.size() - 1  ; i >= 0 ; i--) {
+            AndroidDependency library = directDependencies.get(i);
+
+            // get its libraries
+            List<AndroidDependency> dependencies = library.getDependencies();
+
+            // resolve the dependencies for those libraries
+            resolveIndirectLibraryDependencies(dependencies, outFlatDependencies);
+
+            // and add the current one (if needed) in front (higher priority)
+            if (outFlatDependencies.contains(library) == false) {
+                outFlatDependencies.add(0, library);
+            }
+        }
+    }
+
+    /**
+     * Returns the package name for this variant. This could be coming from the manifest or
+     * could be overridden through the product flavors.
+     * @param testedVariant the tested variant. This is needed if this variant is of type
+     *                      {@link Type#TEST}
+     * @return the package
+     */
+    public String getPackageName(VariantConfiguration testedVariant) {
+        String packageName;
+
+        if (mType == Type.TEST) {
+            packageName = getTestPackage(testedVariant);
+        } else {
+            packageName = getPackageOverride();
+            if (packageName == null) {
+                packageName = getPackageFromManifest();
+            }
+        }
+
+        return packageName;
+    }
+
+    /**
+     * Returns the package override values coming from the Product Flavor. If the package is not
+     * overridden then this returns null.
+     * @return the package override or null
+     */
+    public String getPackageOverride() {
+
+        String packageName = mMergedFlavor.getPackageName();
+        String packageSuffix = mBuildType.getPackageNameSuffix();
+
+        if (packageSuffix != null) {
+            if (packageName == null) {
+                packageName = getPackageFromManifest();
+            }
+
+            if (packageSuffix.charAt(0) == '.') {
+                packageName = packageName + packageSuffix;
+            } else {
+                packageName = packageName + '.' + packageSuffix;
+            }
+        }
+
+        return packageName;
+    }
+
+    /**
+     * Returns the package name of the test app.
+     * @return the package name for the test app.
+     */
+    public String getTestPackage(VariantConfiguration testedVariant) {
+        String testPackage = mMergedFlavor.getTestPackageName();
+        if (testPackage == null) {
+            String testedPackage = testedVariant.getPackageName(null);
+
+            testPackage = testedPackage + ".test";
+        }
+
+        return testPackage;
+    }
+
+    /**
+     * Reads the package name from the manifest.
+     * @return
+     */
+    @VisibleForTesting
+    String getPackageFromManifest() {
+        File manifestLocation = mDefaultSourceSet.getAndroidManifest();
+        return sManifestParser.getPackage(manifestLocation);
+    }
+
+    protected void validate() {
+        if (mType != Type.TEST) {
+            File manifest = mDefaultSourceSet.getAndroidManifest();
+            if (!manifest.isFile()) {
+                throw new IllegalArgumentException(
+                        "Main Manifest missing from " + manifest.getAbsolutePath());
+            }
+        }
+    }
+}
diff --git a/builder/src/main/resources/com/android/builder/AndroidManifest.template b/builder/src/main/resources/com/android/builder/AndroidManifest.template
new file mode 100644 (file)
index 0000000..b4a792f
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="#PACKAGE#">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="#TESTRUNNER#"
+                     android:targetPackage="#TESTPACKAGE#"
+                     android:label="Tests for #TESTPACKAGE#"/>
+</manifest>
diff --git a/builder/src/test/java/com/android/builder/AndroidBuilderTest.java b/builder/src/test/java/com/android/builder/AndroidBuilderTest.java
deleted file mode 100644 (file)
index f4192b3..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder;
-
-import com.android.annotations.NonNull;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-import junit.framework.TestCase;
-
-public class AndroidBuilderTest extends TestCase {
-
-    private ProductFlavorHolder mMain;
-    private ProductFlavorHolder mFlavor;
-    private BuildTypeHolder mDebug;
-
-    private static class AndroidBuilderMock extends AndroidBuilder {
-
-        public AndroidBuilderMock(@NonNull SdkParser sdkParser, ILogger logger, boolean verboseExec) {
-            super(sdkParser, logger, verboseExec);
-        }
-
-        AndroidBuilderMock(@NonNull SdkParser sdkParser, @NonNull ManifestParser manifestParser, @NonNull CommandLineRunner cmdLineRunner, @NonNull ILogger logger, boolean verboseExec) {
-            super(sdkParser, manifestParser, cmdLineRunner, logger, verboseExec);
-        }
-
-        @Override
-        protected void validateMainFlavor() {
-            // do nothing
-        }
-    }
-
-    private static class ManifestParserMock implements ManifestParser {
-
-        private final String mPackageName;
-
-        ManifestParserMock(String packageName) {
-            mPackageName = packageName;
-        }
-
-        @Override
-        public String getPackage(String manifestFile) {
-            return mPackageName;
-        }
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        mMain = new ProductFlavorHolderMock(new ProductFlavor("main"));
-        mFlavor = new ProductFlavorHolderMock(new ProductFlavor("flavor"));
-        mDebug = new BuildTypeHolderMock(new BuildType("debug"));
-    }
-
-    public void testPackageOverrideNone() {
-        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
-                new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
-
-        builder.setBuildVariant(mMain, mDebug);
-        builder.addProductFlavor(mFlavor);
-
-        assertNull(builder.getPackageOverride(""));
-    }
-
-    public void testPackageOverridePackageFromFlavor() {
-        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
-                new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
-
-        mFlavor.getProductFlavor().setPackageName("foo.bar");
-
-        builder.setBuildVariant(mMain, mDebug);
-        builder.addProductFlavor(mFlavor);
-
-        assertEquals("foo.bar", builder.getPackageOverride(""));
-    }
-
-    public void testPackageOverridePackageFromFlavorWithSuffix() {
-        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
-                new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
-
-        mFlavor.getProductFlavor().setPackageName("foo.bar");
-        mDebug.getBuildType().setPackageNameSuffix(".fortytwo");
-
-        builder.setBuildVariant(mMain, mDebug);
-        builder.addProductFlavor(mFlavor);
-
-        assertEquals("foo.bar.fortytwo", builder.getPackageOverride(""));
-    }
-
-    public void testPackageOverridePackageFromFlavorWithSuffix2() {
-        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
-                new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
-
-        mFlavor.getProductFlavor().setPackageName("foo.bar");
-        mDebug.getBuildType().setPackageNameSuffix("fortytwo");
-
-        builder.setBuildVariant(mMain, mDebug);
-        builder.addProductFlavor(mFlavor);
-
-        assertEquals("foo.bar.fortytwo", builder.getPackageOverride(""));
-    }
-
-    public void testPackageOverridePackageWithSuffixOnly() {
-        StdLogger logger = new StdLogger(StdLogger.Level.ERROR);
-
-        AndroidBuilder builder = new AndroidBuilderMock(
-                new DefaultSdkParser(""),
-                new ManifestParserMock("fake.package.name"),
-                new CommandLineRunner(logger),
-                logger,
-                false /*verboseExec*/);
-
-        mDebug.getBuildType().setPackageNameSuffix("fortytwo");
-
-        builder.setBuildVariant(mMain, mDebug);
-        builder.addProductFlavor(mFlavor);
-
-        assertEquals("fake.package.name.fortytwo", builder.getPackageOverride(""));
-    }
-}
diff --git a/builder/src/test/java/com/android/builder/BuildTypeHolderMock.java b/builder/src/test/java/com/android/builder/BuildTypeHolderMock.java
deleted file mode 100644 (file)
index 0e0ae48..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder;
-
-import java.io.File;
-import java.util.Set;
-
-class BuildTypeHolderMock implements BuildTypeHolder {
-
-    private final BuildType mBuildType;
-
-    BuildTypeHolderMock(BuildType buildType) {
-        mBuildType = buildType;
-    }
-
-    @Override
-    public BuildType getBuildType() {
-        return mBuildType;
-    }
-
-    @Override
-    public Set<File> getJavaSource() {
-        return null;
-    }
-
-    @Override
-    public Set<File> getJavaResources() {
-        return null;
-    }
-
-    @Override
-    public File getAndroidResources() {
-        return null;
-    }
-
-    @Override
-    public File getAndroidAssets() {
-        return null;
-    }
-
-    @Override
-    public File getAndroidManifest() {
-        return null;
-    }
-
-    @Override
-    public File getAidlSource() {
-        return null;
-    }
-
-    @Override
-    public File getRenderscriptSource() {
-        return null;
-    }
-
-    @Override
-    public File getNativeSource() {
-        return null;
-    }
-}
index 1fecbb5..6bdff49 100644 (file)
@@ -25,7 +25,7 @@ public class BuildTypeTest extends TestCase {
 
         assertTrue(type.isDebuggable());
         assertTrue(type.isDebugJniBuild());
-        assertTrue(type.isDebugSigningKey());
+        assertTrue(type.isDebugSigned());
     }
 
     public void testRelease() {
@@ -33,6 +33,6 @@ public class BuildTypeTest extends TestCase {
 
         assertFalse(type.isDebuggable());
         assertFalse(type.isDebugJniBuild());
-        assertFalse(type.isDebugSigningKey());
+        assertFalse(type.isDebugSigned());
     }
 }
  * limitations under the License.
  */
 
-package com.android.builder.samples;
-
-import com.android.builder.PathProvider;
+package com.android.builder;
 
 import java.io.File;
 import java.util.Collections;
 import java.util.Set;
 
 /**
- * Implementation of PathProvider for testing that provides the default convention paths.
+ * Implementation of SourceSet for testing that provides the default convention paths.
  */
-public class DefaultPathProvider implements PathProvider {
-
-    private final String mRoot;
+class MockSourceSet implements SourceSet {
 
-    DefaultPathProvider(String root) {
+    public MockSourceSet(String root) {
         mRoot = root;
     }
 
-    @Override
-    public Set<File> getJavaSource() {
-        return Collections.singleton(new File(mRoot, "java"));
-    }
+    private final String mRoot;
 
     @Override
     public Set<File> getJavaResources() {
diff --git a/builder/src/test/java/com/android/builder/ProductFlavorHolderMock.java b/builder/src/test/java/com/android/builder/ProductFlavorHolderMock.java
deleted file mode 100644 (file)
index f887a14..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder;
-
-import java.io.File;
-import java.util.Set;
-
-class ProductFlavorHolderMock implements ProductFlavorHolder {
-
-    private final ProductFlavor mProductFlavor;
-
-    ProductFlavorHolderMock(ProductFlavor productFlavor) {
-        mProductFlavor = productFlavor;
-    }
-
-    @Override
-    public ProductFlavor getProductFlavor() {
-        return mProductFlavor;
-    }
-
-    @Override
-    public Set<File> getJavaSource() {
-        return null;
-    }
-
-    @Override
-    public Set<File> getJavaResources() {
-        return null;
-    }
-
-    @Override
-    public File getAndroidResources() {
-        return null;
-    }
-
-    @Override
-    public File getAndroidAssets() {
-        return null;
-    }
-
-    @Override
-    public File getAndroidManifest() {
-        return null;
-    }
-
-    @Override
-    public File getAidlSource() {
-        return null;
-    }
-
-    @Override
-    public File getRenderscriptSource() {
-        return null;
-    }
-
-    @Override
-    public File getNativeSource() {
-        return null;
-    }
-}
diff --git a/builder/src/test/java/com/android/builder/VariantConfigurationTest.java b/builder/src/test/java/com/android/builder/VariantConfigurationTest.java
new file mode 100644 (file)
index 0000000..2be0a48
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class VariantConfigurationTest extends TestCase {
+
+    private ProductFlavor mDefaultConfig;
+    private ProductFlavor mFlavorConfig;
+    private BuildType mBuildType;
+
+    private static class ManifestParserMock implements ManifestParser {
+
+        private final String mPackageName;
+
+        ManifestParserMock(String packageName) {
+            mPackageName = packageName;
+        }
+
+        public String getPackage(File manifestFile) {
+            return mPackageName;
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        mDefaultConfig = new ProductFlavor("main");
+        mFlavorConfig = new ProductFlavor("flavor");
+        mBuildType = new BuildType("debug");
+    }
+
+    public void testPackageOverrideNone() {
+        VariantConfiguration variant = getVariant();
+
+        assertNull(variant.getPackageOverride());
+    }
+
+    public void testPackageOverridePackageFromFlavor() {
+        mFlavorConfig.setPackageName("foo.bar");
+
+        VariantConfiguration variant = getVariant();
+
+        assertEquals("foo.bar", variant.getPackageOverride());
+    }
+
+    public void testPackageOverridePackageFromFlavorWithSuffix() {
+        mFlavorConfig.setPackageName("foo.bar");
+        mBuildType.setPackageNameSuffix(".fortytwo");
+
+        VariantConfiguration variant = getVariant();
+
+        assertEquals("foo.bar.fortytwo", variant.getPackageOverride());
+    }
+
+    public void testPackageOverridePackageFromFlavorWithSuffix2() {
+        mFlavorConfig.setPackageName("foo.bar");
+        mBuildType.setPackageNameSuffix("fortytwo");
+
+        VariantConfiguration variant = getVariant();
+
+        assertEquals("foo.bar.fortytwo", variant.getPackageOverride());
+    }
+
+    public void testPackageOverridePackageWithSuffixOnly() {
+
+        mBuildType.setPackageNameSuffix("fortytwo");
+
+        VariantConfiguration variant = getVariantWithManifestPackage("fake.package.name");
+
+        assertEquals("fake.package.name.fortytwo", variant.getPackageOverride());
+    }
+
+    private VariantConfiguration getVariant() {
+        VariantConfiguration variant = new VariantConfiguration(
+                mDefaultConfig, new MockSourceSet("main"),
+                mBuildType, new MockSourceSet("debug"),
+                VariantConfiguration.Type.DEFAULT) {
+            // don't do validation.
+            @Override
+            protected void validate() {
+
+            }
+        };
+
+        variant.addProductFlavor(mFlavorConfig, new MockSourceSet("custom"));
+
+        return variant;
+    }
+
+    private VariantConfiguration getVariantWithManifestPackage(final String packageName) {
+        VariantConfiguration variant = new VariantConfiguration(
+                mDefaultConfig, new MockSourceSet("main"),
+                mBuildType, new MockSourceSet("debug"),
+                VariantConfiguration.Type.DEFAULT) {
+            @Override
+            String getPackageFromManifest() {
+                return packageName;
+            }
+            // don't do validation.
+            @Override
+            protected void validate() {
+
+            }
+        };
+
+        variant.addProductFlavor(mFlavorConfig, new MockSourceSet("custom"));
+        return variant;
+    }
+}
diff --git a/builder/src/test/java/com/android/builder/samples/Main.java b/builder/src/test/java/com/android/builder/samples/Main.java
deleted file mode 100644 (file)
index 5cbc21a..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.android.builder.samples;
-
-import com.android.builder.AaptOptions;
-import com.android.builder.AndroidBuilder;
-import com.android.builder.BuildType;
-import com.android.builder.BuildTypeHolder;
-import com.android.builder.DefaultSdkParser;
-import com.android.builder.ProductFlavor;
-import com.android.builder.ProductFlavorHolder;
-import com.android.utils.StdLogger;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-
-public class Main {
-
-    private static class DefaultBuildTypeHolder extends DefaultPathProvider
-            implements BuildTypeHolder {
-
-        private final BuildType mBuildType;
-
-        DefaultBuildTypeHolder(String root, BuildType buildType) {
-            super(root + File.separatorChar + "src" + File.separatorChar + buildType.getName());
-            mBuildType = buildType;
-        }
-
-        @Override
-        public BuildType getBuildType() {
-            return mBuildType;
-        }
-    }
-
-    private static class DefaultProductFlavorHolder extends DefaultPathProvider
-            implements ProductFlavorHolder {
-
-        private final ProductFlavor mProductFlavor;
-
-        DefaultProductFlavorHolder(String root, ProductFlavor productFlavor) {
-            super(root + File.separatorChar + "src" + File.separatorChar + productFlavor.getName());
-            mProductFlavor = productFlavor;
-        }
-
-        @Override
-        public ProductFlavor getProductFlavor() {
-            return mProductFlavor;
-        }
-    }
-
-    /**
-     * Usage: <sdklocation> <samplelocation>
-     *
-     *
-     * @param args
-     * @throws IOException
-     * @throws InterruptedException
-     */
-    public static void main(String[] args) throws IOException, InterruptedException {
-
-        DefaultSdkParser parser = new DefaultSdkParser(args[0]);
-
-        AndroidBuilder builder = new AndroidBuilder(parser,
-                new StdLogger(StdLogger.Level.VERBOSE), true);
-        builder.setTarget("android-15");
-
-        ProductFlavor mainFlavor = new ProductFlavor("main");
-        ProductFlavor customFlavor = new ProductFlavor("custom");
-        BuildType debug = new BuildType(BuildType.DEBUG);
-
-        customFlavor.setMinSdkVersion(15);
-        customFlavor.setTargetSdkVersion(16);
-
-        AaptOptions aaptOptions = new AaptOptions();
-
-        String sample = args[1];
-        String build = sample + File.separator + "build";
-        checkFolder(build);
-
-        String gen = build + File.separator + "gen";
-        checkFolder(gen);
-
-        String outRes = build + File.separator + "res";
-        checkFolder(outRes);
-
-        DefaultProductFlavorHolder mainHolder = new DefaultProductFlavorHolder(sample, mainFlavor);
-        builder.setBuildVariant(
-                mainHolder,
-                new DefaultBuildTypeHolder(sample, debug));
-        builder.addProductFlavor(new DefaultProductFlavorHolder(sample, customFlavor));
-
-
-        String[] lines = new String[] {
-                "public final static int A = 1;"
-        };
-        builder.generateBuildConfig(
-                gen,
-                Arrays.asList(lines));
-
-        builder.preprocessResources(
-                outRes);
-
-        builder.processResources(
-                mainHolder.getAndroidManifest().getAbsolutePath(),
-                outRes,
-                gen,
-                build + File.separator + "foo.apk_",
-                build + File.separator + "foo.proguard.txt",
-                aaptOptions);
-    }
-
-    private static void checkFolder(String path) {
-        File folder = new File(path);
-        if (folder.exists() == false) {
-            folder.mkdirs();
-        }
-    }
-
-}
index 3128f89..93e0d38 100644 (file)
  */
 package com.android.build.gradle
 
+import com.android.build.gradle.internal.ApplicationVariant
+import com.android.build.gradle.internal.ProductFlavorData
 import com.android.builder.AndroidBuilder
 import com.android.builder.DefaultSdkParser
+import com.android.builder.ProductFlavor
 import com.android.builder.SdkParser
 import com.android.utils.ILogger
 import org.gradle.api.Project
 import org.gradle.api.logging.LogLevel
 import org.gradle.api.plugins.JavaBasePlugin
-import com.android.builder.ProductFlavorHolder
-import com.android.builder.BuildTypeHolder
-import com.android.build.gradle.internal.ApplicationVariant
+import org.gradle.api.tasks.SourceSet
 
 /**
  * Base class for all Android plugins
@@ -38,13 +39,29 @@ abstract class AndroidBasePlugin {
     private DefaultSdkParser androidSdkParser
     private AndroidLogger androidLogger
 
+    private ProductFlavorData defaultConfigData
+    protected SourceSet mainSourceSet
+    protected SourceSet testSourceSet
+
     abstract String getTarget()
-    abstract ProductFlavorHolder getMainFlavor()
-    abstract BuildTypeHolder getDebugType()
 
     protected void apply(Project project) {
         this.project = project
         project.apply plugin: JavaBasePlugin
+
+        mainSourceSet = project.sourceSets.add("main")
+        testSourceSet = project.sourceSets.add("test")
+
+        project.tasks.assemble.description = "Assembles all variants of all applications, and secondary packages."
+    }
+
+    protected setDefaultConfig(ProductFlavor defaultConfig) {
+        defaultConfigData = new ProductFlavorData(defaultConfig, mainSourceSet,
+                testSourceSet, project)
+    }
+
+    ProductFlavorData getDefaultConfigData() {
+        return defaultConfigData
     }
 
     SdkParser getSdkParser() {
index 067a9e4..426724b 100644 (file)
@@ -25,6 +25,8 @@ import com.android.builder.DexOptions
 class AndroidExtension extends BaseAndroidExtension {
     final NamedDomainObjectContainer<ProductFlavor> productFlavors
 
+    String testBuildType = "debug"
+
     // FIXME?
     final AaptOptions aaptOptions = new AaptOptions()
     final DexOptions dexOptions = new DexOptions()
index e4b3d4f..f7aff52 100644 (file)
@@ -16,8 +16,6 @@
 package com.android.build.gradle
 
 import com.android.builder.BuildType
-import com.android.builder.BuildTypeHolder
-import com.android.builder.ProductFlavorHolder
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 
@@ -25,7 +23,6 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
 
     AndroidLibraryExtension extension
 
-
     @Override
     void apply(Project project) {
         super.apply(project)
@@ -33,6 +30,7 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
         def buildTypes = project.container(BuildType)
 
         extension = project.extensions.create('android', AndroidLibraryExtension, buildTypes)
+        setDefaultConfig(extension.defaultConfig)
 
         buildTypes.whenObjectAdded { BuildType buildType ->
             addBuildType(buildType)
@@ -55,14 +53,4 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
     String getTarget() {
         return extension.target
     }
-
-    @Override
-    ProductFlavorHolder getMainFlavor() {
-        return null  //To change body of implemented methods use File | Settings | File Templates.
-    }
-
-    @Override
-    BuildTypeHolder getDebugType() {
-        return null  //To change body of implemented methods use File | Settings | File Templates.
-    }
 }
index 8aa86d0..e33019b 100644 (file)
@@ -51,6 +51,6 @@ class AndroidLogger implements ILogger {
 
     @Override
     void verbose(String s, Object... objects) {
-        logger.log(LogLevel.DEBUG, s, objects)
+        logger.log(LogLevel.INFO, s, objects)
     }
 }
index 903771c..3898578 100644 (file)
 package com.android.build.gradle
 
 import com.android.build.gradle.internal.AndroidManifest
+import com.android.build.gradle.internal.AndroidSourceSet
 import com.android.build.gradle.internal.ApplicationVariant
-import com.android.build.gradle.internal.BuildTypeDimension
-import com.android.build.gradle.internal.ProductFlavorDimension
+import com.android.build.gradle.internal.BuildTypeData
+import com.android.build.gradle.internal.ProductFlavorData
 import com.android.build.gradle.internal.ProductionAppVariant
 import com.android.build.gradle.internal.TestAppVariant
 import com.android.builder.BuildType
-import com.android.builder.BuildTypeHolder
 import com.android.builder.ProductFlavor
-import com.android.builder.ProductFlavorHolder
+import com.android.builder.VariantConfiguration
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.tasks.SourceSet
+import org.gradle.api.Task
 import org.gradle.api.tasks.compile.Compile
 import org.gradle.internal.reflect.Instantiator
 
 class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
-    private final Set<ProductionAppVariant> variants = []
-    private final Map<String, BuildTypeDimension> buildTypes = [:]
-    private final Map<String, ProductFlavorDimension> productFlavors = [:]
-    private SourceSet main
-    private SourceSet test
+    private final Map<String, BuildTypeData> buildTypes = [:]
+    private final Map<String, ProductFlavorData> productFlavors = [:]
+
     private AndroidExtension extension
     private AndroidManifest mainManifest
 
@@ -45,8 +42,6 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
     void apply(Project project) {
         super.apply(project)
 
-        project.apply plugin: JavaBasePlugin
-
         def buildTypes = project.container(BuildType)
         // TODO - do the decoration by default
         def productFlavors = project.container(ProductFlavor) { name ->
@@ -54,12 +49,10 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
         }
 
         extension = project.extensions.create('android', AndroidExtension, buildTypes, productFlavors)
+        setDefaultConfig(extension.defaultConfig)
 
         findSdk(project)
 
-        main = project.sourceSets.add('main')
-        test = project.sourceSets.add('test')
-
         buildTypes.whenObjectAdded { BuildType buildType ->
             addBuildType(buildType)
         }
@@ -70,140 +63,175 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
         buildTypes.create(BuildType.DEBUG)
         buildTypes.create(BuildType.RELEASE)
 
-        productFlavors.whenObjectAdded { ProductFlavor flavor ->
-            addProductFlavor(flavor)
+        productFlavors.whenObjectAdded { ProductFlavor productFlavor ->
+            addProductFlavor(productFlavor)
         }
+
         productFlavors.whenObjectRemoved {
-            throw new UnsupportedOperationException("Removing product flavors is not implemented yet.")
+            throw new UnsupportedOperationException(
+                    "Removing product flavors is not implemented yet.")
         }
 
-        productFlavors.create('main')
 
-        project.tasks.assemble.dependsOn { variants.collect { "assemble${it.name}" } }
-    }
-
-    private AndroidManifest getMainManifest() {
-        if (mainManifest == null) {
-            mainManifest = new AndroidManifest()
-            mainManifest.load(project.file("src/main/AndroidManifest.xml"))
+        project.afterEvaluate {
+            createAndroidTasks()
         }
-        return mainManifest
     }
 
     private void addBuildType(BuildType buildType) {
         if (buildType.name.startsWith("test")) {
             throw new RuntimeException("BuildType names cannot start with 'test'")
         }
+        if (productFlavors.containsKey(buildType.name)) {
+            throw new RuntimeException("BuildType names cannot collide with ProductFlavor names")
+        }
 
         def sourceSet = project.sourceSets.add(buildType.name)
 
-        def buildTypeDimension = new BuildTypeDimension(buildType, sourceSet, project.projectDir.absolutePath)
-        buildTypes[buildType.name] = buildTypeDimension
-
-        def assembleBuildType = project.tasks.add(buildTypeDimension.assembleTaskName)
-        assembleBuildType.dependsOn {
-            buildTypeDimension.variants.collect { "assemble$it.name" }
-        }
-        assembleBuildType.description = "Assembles all ${buildType.name} applications"
-        assembleBuildType.group = "Build"
+        BuildTypeData buildTypeData = new BuildTypeData(buildType, sourceSet, project)
+        project.tasks.assemble.dependsOn buildTypeData.assembleTask
 
-        productFlavors.values().each { flavor ->
-            addVariant(buildTypeDimension, flavor)
-        }
+        buildTypes[buildType.name] = buildTypeData
     }
 
     private void addProductFlavor(ProductFlavor productFlavor) {
         if (productFlavor.name.startsWith("test")) {
             throw new RuntimeException("ProductFlavor names cannot start with 'test'")
         }
+        if (productFlavors.containsKey(productFlavor.name)) {
+            throw new RuntimeException("ProductFlavor names cannot collide with BuildType names")
+        }
+
+        def mainSourceSet = project.sourceSets.add(productFlavor.name)
+        def testSourceSet = project.sourceSets.add("test${productFlavor.name.capitalize()}")
+
+        ProductFlavorData productFlavorData = new ProductFlavorData(
+                productFlavor, mainSourceSet, testSourceSet, project)
+        project.tasks.assemble.dependsOn productFlavorData.assembleTask
 
-        def mainSourceSet
-        def testSourceSet
-        if (productFlavor.name == 'main') {
-            mainSourceSet = main
-            testSourceSet = test
+        productFlavors[productFlavor.name] = productFlavorData
+    }
+
+    private void createAndroidTasks() {
+        if (productFlavors.isEmpty()) {
+            createTasksForDefaultBuild()
         } else {
-            mainSourceSet = project.sourceSets.add(productFlavor.name)
-            testSourceSet = project.sourceSets.add("test${productFlavor.name.capitalize()}")
+            productFlavors.values().each { ProductFlavorData productFlavorData ->
+                createTasksForFlavoredBuild(productFlavorData)
+            }
         }
+    }
 
-        def productFlavorDimension = new ProductFlavorDimension(productFlavor, mainSourceSet, testSourceSet, project.projectDir.absolutePath)
-        productFlavors[productFlavor.name] = productFlavorDimension
+    private createTasksForDefaultBuild() {
 
-        def assembleFlavour = project.tasks.add(productFlavorDimension.assembleTaskName)
-        assembleFlavour.dependsOn {
-            productFlavorDimension.variants.collect { "assemble${it.name}" }
+        BuildTypeData testData = buildTypes[extension.testBuildType]
+        if (testData == null) {
+            throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
         }
-        assembleFlavour.description = "Assembles all ${productFlavor.name} applications"
-        assembleFlavour.group = "Build"
 
-        buildTypes.values().each { buildType ->
-            addVariant(buildType, productFlavorDimension)
+        VariantConfiguration testedVariantConfig = null
+        ProductionAppVariant testedVariant = null
+
+        ProductFlavorData defaultConfigData = getDefaultConfigData();
+
+        for (BuildTypeData buildTypeData : buildTypes.values()) {
+
+            def variantConfig = new VariantConfiguration(
+                    defaultConfigData.productFlavor, defaultConfigData.androidSourceSet,
+                    buildTypeData.buildType, buildTypeData.androidSourceSet,
+                    VariantConfiguration.Type.DEFAULT)
+
+            ProductionAppVariant productionAppVariant = addVariant(variantConfig,
+                    buildTypeData.assembleTask)
+
+            if (buildTypeData == testData) {
+                testedVariantConfig = variantConfig
+                testedVariant = productionAppVariant
+            }
         }
 
-        assert productFlavorDimension.debugVariant != null
+        assert testedVariantConfig != null && testedVariant != null
 
-        def testAppVariant = new TestAppVariant(productFlavorDimension)
-        def flavorName = productFlavor.name.capitalize()
+        def variantTestConfig = new VariantConfiguration(
+                defaultConfigData.productFlavor, defaultConfigData.androidSourceSet,
+                testData.buildType, null,
+                VariantConfiguration.Type.TEST)
 
-        // Add a task to generate the test app manifest
-        def generateManifestTask = project.tasks.add("generate${flavorName}TestManifest", GenerateTestManifest)
-        generateManifestTask.conventionMapping.outputFile = { project.file("$project.buildDir/manifests/test/$flavorName/AndroidManifest.xml") }
-        generateManifestTask.conventionMapping.packageName = { getMainManifest().packageName + '.test' }
+        addTestVariant(variantTestConfig, testedVariantConfig, testedVariant)
+    }
 
-        // Add a task to crunch resource files
-        def crunchTask = project.tasks.add("crunch${flavorName}TestResources", CrunchResources)
-        crunchTask.plugin = this
-        crunchTask.variant = testAppVariant
-        crunchTask.conventionMapping.outputDir = { project.file("$project.buildDir/resources/test/$flavorName") }
+    private createTasksForFlavoredBuild(ProductFlavorData productFlavorData) {
 
-        // Add a task to generate resource package
-        def processResources = project.tasks.add("process${flavorName}TestResources", ProcessResources)
-        processResources.dependsOn generateManifestTask, crunchTask
-        processResources.plugin = this
-        processResources.variant = testAppVariant
-        processResources.conventionMapping.manifestFile = { generateManifestTask.outputFile }
-        processResources.conventionMapping.crunchDir = { crunchTask.outputDir }
-        // TODO: unify with generateManifestTask somehow?
-        processResources.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/test/$flavorName") }
-        processResources.conventionMapping.packageFile = { project.file("$project.buildDir/libs/${project.archivesBaseName}-test${flavorName}.ap_") }
-        processResources.aaptOptions = extension.aaptOptions
+        BuildTypeData testData = buildTypes[extension.testBuildType]
+        if (testData == null) {
+            throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
+        }
 
-        // Add a task to compile the test application
-        def testCompile = project.tasks.add("compile${flavorName}Test", Compile)
-        testCompile.dependsOn processResources
-        testCompile.source test.java, productFlavorDimension.testSource.java
-        testCompile.classpath = test.compileClasspath + productFlavorDimension.debugVariant.runtimeClasspath
-        testCompile.conventionMapping.destinationDir = { project.file("$project.buildDir/classes/test/$flavorName") }
-        testCompile.doFirst {
-            testCompile.options.bootClasspath = getRuntimeJars(testAppVariant)
+        VariantConfiguration testedVariantConfig = null
+        ProductionAppVariant testedVariant = null
+
+        for (BuildTypeData buildTypeData : buildTypes.values()) {
+
+            def variantConfig = new VariantConfiguration(
+                    extension.defaultConfig, getDefaultConfigData().androidSourceSet,
+                    buildTypeData.buildType, buildTypeData.androidSourceSet,
+                    VariantConfiguration.Type.DEFAULT)
+
+            variantConfig.addProductFlavor(productFlavorData.productFlavor,
+                    productFlavorData.androidSourceSet)
+
+            ProductionAppVariant productionAppVariant = addVariant(variantConfig, null)
+
+            buildTypeData.assembleTask.dependsOn productionAppVariant.assembleTask
+            productFlavorData.assembleTask.dependsOn productionAppVariant.assembleTask
+
+            if (buildTypeData == testData) {
+                testedVariantConfig = variantConfig
+                testedVariant = productionAppVariant
+            }
         }
 
-        testAppVariant.runtimeClasspath = testCompile.outputs.files + testCompile.classpath
-        testAppVariant.resourcePackage = project.files({processResources.packageFile}) { builtBy processResources }
-        testAppVariant.compileTask = testCompile
-        addPackageTasks(testAppVariant)
+        assert testedVariantConfig != null && testedVariant != null
+
+        def variantTestConfig = new VariantConfiguration(
+                extension.defaultConfig, getDefaultConfigData().androidTestSourceSet,
+                testData.buildType, null,
+                VariantConfiguration.Type.TEST)
+        variantTestConfig.addProductFlavor(productFlavorData.productFlavor,
+                productFlavorData.androidTestSourceSet)
 
-        project.tasks.check.dependsOn "assemble${testAppVariant.name}"
+        addTestVariant(variantTestConfig, testedVariantConfig, testedVariant)
     }
 
-    private void addVariant(BuildTypeDimension buildType, ProductFlavorDimension productFlavor) {
-        def variant = new ProductionAppVariant(buildType, productFlavor)
-        variants << variant
-        buildType.variants << variant
-        productFlavor.variants << variant
-        if (buildType.name == BuildType.DEBUG) {
-            productFlavor.debugVariant = variant
+
+
+    private AndroidManifest getMainManifest() {
+        if (mainManifest == null) {
+            mainManifest = new AndroidManifest()
+            mainManifest.load(project.file("src/main/AndroidManifest.xml"))
         }
+        return mainManifest
+    }
+
+    /**
+     * Creates build tasks for a given variant.
+     * @param variantConfig
+     * @param assembleTask an optional assembleTask to be used. If null, a new one is created in the
+     *                     returned ProductAppVariant instance.
+     * @return
+     */
+    private ProductionAppVariant addVariant(VariantConfiguration variantConfig, Task assembleTask) {
+
+        def variant = new ProductionAppVariant(variantConfig)
 
         // Add a task to merge the manifest(s)
-        def mergeManifestTask = project.tasks.add("merge${variant.name}Manifest", MergeManifest)
+        def mergeManifestTask = project.tasks.add("process${variant.name}Manifest", MergeManifest)
         mergeManifestTask.plugin = this
         mergeManifestTask.variant = variant
         mergeManifestTask.conventionMapping.mergedManifest = { project.file("$project.buildDir/manifests/$variant.dirName/AndroidManifest.xml") }
 
         // Add a task to crunch resource files
-        def crunchTask = project.tasks.add("crunch${variant.name}Resources", CrunchResources)
+        def crunchTask = project.tasks.add("crunchAndroid${variant.name}Res", CrunchResources)
         crunchTask.plugin = this
         crunchTask.variant = variant
         crunchTask.conventionMapping.outputDir = { project.file("$project.buildDir/resources/$variant.dirName/res") }
@@ -215,7 +243,7 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
         generateBuildConfigTask.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/${variant.dirName}") }
 
         // Add a task to generate resource source files
-        def processResources = project.tasks.add("process${variant.name}Resources", ProcessResources)
+        def processResources = project.tasks.add("processAndroid${variant.name}Res", ProcessResources)
         processResources.dependsOn mergeManifestTask, crunchTask
         processResources.plugin = this
         processResources.variant = variant
@@ -224,7 +252,7 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
         // TODO: unify with generateBuilderConfig somehow?
         processResources.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/$variant.dirName") }
         processResources.conventionMapping.packageFile = { project.file("$project.buildDir/libs/${project.archivesBaseName}-${variant.baseName}.ap_") }
-        if (buildType.buildType.runProguard) {
+        if (variantConfig.buildType.runProguard) {
             processResources.conventionMapping.proguardFile = { project.file("$project.buildDir/proguard/${variant.dirName}/rules.txt") }
         }
         processResources.aaptOptions = extension.aaptOptions
@@ -233,22 +261,106 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
         def compileTaskName = "compile${variant.name}"
         def compileTask = project.tasks.add(compileTaskName, Compile)
         compileTask.dependsOn processResources, generateBuildConfigTask
-        compileTask.source main.java, buildType.mainSource.java, productFlavor.mainSource.java, { processResources.sourceOutputDir }
-        compileTask.classpath = main.compileClasspath
+        if (variantConfig.hasFlavors()) {
+            compileTask.source(
+                    ((AndroidSourceSet)variantConfig.defaultSourceSet).sourceSet.java,
+                    ((AndroidSourceSet)variantConfig.buildTypeSourceSet).sourceSet.java,
+                    ((AndroidSourceSet)variantConfig.firstFlavorSourceSet).sourceSet.java,
+                    { processResources.sourceOutputDir })
+        } else {
+            compileTask.source(
+                    ((AndroidSourceSet)variantConfig.defaultSourceSet).sourceSet.java,
+                    ((AndroidSourceSet)variantConfig.buildTypeSourceSet).sourceSet.java,
+                    { processResources.sourceOutputDir })
+        }
+        // TODO: support classpath per flavor
+        compileTask.classpath = ((AndroidSourceSet)variantConfig.defaultSourceSet).sourceSet.compileClasspath
         compileTask.conventionMapping.destinationDir = { project.file("$project.buildDir/classes/$variant.dirName") }
         compileTask.doFirst {
             compileTask.options.bootClasspath = getRuntimeJars(variant)
         }
 
         // Wire up the outputs
-        variant.runtimeClasspath = project.files(compileTask.outputs, main.compileClasspath)
+        // TODO: remove the classpath once the jar deps are set in the Variantconfig, so that we can pre-dex
+        variant.runtimeClasspath = compileTask.outputs.files + compileTask.classpath
         variant.resourcePackage = project.files({processResources.packageFile}) { builtBy processResources }
         variant.compileTask = compileTask
 
-        addPackageTasks(variant)
+        Task returnTask = addPackageTasks(variant, assembleTask)
+        if (returnTask != null) {
+            variant.assembleTask = returnTask
+        }
+
+        return variant;
     }
 
-    private void addPackageTasks(ApplicationVariant variant) {
+    private void addTestVariant(VariantConfiguration variantConfig,
+                                VariantConfiguration testedVariantConfig,
+                                ProductionAppVariant testedVariant) {
+
+        def variant = new TestAppVariant(variantConfig, testedVariantConfig)
+
+        // Add a task to merge the manifest(s)
+        def mergeManifestTask = project.tasks.add("process${variant.name}Manifest", MergeManifest)
+        mergeManifestTask.plugin = this
+        mergeManifestTask.variant = variant
+        mergeManifestTask.conventionMapping.mergedManifest = { project.file("$project.buildDir/manifests/$variant.dirName/AndroidManifest.xml") }
+
+        // Add a task to crunch resource files
+        def crunchTask = project.tasks.add("crunchAndroid${variant.name}Res", CrunchResources)
+        crunchTask.plugin = this
+        crunchTask.variant = variant
+        crunchTask.conventionMapping.outputDir = { project.file("$project.buildDir/resources/$variant.dirName/res") }
+
+        // Add a task to create the BuildConfig class
+        def generateBuildConfigTask = project.tasks.add("generate${variant.name}BuildConfig", GenerateBuildConfigTask)
+        generateBuildConfigTask.plugin = this
+        generateBuildConfigTask.variant = variant
+        generateBuildConfigTask.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/${variant.dirName}") }
+
+        // Add a task to generate resource source files
+        def processResources = project.tasks.add("processAndroid${variant.name}Res", ProcessResources)
+        processResources.dependsOn mergeManifestTask, crunchTask
+        processResources.plugin = this
+        processResources.variant = variant
+        processResources.conventionMapping.manifestFile = { mergeManifestTask.mergedManifest }
+        processResources.conventionMapping.crunchDir = { crunchTask.outputDir }
+        // TODO: unify with generateBuilderConfig somehow?
+        processResources.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/$variant.dirName") }
+        processResources.conventionMapping.packageFile = { project.file("$project.buildDir/libs/${project.archivesBaseName}-${variant.baseName}.ap_") }
+        processResources.aaptOptions = extension.aaptOptions
+
+        // Add a task to compile the test application
+        def testCompile = project.tasks.add("compile${variant.name}", Compile)
+        testCompile.dependsOn processResources, generateBuildConfigTask
+        if (variantConfig.hasFlavors()) {
+            testCompile.source(
+                    ((AndroidSourceSet)variantConfig.defaultSourceSet).sourceSet.java,
+                    ((AndroidSourceSet)variantConfig.firstFlavorSourceSet).sourceSet.java,
+                    { processResources.sourceOutputDir })
+        } else {
+            testCompile.source(
+                    ((AndroidSourceSet)variantConfig.defaultSourceSet).sourceSet.java,
+                    { processResources.sourceOutputDir })
+        }
+
+        testCompile.classpath = ((AndroidSourceSet)variantConfig.defaultSourceSet).sourceSet.compileClasspath + testedVariant.runtimeClasspath
+        testCompile.conventionMapping.destinationDir = { project.file("$project.buildDir/classes/$variant.dirName") }
+        testCompile.doFirst {
+            testCompile.options.bootClasspath = getRuntimeJars(variant)
+        }
+
+        // TODO: remove the classpath once the jar deps are set in the Variantconfig, so that we can pre-dex
+        variant.runtimeClasspath = testCompile.outputs.files + testCompile.classpath
+        variant.resourcePackage = project.files({processResources.packageFile}) { builtBy processResources }
+        variant.compileTask = testCompile
+
+        Task assembleTask = addPackageTasks(variant, null)
+
+        project.tasks.check.dependsOn assembleTask
+    }
+
+    private Task addPackageTasks(ApplicationVariant variant, Task assembleTask) {
         // Add a dex task
         def dexTaskName = "dex${variant.name}"
         def dexTask = project.tasks.add(dexTaskName, Dex)
@@ -277,42 +389,40 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
 
         def appTask = packageApp
 
-        if (signedApk && variant.zipAlign) {
-            // Add a task to zip align application package
-            def alignApp = project.tasks.add("zipalign${variant.name}", ZipAlign)
-            alignApp.dependsOn packageApp
-            alignApp.conventionMapping.inputFile = { packageApp.outputFile }
-            alignApp.conventionMapping.outputFile = { project.file("$project.buildDir/apk/${project.archivesBaseName}-${variant.baseName}.apk") }
-            alignApp.sdkDir = sdkDir
-
-            appTask = alignApp
+        if (signedApk) {
+            if (variant.zipAlign) {
+                // Add a task to zip align application package
+                def alignApp = project.tasks.add("zipalign${variant.name}", ZipAlign)
+                alignApp.dependsOn packageApp
+                alignApp.conventionMapping.inputFile = { packageApp.outputFile }
+                alignApp.conventionMapping.outputFile = { project.file("$project.buildDir/apk/${project.archivesBaseName}-${variant.baseName}.apk") }
+                alignApp.sdkDir = sdkDir
+
+                appTask = alignApp
+            }
+
+            // Add a task to install the application package
+            def installApp = project.tasks.add("install${variant.name}", InstallApplication)
+            installApp.dependsOn appTask
+            installApp.conventionMapping.packageFile = { appTask.outputFile }
+            installApp.sdkDir = sdkDir
         }
 
         // Add an assemble task
-        def assembleTask = project.tasks.add("assemble${variant.name}")
+        Task returnTask = null
+        if (assembleTask == null) {
+            assembleTask = project.tasks.add("assemble${variant.name}")
+            assembleTask.description = variant.description
+            assembleTask.group = "Build"
+            returnTask = assembleTask
+        }
         assembleTask.dependsOn appTask
-        assembleTask.description = "Assembles the ${variant.description} application"
-        assembleTask.group = "Build"
-
-        // Add a task to install the application package
-        def installApp = project.tasks.add("install${variant.name}", InstallApplication)
-        installApp.dependsOn appTask
-        installApp.conventionMapping.packageFile = { appTask.outputFile }
-        installApp.sdkDir = sdkDir
+
+        return returnTask
     }
 
     @Override
     String getTarget() {
         return extension.target;
     }
-
-    @Override
-    ProductFlavorHolder getMainFlavor() {
-        return productFlavors.get('main')
-    }
-
-    @Override
-    BuildTypeHolder getDebugType() {
-        return buildTypes.get(BuildType.DEBUG)
-    }
 }
index cca9eeb..c3a5587 100644 (file)
@@ -18,6 +18,7 @@ package com.android.build.gradle
 import com.android.builder.BuildType
 import org.gradle.api.Action
 import org.gradle.api.NamedDomainObjectContainer
+import com.android.builder.ProductFlavor
 
 /**
  * Base android extension for all android plugins.
@@ -25,6 +26,8 @@ import org.gradle.api.NamedDomainObjectContainer
 class BaseAndroidExtension {
 
     String target
+    final ProductFlavor defaultConfig = new ProductFlavor("main");
+
     final NamedDomainObjectContainer<BuildType> buildTypes
 
     BaseAndroidExtension(NamedDomainObjectContainer<BuildType> buildTypes) {
@@ -34,4 +37,8 @@ class BaseAndroidExtension {
     void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
         action.execute(buildTypes)
     }
+
+    void defaultConfig(Action<ProductFlavor> action) {
+        action.execute(defaultConfig)
+    }
 }
  */
 package com.android.build.gradle.internal
 
+import org.gradle.api.Project
 import org.gradle.api.tasks.SourceSet
-import com.android.builder.PathProvider
 
 /**
- * Base Dimension providing an implementation of PathProvider for a given SourceSet.
+ * Implementation of the AndroidBuilder SourceSet on top of a gradle SourceSet
  */
-class BaseDimension implements PathProvider {
+public class AndroidSourceSet implements com.android.builder.SourceSet {
 
-    final SourceSet mainSource
-    private final String baseDir
+    final SourceSet sourceSet
     private final String name
+    private final Project project
 
-
-    BaseDimension(SourceSet mainSource, String baseDir, String name) {
-        this.mainSource = mainSource
-        this.baseDir = baseDir
+    public AndroidSourceSet(SourceSet sourceSet, String name, Project project) {
+        this.sourceSet = sourceSet
         this.name = name
-    }
-
-    @Override
-    Set<File> getJavaSource() {
-        return mainSource.allJava.srcDirs
+        this.project = project
     }
 
     @Override
     Set<File> getJavaResources() {
-        return mainSource.resources.srcDirs
+        return sourceSet.resources.srcDirs
     }
 
     @Override
     File getAndroidResources() {
         // FIXME: make this configurable by the SourceSet
-        return new File("$baseDir/src/$name/res")
+        return project.file("src/$name/res")
     }
 
     @Override
     File getAndroidAssets() {
         // FIXME: make this configurable by the SourceSet
-        return new File("$baseDir/src/$name/assets")
+        return project.file("src/$name/assets")
     }
 
     @Override
     File getAndroidManifest() {
         // FIXME: make this configurable by the SourceSet
-        return new File("$baseDir/src/$name/AndroidManifest.xml")
+        return project.file("src/$name/AndroidManifest.xml")
     }
 
     @Override
     File getAidlSource() {
         // FIXME: make this configurable by the SourceSet
-        return new File("$baseDir/src/$name/aidl")
+        return project.file("src/$name/aidl")
     }
 
     @Override
     File getRenderscriptSource() {
         // FIXME: make this configurable by the SourceSet
-        return new File("$baseDir/src/$name/rs")
+        return project.file("src/$name/rs")
     }
 
     @Override
     File getNativeSource() {
         // FIXME: make this configurable by the SourceSet
-        return new File("$baseDir/src/$name/jni")
+        return project.file("src/$name/jni")
     }
 }
 package com.android.build.gradle.internal
 
 import com.android.builder.BuildType
+import org.gradle.api.Project
+import org.gradle.api.Task
 import org.gradle.api.tasks.SourceSet
-import com.android.builder.BuildTypeHolder
 
-class BuildTypeDimension extends BaseDimension implements BuildTypeHolder {
+class BuildTypeData {
     final BuildType buildType
-    final Set<ProductionAppVariant> variants = []
 
-    BuildTypeDimension(BuildType buildType, SourceSet mainSource, String baseDir) {
-        super(mainSource, baseDir, buildType.name)
-        this.buildType = buildType
-    }
+    final SourceSet mainSource
+    final AndroidSourceSet androidSourceSet
 
-    String getName() {
-        return buildType.name
-    }
+    final Task assembleTask
+
+    BuildTypeData(BuildType buildType, SourceSet mainSource, Project project) {
+        this.buildType = buildType
+        this.mainSource = mainSource
+        androidSourceSet = new AndroidSourceSet(mainSource, buildType.name, project)
 
-    String getAssembleTaskName() {
-        return "assemble${buildType.name.capitalize()}"
+        assembleTask = project.tasks.add("assemble${buildType.name.capitalize()}")
+        assembleTask.description = "Assembles all ${buildType.name.capitalize()} builds"
+        assembleTask.group = "Build"
     }
 }
 package com.android.build.gradle.internal
 
 import com.android.builder.ProductFlavor
+import org.gradle.api.Project
+import org.gradle.api.Task
 import org.gradle.api.tasks.SourceSet
-import com.android.builder.ProductFlavorHolder
 
-class ProductFlavorDimension extends BaseDimension implements ProductFlavorHolder {
+class ProductFlavorData {
     final ProductFlavor productFlavor
-    final Set<ProductionAppVariant> variants = []
+
+    final SourceSet mainSource
+    final AndroidSourceSet androidSourceSet
+
     final SourceSet testSource
-    ProductionAppVariant debugVariant
+    final AndroidSourceSet androidTestSourceSet
 
-    ProductFlavorDimension(ProductFlavor productFlavor, SourceSet mainSource, SourceSet testSource,
-                           String baseDir) {
-        super(mainSource, baseDir, productFlavor.name)
+    final Task assembleTask
+
+    ProductFlavorData(ProductFlavor productFlavor, SourceSet mainSource, SourceSet testSource,
+                           Project project) {
         this.productFlavor = productFlavor
-        this.testSource = testSource
-    }
 
-    String getName() {
-        return productFlavor.name
-    }
+        this.mainSource = mainSource
+        androidSourceSet = new AndroidSourceSet(mainSource, productFlavor.name, project)
+
+        this.testSource = testSource
+        androidTestSourceSet = new AndroidSourceSet(testSource, productFlavor.name, project)
 
-    String getAssembleTaskName() {
-        return "assemble${productFlavor.name.capitalize()}"
+        if (productFlavor.name != "main") {
+            assembleTask = project.tasks.add("assemble${productFlavor.name.capitalize()}")
+            assembleTask.description = "Assembles all builds for flavor ${productFlavor.name.capitalize()}"
+            assembleTask.group = "Build"
+        }
     }
 }
index 043a94a..3b4d050 100644 (file)
@@ -17,46 +17,61 @@ package com.android.build.gradle.internal
 
 import com.android.build.gradle.AndroidBasePlugin
 import com.android.builder.AndroidBuilder
-import com.android.builder.BuildTypeHolder
-import com.android.builder.ProductFlavorHolder
+import com.android.builder.VariantConfiguration
+import org.gradle.api.Task
 import org.gradle.api.file.FileCollection
 import org.gradle.api.tasks.compile.Compile
 
 class ProductionAppVariant implements ApplicationVariant {
     final String name
-    final BuildTypeHolder buildTypeHolder
-    final ProductFlavorHolder productFlavorHolder
+    final VariantConfiguration variant
     FileCollection runtimeClasspath
     FileCollection resourcePackage
     Compile compileTask
+    Task assembleTask
 
-    ProductionAppVariant(BuildTypeHolder buildTypeHolder, ProductFlavorHolder productFlavorHolder) {
-        this.name = "${productFlavorHolder.productFlavor.name.capitalize()}${buildTypeHolder.buildType.name.capitalize()}"
-        this.buildTypeHolder = buildTypeHolder
-        this.productFlavorHolder = productFlavorHolder
+    ProductionAppVariant(VariantConfiguration variant) {
+        this.variant = variant
+        if (variant.hasFlavors()) {
+            this.name = "${variant.firstFlavor.name.capitalize()}${variant.buildType.name.capitalize()}"
+        } else {
+            this.name = "${variant.buildType.name.capitalize()}"
+        }
     }
 
     String getDescription() {
-        return "$productFlavorHolder.productFlavor.name $buildTypeHolder.buildType.name"
+        if (variant.hasFlavors()) {
+            return "Assembles the ${variant.buildType.name.capitalize()} build for flavor ${variant.firstFlavor.name.capitalize()}"
+        } else {
+            return "Assembles the ${variant.buildType.name.capitalize()} build"
+        }
     }
 
     String getDirName() {
-        return "$productFlavorHolder.productFlavor.name/$buildTypeHolder.buildType.name"
+        if (variant.hasFlavors()) {
+            return "$variant.firstFlavor.name/$variant.buildType.name"
+        } else {
+            return "$variant.buildType.name"
+        }
     }
 
     String getBaseName() {
-        return "$productFlavorHolder.productFlavor.name-$buildTypeHolder.buildType.name"
+        if (variant.hasFlavors()) {
+            return "$variant.firstFlavor.name-$variant.buildType.name"
+        } else {
+            return "$variant.buildType.name"
+        }
     }
 
     @Override
     boolean getZipAlign() {
-        return buildTypeHolder.buildType.zipAlign
+        return variant.buildType.zipAlign
     }
 
     @Override
     boolean isSigned() {
-        return buildTypeHolder.buildType.debugSigned ||
-                productFlavorHolder.productFlavor.isSigningReady()
+        return variant.buildType.debugSigned ||
+                variant.mergedFlavor.isSigningReady()
     }
 
     @Override
@@ -67,12 +82,7 @@ class ProductionAppVariant implements ApplicationVariant {
                 androidBasePlugin.verbose)
 
         androidBuilder.setTarget(androidBasePlugin.target)
-
-        androidBuilder.setBuildVariant(androidBasePlugin.mainFlavor, buildTypeHolder)
-
-        if (productFlavorHolder != null) {
-            androidBuilder.addProductFlavor(productFlavorHolder)
-        }
+        androidBuilder.setBuildVariant(variant, null /*testedVariant*/)
 
         return androidBuilder
     }
index bb5ebfb..b452724 100644 (file)
@@ -17,33 +17,51 @@ package com.android.build.gradle.internal
 
 import com.android.build.gradle.AndroidBasePlugin
 import com.android.builder.AndroidBuilder
-import com.android.builder.ProductFlavorHolder
 import org.gradle.api.file.FileCollection
 import org.gradle.api.tasks.compile.Compile
+import com.android.builder.VariantConfiguration
 
 class TestAppVariant implements ApplicationVariant {
     final String name
-    final ProductFlavorHolder productFlavorHolder
+    final VariantConfiguration variant
+    final VariantConfiguration testedVariant
     FileCollection runtimeClasspath
     FileCollection resourcePackage
     Compile compileTask
 
-    TestAppVariant(ProductFlavorHolder productFlavorHolder) {
-        this.name = "${productFlavorHolder.productFlavor.name.capitalize()}Test"
-        this.productFlavorHolder = productFlavorHolder
+    TestAppVariant(VariantConfiguration variant, VariantConfiguration testedVariant) {
+        this.variant = variant
+        this.testedVariant = testedVariant
+        if (variant.hasFlavors()) {
+            this.name = "${variant.firstFlavor.name.capitalize()}Test"
+        } else {
+            this.name = "Test"
+        }
     }
 
     @Override
     String getDescription() {
-        return "$productFlavorHolder.productFlavor.name test"
+        if (variant.hasFlavors()) {
+            return "Assembles the Test build for the ${variant.firstFlavor.name.capitalize()}${variant.buildType.name.capitalize()} build"
+        } else {
+            return "Assembles the Test for the ${variant.buildType.name.capitalize()} build"
+        }
     }
 
     String getDirName() {
-        return "${productFlavorHolder.productFlavor.name}/test"
+        if (variant.hasFlavors()) {
+            return "$variant.firstFlavor.name/test"
+        } else {
+            return "test"
+        }
     }
 
     String getBaseName() {
-        return "$productFlavorHolder.productFlavor.name-test"
+        if (variant.hasFlavors()) {
+            return "$variant.firstFlavor.name-test"
+        } else {
+            return "test"
+        }
     }
 
     @Override
@@ -64,14 +82,7 @@ class TestAppVariant implements ApplicationVariant {
                 androidBasePlugin.verbose)
 
         androidBuilder.setTarget(androidBasePlugin.target)
-
-        androidBuilder.setBuildVariant(androidBasePlugin.mainFlavor,
-                androidBasePlugin.debugType)
-
-        // FIXME: I'm not sure this is what is needed for test apps
-        if (productFlavorHolder != null) {
-            androidBuilder.addProductFlavor(productFlavorHolder)
-        }
+        androidBuilder.setBuildVariant(variant, testedVariant)
 
         return androidBuilder
     }
index eade735..7a56c1b 100644 (file)
@@ -24,13 +24,16 @@ dependencies {
 android {
     target = "android-15"
 
-       productFlavors {
-               main {
-                       signingStoreLocation = "debug.keystore"
-                       signingStorePassword = "android"
-                       signingKeyAlias = "androiddebugkey"
-                       signingKeyPassword = "android"
-               }
+       defaultConfig {
+               signingStoreLocation = "debug.keystore"
+               signingStorePassword = "android"
+               signingKeyAlias = "androiddebugkey"
+               signingKeyPassword = "android"
+       }
 
+       buildTypes {
+               blah {
+                       packageNameSuffix = ".blah"
+               }
        }
 }
\ No newline at end of file
diff --git a/testapps/flavored/build.gradle b/testapps/flavored/build.gradle
new file mode 100644 (file)
index 0000000..9e670c5
--- /dev/null
@@ -0,0 +1,51 @@
+//
+// A basic Android application that follows all the conventions
+//
+buildscript {
+    repositories {
+        maven { url '../../repo' }
+    }
+    dependencies {
+        classpath 'com.android.build:gradle-android:0.1-SNAPSHOT'
+    }
+}
+apply plugin: 'android'
+
+version='1.0'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile 'com.google.guava:guava:11.0.2'
+}
+
+android {
+    target = "android-15"
+
+       defaultConfig {
+               signingStoreLocation = "debug.keystore"
+               signingStorePassword = "android"
+               signingKeyAlias = "androiddebugkey"
+               signingKeyPassword = "android"
+       }
+
+       productFlavors {
+           foo {
+                       packageName = "org.gradle.sample.foo2"
+                       signingStoreLocation = "debug.keystore2"
+                       signingStorePassword = "android2"
+               }
+           bloo {
+                       packageName = "org.gradle.sample.bloo"
+               }
+       }
+       
+       buildTypes {
+               blah {
+                       packageNameSuffix = ".blah"
+                       debugSigned = true
+               }
+       }
+}
\ No newline at end of file
diff --git a/testapps/flavored/debug.keystore b/testapps/flavored/debug.keystore
new file mode 100644 (file)
index 0000000..389278e
Binary files /dev/null and b/testapps/flavored/debug.keystore differ
diff --git a/testapps/flavored/src/main/AndroidManifest.xml b/testapps/flavored/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..1f411e0
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="org.gradle.sample"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+        <activity android:name="MainActivity"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+                android:name="ShowPeopleActivity"
+                android:label="@string/title_activity_display_message" >
+                <meta-data
+                    android:name="android.support.PARENT_ACTIVITY"
+                    android:value="org.gradle.sample.MainActivity" />
+            </activity>
+    </application>
+</manifest>
diff --git a/testapps/flavored/src/main/java/org/gradle/sample/BuildType.java b/testapps/flavored/src/main/java/org/gradle/sample/BuildType.java
new file mode 100644 (file)
index 0000000..4364026
--- /dev/null
@@ -0,0 +1,5 @@
+package org.gradle.sample;
+
+public interface BuildType {
+    String getBuildType();
+}
diff --git a/testapps/flavored/src/main/java/org/gradle/sample/MainActivity.java b/testapps/flavored/src/main/java/org/gradle/sample/MainActivity.java
new file mode 100644 (file)
index 0000000..b6b2fb7
--- /dev/null
@@ -0,0 +1,22 @@
+package org.gradle.sample;
+
+import android.app.Activity;
+import android.view.View;
+import android.content.Intent;
+import android.os.Bundle;
+
+import android.annotation.TargetApi;
+
+public class MainActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @TargetApi(10)
+    public void sendMessage(View view) {
+        Intent intent = new Intent(this, ShowPeopleActivity.class);
+        startActivity(intent);
+    }
+}
diff --git a/testapps/flavored/src/main/java/org/gradle/sample/Person.java b/testapps/flavored/src/main/java/org/gradle/sample/Person.java
new file mode 100644 (file)
index 0000000..b6fcb27
--- /dev/null
@@ -0,0 +1,13 @@
+package org.gradle.sample;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/testapps/flavored/src/main/java/org/gradle/sample/ShowPeopleActivity.java b/testapps/flavored/src/main/java/org/gradle/sample/ShowPeopleActivity.java
new file mode 100644 (file)
index 0000000..24a739a
--- /dev/null
@@ -0,0 +1,31 @@
+package org.gradle.sample;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.content.Intent;
+import android.widget.TextView;
+import com.google.common.collect.Lists;
+
+import java.lang.String;
+import java.util.Arrays;
+
+public class ShowPeopleActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String message = "People:";
+
+        Iterable<Person> people = Lists.newArrayList(new Person("fred"));
+        for (Person person : people) {
+            message += "\n * ";
+            message += person.getName();
+        }
+
+        TextView textView = new TextView(this);
+        textView.setTextSize(20);
+        textView.setText(message);
+
+        setContentView(textView);
+    }
+}
diff --git a/testapps/flavored/src/main/res/drawable-hdpi/ic_launcher.png b/testapps/flavored/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..96a442e
Binary files /dev/null and b/testapps/flavored/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/testapps/flavored/src/main/res/drawable-ldpi/ic_launcher.png b/testapps/flavored/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..9923872
Binary files /dev/null and b/testapps/flavored/src/main/res/drawable-ldpi/ic_launcher.png differ
diff --git a/testapps/flavored/src/main/res/drawable-mdpi/ic_launcher.png b/testapps/flavored/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..359047d
Binary files /dev/null and b/testapps/flavored/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/testapps/flavored/src/main/res/drawable-xhdpi/ic_launcher.png b/testapps/flavored/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..71c6d76
Binary files /dev/null and b/testapps/flavored/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/testapps/flavored/src/main/res/layout/main.xml b/testapps/flavored/src/main/res/layout/main.xml
new file mode 100644 (file)
index 0000000..ccc59fb
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+    <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/button_send"
+            android:onClick="sendMessage" />
+</LinearLayout>
diff --git a/testapps/flavored/src/main/res/values/strings.xml b/testapps/flavored/src/main/res/values/strings.xml
new file mode 100644 (file)
index 0000000..8eda275
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Basic App</string>
+    <string name="button_send">Go</string>
+    <string name="title_activity_display_message">People</string>
+</resources>