Fix support for building and testing libraries.
Xavier Ducrohet [Thu, 30 Aug 2012 02:23:33 +0000 (19:23 -0700)]
Libraries weren't properly builds due to:
--non-constant-id was not used
--extra-packages was not used.

Also this creates a fake AndroidDependency for the tested
config so that it can be added to the dependency of the test
app.

Finally, this fixes some of the issues around classpath.
The Dex task receives separately the compiled code and the
library to planned for pre-dexing the libraries. The libraries
are handled through the AndroidDependency class provided
to the VariantConfig.
The compileTask setup is more precise in what is the
compiled *and packaged* classpath vs just a compile classpath
(this is different for test apps depending on if the test target
is a library or an app).

Change-Id: I0be9da2ce03bb48f40f4577e4323edf50062f2c0

38 files changed:
.gitignore
builder/prebuilts/manifmerger.jar
builder/src/main/java/com/android/builder/AndroidBuilder.java
builder/src/main/java/com/android/builder/AndroidDependency.java
builder/src/main/java/com/android/builder/SourceSet.java
builder/src/main/java/com/android/builder/VariantConfiguration.java
gradle/src/main/groovy/com/android/build/gradle/AndroidBasePlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidLibraryPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/Dex.groovy
gradle/src/main/groovy/com/android/build/gradle/ProcessManifest.groovy
gradle/src/main/groovy/com/android/build/gradle/ProcessResources.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/AndroidSourceSet.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/ApplicationVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/ProductionAppVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/TestAppVariant.groovy
testapps/applibtest/app/build.gradle [new file with mode: 0644]
testapps/applibtest/app/proguard-project.txt [new file with mode: 0644]
testapps/applibtest/app/src/main/AndroidManifest.xml [new file with mode: 0644]
testapps/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
testapps/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png [new file with mode: 0644]
testapps/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
testapps/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
testapps/applibtest/app/src/main/res/values/strings.xml [new file with mode: 0644]
testapps/applibtest/app/src/test/AndroidManifest.xml [new file with mode: 0644]
testapps/applibtest/app/src/test/java/com/android/tests/testprojecttest/lib/LibActivityTest.java [new file with mode: 0644]
testapps/applibtest/app/src/test/java/com/android/tests/testprojecttest/test/AllTests.java [new file with mode: 0644]
testapps/applibtest/build.gradle [new file with mode: 0644]
testapps/applibtest/lib/build.gradle [new file with mode: 0644]
testapps/applibtest/lib/proguard-project.txt [new file with mode: 0644]
testapps/applibtest/lib/src/main/AndroidManifest.xml [new file with mode: 0644]
testapps/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java [new file with mode: 0644]
testapps/applibtest/lib/src/main/res/layout/main.xml [new file with mode: 0644]
testapps/applibtest/lib/src/main/res/values/strings.xml [new file with mode: 0644]
testapps/applibtest/lib/src/test/java/com/android/tests/testprojecttest/lib/LibActivityTest.java [new file with mode: 0644]
testapps/applibtest/lib/src/test/java/com/android/tests/testprojecttest/test/AllTests.java [new file with mode: 0644]
testapps/applibtest/lib/src/test/res/values/strings.xml [new file with mode: 0644]
testapps/applibtest/settings.gradle [new file with mode: 0644]

index c6fbd72..247eb2a 100644 (file)
@@ -8,5 +8,6 @@ gradle/build
 testapps/*/build
 testapps/multiproject/*/build
 testapps/tictactoe/*/build
+testapps/applibtest/*/build
 /repo
 /out
index db1ea77..74c366c 100644 (file)
Binary files a/builder/prebuilts/manifmerger.jar and b/builder/prebuilts/manifmerger.jar differ
index cd94407..5489f84 100644 (file)
@@ -72,7 +72,7 @@ public class AndroidBuilder {
 
     private IAndroidTarget mTarget;
 
-    // config for the main app.
+    // config
     private VariantConfiguration mVariant;
 
     /**
@@ -194,42 +194,13 @@ public class AndroidBuilder {
     }
 
     /**
-     * Returns the dynamic list of resource folders.
-     * @return a list of input resource folders, guaranteed to exist.
-     */
-    public List<File> getResourceInputs() {
-        List<File> inputs = new ArrayList<File>();
-
-        if (mVariant.getBuildTypeSourceSet() != null) {
-            File typeResLocation = mVariant.getBuildTypeSourceSet().getAndroidResources();
-            if (typeResLocation != null && typeResLocation.isDirectory()) {
-                inputs.add(typeResLocation);
-            }
-        }
-
-        for (SourceSet sourceSet : mVariant.getFlavorSourceSets()) {
-            File flavorResLocation = sourceSet.getAndroidResources();
-            if (flavorResLocation != null && flavorResLocation.isDirectory()) {
-                inputs.add(flavorResLocation);
-            }
-        }
-
-        File mainResLocation = mVariant.getDefaultSourceSet().getAndroidResources();
-        if (mainResLocation != null && mainResLocation.isDirectory()) {
-            inputs.add(mainResLocation);
-        }
-
-        return inputs;
-    }
-
-    /**
      * Pre-process resources. This crunches images and process 9-patches before they can
      * be packaged.
      * This is incremental.
      *
      * Call this directly if you don't care about checking whether the inputs have changed.
-     * Otherwise, get the input first to check with {@link #getResourceInputs()}, and then call
-     * (or not), {@link #preprocessResources(String, java.util.List)}.
+     * Otherwise, get the input first to check with {@link VariantConfiguration#getResourceInputs()}
+     * and then call (or not), {@link #preprocessResources(String, java.util.List)}.
      *
      * @param resOutputDir where the processed resources are stored.
      * @throws IOException
@@ -237,7 +208,7 @@ public class AndroidBuilder {
      */
     public void preprocessResources(@NonNull String resOutputDir)
             throws IOException, InterruptedException {
-        List<File> inputs = getResourceInputs();
+        List<File> inputs = mVariant.getResourceInputs();
 
         preprocessResources(resOutputDir, inputs);
     }
@@ -277,14 +248,24 @@ public class AndroidBuilder {
             command.add("-v");
         }
 
+        boolean runCommand = false;
         for (File input : inputs) {
-            command.add("-S");
-            command.add(input.getAbsolutePath());
+            if (input.isDirectory()) {
+                command.add("-S");
+                command.add(input.getAbsolutePath());
+                runCommand = true;
+            }
+        }
+
+        if (!runCommand) {
+            return;
         }
 
         command.add("-C");
         command.add(resOutputDir);
 
+        mLogger.info("crunch command: %s", command.toString());
+
         mCmdLineRunner.runCmdLine(command);
     }
 
@@ -304,9 +285,26 @@ public class AndroidBuilder {
         }
 
         if (mVariant.getType() == VariantConfiguration.Type.TEST) {
-            generateTestManifest(outManifestLocation);
+            VariantConfiguration testedConfig = mVariant.getTestedConfig();
+            if (testedConfig.getType() == VariantConfiguration.Type.LIBRARY) {
+                try {
+                    // create the test manifest, merge the libraries in it
+                    File generatedTestManifest = File.createTempFile("manifestMerge", ".xml");
+
+                    generateTestManifest(generatedTestManifest.getAbsolutePath());
+
+                    mergeLibraryManifests(
+                            generatedTestManifest,
+                            mVariant.getFullDirectDependencies(),
+                            new File(outManifestLocation));
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            } else {
+                generateTestManifest(outManifestLocation);
+            }
         } else {
-            mergeManifest(outManifestLocation);
+            mergeManifest(mVariant, outManifestLocation);
         }
     }
 
@@ -323,16 +321,16 @@ public class AndroidBuilder {
         }
     }
 
-    private void mergeManifest(String outManifestLocation) {
+    private void mergeManifest(VariantConfiguration config, String outManifestLocation) {
         try {
-            File mainLocation = mVariant.getDefaultSourceSet().getAndroidManifest();
-            File typeLocation = mVariant.getBuildTypeSourceSet().getAndroidManifest();
+            File mainLocation = config.getDefaultSourceSet().getAndroidManifest();
+            File typeLocation = config.getBuildTypeSourceSet().getAndroidManifest();
             if (typeLocation != null && typeLocation.isDirectory() == false) {
                 typeLocation = null;
             }
 
             List<File> flavorManifests = new ArrayList<File>();
-            for (SourceSet sourceSet : mVariant.getFlavorSourceSets()) {
+            for (SourceSet sourceSet : config.getFlavorSourceSets()) {
                 File f = sourceSet.getAndroidManifest();
                 if (f != null && f.isFile()) {
                     flavorManifests.add(f);
@@ -340,10 +338,10 @@ public class AndroidBuilder {
             }
 
             // if no manifest to merge, just copy to location
-            if (typeLocation == null && flavorManifests.isEmpty() && !mVariant.hasLibraries()) {
+            if (typeLocation == null && flavorManifests.isEmpty() && !config.hasLibraries()) {
                 new FileOp().copyFile(mainLocation, new File(outManifestLocation));
             } else {
-                if (!mVariant.hasLibraries()) {
+                if (!config.hasLibraries()) {
 
                     File appMergeOut = new File(outManifestLocation);
 
@@ -367,7 +365,7 @@ public class AndroidBuilder {
 
                     // recursively merge all manifests starting with the leaves and up toward the
                     // root (the app)
-                    mergeLibraryManifests(appMergeOut, mVariant.getDirectLibraries(),
+                    mergeLibraryManifests(appMergeOut, config.getDirectLibraries(),
                             new File(outManifestLocation));
                     }
             }
@@ -385,13 +383,13 @@ public class AndroidBuilder {
         for (AndroidDependency library : directLibraries) {
             List<AndroidDependency> subLibraries = library.getDependencies();
             if (subLibraries == null || subLibraries.size() == 0) {
-                manifests.add(new File(library.getManifest()));
+                manifests.add(library.getManifest());
             } else {
                 File mergeLibManifest = File.createTempFile("manifestMerge", ".xml");
                 mergeLibManifest.deleteOnExit();
 
                 mergeLibraryManifests(
-                        new File(library.getManifest()), subLibraries, mergeLibManifest);
+                        library.getManifest(), subLibraries, mergeLibManifest);
 
                 manifests.add(mergeLibManifest);
             }
@@ -413,6 +411,19 @@ public class AndroidBuilder {
             @Nullable String resPackageOutput,
             @Nullable String proguardOutput,
             @NonNull AaptOptions options) throws IOException, InterruptedException {
+        List<File> inputs = mVariant.getResourceInputs();
+        processResources(manifestFile, preprocessResDir, inputs, sourceOutputDir,
+                resPackageOutput, proguardOutput, options);
+    }
+
+        public void processResources(
+            @NonNull String manifestFile,
+            @Nullable String preprocessResDir,
+            @NonNull List<File> resInputs,
+            @Nullable String sourceOutputDir,
+            @Nullable String resPackageOutput,
+            @Nullable String proguardOutput,
+            @NonNull AaptOptions options) throws IOException, InterruptedException {
         if (mVariant == null) {
             throw new IllegalArgumentException("No Variant Configuration has been set.");
         }
@@ -448,44 +459,24 @@ public class AndroidBuilder {
         command.add("-M");
         command.add(manifestFile);
 
-        // TODO: handle libraries!
         boolean useOverlay =  false;
         if (preprocessResDir != null) {
             File preprocessResFile = new File(preprocessResDir);
             if (preprocessResFile.isDirectory()) {
                 command.add("-S");
                 command.add(preprocessResDir);
-                useOverlay = true;
             }
         }
 
-        if (mVariant.getBuildTypeSourceSet() != null) {
-            File typeResLocation = mVariant.getBuildTypeSourceSet().getAndroidResources();
-            if (typeResLocation != null && typeResLocation.isDirectory()) {
+        for (File resFolder : resInputs) {
+            if (resFolder.isDirectory()) {
                 command.add("-S");
-                command.add(typeResLocation.getAbsolutePath());
-                useOverlay = true;
+                command.add(resFolder.getAbsolutePath());
             }
         }
 
-        for (SourceSet sourceSet : mVariant.getFlavorSourceSets()) {
-            File flavorResLocation = sourceSet.getAndroidResources();
-            if (flavorResLocation != null && flavorResLocation.isDirectory()) {
-                command.add("-S");
-                command.add(flavorResLocation.getAbsolutePath());
-                useOverlay = true;
-            }
-        }
-
-        File mainResLocation = mVariant.getDefaultSourceSet().getAndroidResources();
-        if (mainResLocation != null && mainResLocation.isDirectory()) {
-            command.add("-S");
-            command.add(mainResLocation.getAbsolutePath());
-        }
+        command.add("--auto-add-overlay");
 
-        if (useOverlay) {
-            command.add("--auto-add-overlay");
-        }
 
         // TODO support 2+ assets folders.
 //        if (typeAssetsLocation != null) {
@@ -580,6 +571,17 @@ public class AndroidBuilder {
             }
         }
 
+        // library specific options
+        if (mVariant.getType() == VariantConfiguration.Type.LIBRARY) {
+            command.add("--non-constant-id");
+        }
+
+        String extraPackages = mVariant.getLibraryPackages();
+        if (extraPackages != null) {
+            command.add("--extra-packages");
+            command.add(extraPackages);
+        }
+
         // AAPT options
         String ignoreAssets = options.getIgnoreAssets();
         if (ignoreAssets != null) {
@@ -595,13 +597,14 @@ public class AndroidBuilder {
             }
         }
 
-        mLogger.verbose("aapt command: %s", command.toString());
+        mLogger.info("aapt command: %s", command.toString());
 
         mCmdLineRunner.runCmdLine(command);
     }
 
     public void convertBytecode(
             @NonNull List<String> classesLocation,
+            @NonNull List<String> libraries,
             @NonNull String outDexFile,
             @NonNull DexOptions dexOptions) throws IOException, InterruptedException {
         if (mVariant == null) {
@@ -630,10 +633,14 @@ public class AndroidBuilder {
         // TODO: handle dependencies
         // TODO: handle dex options
 
-        mLogger.verbose("Input: " + classesLocation);
+        mLogger.verbose("Dex class inputs: " + classesLocation);
 
         command.addAll(classesLocation);
 
+        mLogger.verbose("Dex library inputs: " + libraries);
+
+        command.addAll(libraries);
+
         mCmdLineRunner.runCmdLine(command);
     }
 
index 4c53d41..b667fa6 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.builder;
 
+import java.io.File;
 import java.util.List;
 
 /**
@@ -32,35 +33,35 @@ public interface AndroidDependency {
      * Returns the location of the jar file to use for packaging.
      * Cannot be null.
      */
-    String getJarFile();
+    File getJarFile();
 
     /**
      * Returns the location of the manifest.
      */
-    String getManifest();
+    File getManifest();
 
     /**
      * Returns the location of the res folder.
      */
-    String getResFolder();
+    File getResFolder();
 
     /**
      * Returns the location of the assets folder.
      */
-    String getAssetsFolder();
+    File getAssetsFolder();
 
     /**
      * Returns the location of the jni libraries folder.
      */
-    String getJniFolder();
+    File getJniFolder();
 
     /**
      * Returns the location of the proguard files.
      */
-    String getProguardRules();
+    File getProguardRules();
 
     /**
      * Returns the location of the lint jar.
      */
-    String getLintJar();
+    File getLintJar();
 }
index 1742b61..d7014ae 100644 (file)
@@ -25,6 +25,8 @@ public interface SourceSet {
 
     Set<File> getJavaResources();
 
+    Iterable<File> getCompileClasspath();
+
     File getAndroidResources();
     File getAndroidAssets();
     File getAndroidManifest();
index 232f261..010b019 100644 (file)
@@ -22,7 +22,9 @@ import com.android.annotations.VisibleForTesting;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * A Variant configuration.
@@ -45,6 +47,8 @@ public class VariantConfiguration {
 
     private ProductFlavor mMergedFlavor;
 
+    private AndroidDependency mOutput;
+
     private List<JarDependency> mJars;
 
     /** List of direct library project dependencies. Each object defines its own dependencies. */
@@ -55,7 +59,6 @@ public class VariantConfiguration {
      * of latter ones. */
     private final List<AndroidDependency> mFlatLibraryProjects = new ArrayList<AndroidDependency>();
 
-
     public static enum Type {
         DEFAULT, LIBRARY, TEST;
     }
@@ -117,6 +120,9 @@ public class VariantConfiguration {
         mTestedConfig = testedConfig;
 
         assert mType != Type.TEST || mTestedConfig != null;
+        assert mTestedConfig == null ||
+                mTestedConfig.mType != Type.LIBRARY ||
+                mTestedConfig.mOutput != null;
 
         mMergedFlavor = mDefaultConfig;
 
@@ -148,9 +154,56 @@ public class VariantConfiguration {
      * @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 void setAndroidDependencies(@NonNull List<AndroidDependency> directLibraryProjects) {
+        if (directLibraryProjects != null) {
+            mDirectLibraryProjects.addAll(directLibraryProjects);
+        }
+
+        resolveIndirectLibraryDependencies(getFullDirectDependencies(), mFlatLibraryProjects);
+    }
+
+    /**
+     * Returns all direct dependencies, including the tested config if it's a library itself.
+     * @return
+     */
+    public List<AndroidDependency> getFullDirectDependencies() {
+        if (mTestedConfig != null && mTestedConfig.getType() == Type.LIBRARY) {
+            // in case of a library we merge all the dependencies together.
+            List<AndroidDependency> list = new ArrayList<AndroidDependency>(
+                    mDirectLibraryProjects.size() +
+                            mTestedConfig.mDirectLibraryProjects.size() + 1);
+            list.addAll(mDirectLibraryProjects);
+            list.add(mTestedConfig.mOutput);
+            list.addAll(mTestedConfig.mDirectLibraryProjects);
+
+            return list;
+        }
+
+        return mDirectLibraryProjects;
+    }
+
+    public String getLibraryPackages() {
+        if (mFlatLibraryProjects.isEmpty()) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        for (AndroidDependency dep : mFlatLibraryProjects) {
+            File manifest = dep.getManifest();
+            String packageName = sManifestParser.getPackage(manifest);
+            if (sb.length() > 0) {
+                sb.append(':');
+            }
+            sb.append(packageName);
+        }
+
+        return sb.toString();
+    }
+
+
+    public void setOutput(AndroidDependency output) {
+        mOutput = output;
     }
 
     public ProductFlavor getDefaultConfig() {
@@ -217,6 +270,10 @@ public class VariantConfiguration {
         return mType;
     }
 
+    VariantConfiguration getTestedConfig() {
+        return mTestedConfig;
+    }
+
     /**
      * 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
@@ -227,6 +284,9 @@ public class VariantConfiguration {
     @VisibleForTesting
     void resolveIndirectLibraryDependencies(List<AndroidDependency> directDependencies,
                                             List<AndroidDependency> outFlatDependencies) {
+        if (directDependencies == null) {
+            return;
+        }
         // 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--) {
@@ -272,7 +332,11 @@ public class VariantConfiguration {
 
     public String getTestedPackageName() {
         if (mType == Type.TEST) {
-            return mTestedConfig.getPackageName();
+            if (mTestedConfig.mType == Type.LIBRARY) {
+                return getPackageName();
+            } else {
+                return mTestedConfig.getPackageName();
+            }
         }
 
         return null;
@@ -314,12 +378,83 @@ public class VariantConfiguration {
      * Reads the package name from the manifest.
      * @return
      */
-    @VisibleForTesting
-    String getPackageFromManifest() {
+    public String getPackageFromManifest() {
         File manifestLocation = mDefaultSourceSet.getAndroidManifest();
         return sManifestParser.getPackage(manifestLocation);
     }
 
+    /**
+     * Returns the dynamic list of resource folders based on the configuration, its dependencies,
+     * as well as tested config if applicable (test of a library).
+     * @return a list of input resource folders.
+     */
+    public List<File> getResourceInputs() {
+        List<File> inputs = new ArrayList<File>();
+
+        if (mBuildTypeSourceSet != null) {
+            File typeResLocation = mBuildTypeSourceSet.getAndroidResources();
+            if (typeResLocation != null) {
+                inputs.add(typeResLocation);
+            }
+        }
+
+        for (SourceSet sourceSet : mFlavorSourceSets) {
+            File flavorResLocation = sourceSet.getAndroidResources();
+            if (flavorResLocation != null) {
+                inputs.add(flavorResLocation);
+            }
+        }
+
+        File mainResLocation = mDefaultSourceSet.getAndroidResources();
+        if (mainResLocation != null) {
+            inputs.add(mainResLocation);
+        }
+
+        for (AndroidDependency dependency : mFlatLibraryProjects) {
+            File resFolder = dependency.getResFolder();
+            if (resFolder != null) {
+                inputs.add(resFolder);
+            }
+        }
+
+        return inputs;
+    }
+
+    /**
+     * Returns the compile classpath for this config. If the config tests a library, this
+     * will include the classpath of the tested config
+     * @return
+     */
+    public Set<File> getCompileClasspath() {
+        Set<File> classpath = new HashSet<File>();
+
+        for (File f : mDefaultSourceSet.getCompileClasspath()) {
+            classpath.add(f);
+        }
+
+        if (mBuildTypeSourceSet != null) {
+            for (File f : mBuildTypeSourceSet.getCompileClasspath()) {
+                classpath.add(f);
+            }
+        }
+
+        for (SourceSet sourceSet : mFlavorSourceSets) {
+            for (File f : sourceSet.getCompileClasspath()) {
+                classpath.add(f);
+            }
+        }
+
+        if (mType == Type.TEST && mTestedConfig.mType == Type.LIBRARY) {
+            // the tested library is added to the main app so we need its compile classpath as well.
+            // which starts with its output
+            classpath.add(mTestedConfig.mOutput.getJarFile());
+            classpath.addAll(mTestedConfig.getCompileClasspath());
+        }
+
+        return classpath;
+    }
+
+
     protected void validate() {
         if (mType != Type.TEST) {
             File manifest = mDefaultSourceSet.getAndroidManifest();
index 7aa946f..10c9635 100644 (file)
@@ -18,6 +18,8 @@ package com.android.build.gradle
 import com.android.build.gradle.internal.AndroidSourceSet
 import com.android.build.gradle.internal.ApplicationVariant
 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.AndroidBuilder
 import com.android.builder.DefaultSdkParser
 import com.android.builder.ProductFlavor
@@ -25,14 +27,12 @@ import com.android.builder.SdkParser
 import com.android.builder.VariantConfiguration
 import com.android.utils.ILogger
 import org.gradle.api.Project
+import org.gradle.api.Task
 import org.gradle.api.logging.LogLevel
+import org.gradle.api.plugins.BasePlugin
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.compile.Compile
-import org.gradle.api.Task
-import org.gradle.api.plugins.BasePlugin
-import com.android.build.gradle.internal.TestAppVariant
-import com.android.build.gradle.internal.ProductionAppVariant
 
 /**
  * Base class for all Android plugins
@@ -159,20 +159,17 @@ abstract class AndroidBasePlugin {
         return androidBuilder.runtimeClasspath.join(":")
     }
 
-    /**
-     * Returns the folder directly under build/ into which the generated manifest is saved.
-     */
-    protected abstract String getManifestOutDir();
-
-    protected ProcessManifest createProcessManifestTask(ApplicationVariant variant) {
+    protected ProcessManifest createProcessManifestTask(ApplicationVariant variant,
+                                                        String manifestOurDir) {
         def processManifestTask = project.tasks.add("process${variant.name}Manifest",
                 ProcessManifest)
         processManifestTask.plugin = this
         processManifestTask.variant = variant
-        processManifestTask.conventionMapping.mergedManifest = {
+        processManifestTask.conventionMapping.processedManifest = {
             project.file(
-                    "$project.buildDir/${getManifestOutDir()}/$variant.dirName/AndroidManifest.xml")
+                    "$project.buildDir/${manifestOurDir}/$variant.dirName/AndroidManifest.xml")
         }
+
         return processManifestTask
     }
 
@@ -180,12 +177,11 @@ abstract class AndroidBasePlugin {
         def crunchTask = project.tasks.add("crunch${variant.name}Res", CrunchResources)
         crunchTask.plugin = this
         crunchTask.variant = variant
-        crunchTask.conventionMapping.resDirectories = {
-            crunchTask.getBuilder().getResourceInputs()
-        }
+        crunchTask.conventionMapping.resDirectories = { variant.config.resourceInputs }
         crunchTask.conventionMapping.outputDir = {
             project.file("$project.buildDir/res/$variant.dirName")
         }
+
         return crunchTask
     }
 
@@ -212,7 +208,7 @@ abstract class AndroidBasePlugin {
         processResources.dependsOn processManifestTask
         processResources.plugin = this
         processResources.variant = variant
-        processResources.conventionMapping.manifestFile = { processManifestTask.mergedManifest }
+        processResources.conventionMapping.manifestFile = { processManifestTask.processedManifest }
         // TODO: unify with generateBuilderConfig somehow?
         processResources.conventionMapping.sourceOutputDir = {
             project.file("$project.buildDir/source/$variant.dirName")
@@ -230,9 +226,11 @@ abstract class AndroidBasePlugin {
         if (crunchTask != null) {
             processResources.dependsOn crunchTask
             processResources.conventionMapping.crunchDir = { crunchTask.outputDir }
+            processResources.conventionMapping.resDirectories = { crunchTask.resDirectories }
+        } else {
+            processResources.conventionMapping.resDirectories = { variant.config.resourceInputs }
         }
 
-
         processResources.aaptOptions = extension.aaptOptions
         return processResources
     }
@@ -258,15 +256,17 @@ abstract class AndroidBasePlugin {
             }
         }
         compileTask.source = sourceList.toArray()
-        // TODO: support classpath per flavor
-        if (testedVariant != null) {
-            compileTask.classpath =
-                ((AndroidSourceSet) config.defaultSourceSet).sourceSet.compileClasspath +
-                        testedVariant.runtimeClasspath
+
+        // if the test is for a full app, the tested runtimeClasspath is added to the classpath for
+        // compilation only, not for packaging
+        variant.packagedClasspath = project.files(config.compileClasspath)
+        if (testedVariant != null &&
+                testedVariant.config.type != VariantConfiguration.Type.LIBRARY) {
+            compileTask.classpath = variant.packagedClasspath + testedVariant.runtimeClasspath
         } else {
-            compileTask.classpath =
-                ((AndroidSourceSet) config.defaultSourceSet).sourceSet.compileClasspath
+            compileTask.classpath = variant.packagedClasspath
         }
+
         compileTask.conventionMapping.destinationDir = {
             project.file("$project.buildDir/classes/$variant.dirName")
         }
@@ -275,7 +275,6 @@ abstract class AndroidBasePlugin {
         }
 
         // Wire up the outputs
-        // 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
@@ -285,11 +284,19 @@ abstract class AndroidBasePlugin {
 
     protected void createTestTasks(TestAppVariant variant, ProductionAppVariant testedVariant) {
         // Add a task to process the manifest
-        def processManifestTask = createProcessManifestTask(variant)
+        def processManifestTask = createProcessManifestTask(variant, "manifests")
 
         // Add a task to crunch resource files
         def crunchTask = createCrunchResTask(variant)
 
+        if (testedVariant.config.type == VariantConfiguration.Type.LIBRARY) {
+            // in this case the tested library must be fully built before test can be built!
+            if (testedVariant.assembleTask != null) {
+                processManifestTask.dependsOn testedVariant.assembleTask
+                crunchTask.dependsOn testedVariant.assembleTask
+            }
+        }
+
         // Add a task to create the BuildConfig class
         def generateBuildConfigTask = createBuildConfigTask(variant, processManifestTask)
 
@@ -320,7 +327,8 @@ abstract class AndroidBasePlugin {
         dexTask.dependsOn variant.compileTask
         dexTask.plugin = this
         dexTask.variant = variant
-        dexTask.conventionMapping.sourceFiles = { variant.runtimeClasspath }
+        dexTask.conventionMapping.libraries = { variant.packagedClasspath }
+        dexTask.conventionMapping.sourceFiles = { variant.compileTask.outputs.files }
         dexTask.conventionMapping.outputFile = {
             project.file(
                     "${project.buildDir}/libs/${project.archivesBaseName}-${variant.baseName}.dex")
index bbfaf93..c8b7a99 100644 (file)
@@ -26,6 +26,7 @@ import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.tasks.bundling.Zip
 import com.android.build.gradle.internal.TestAppVariant
+import com.android.builder.AndroidDependency
 
 class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project> {
 
@@ -49,6 +50,8 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
 
         debugBuildTypeData = new BuildTypeData(extension.debug, debugSourceSet, project)
         releaseBuildTypeData = new BuildTypeData(extension.release, releaseSourceSet, project)
+        project.tasks.assemble.dependsOn debugBuildTypeData.assembleTask
+        project.tasks.assemble.dependsOn releaseBuildTypeData.assembleTask
 
         project.afterEvaluate {
             createAndroidTasks()
@@ -68,11 +71,13 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
                 defaultConfigData.productFlavor, defaultConfigData.androidSourceSet,
                 buildTypeData.buildType, buildTypeData.androidSourceSet,
                 VariantConfiguration.Type.LIBRARY)
+        // TODO: add actual dependencies
+        variantConfig.setAndroidDependencies(null)
 
         ProductionAppVariant variant = new ProductionAppVariant(variantConfig)
 
         // Add a task to process the manifest(s)
-        ProcessManifest processManifestTask = createProcessManifestTask(variant)
+        ProcessManifest processManifestTask = createProcessManifestTask(variant, DIR_BUNDLES)
 
         // Add a task to create the BuildConfig class
         def generateBuildConfigTask = createBuildConfigTask(variant, null)
@@ -89,6 +94,11 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
         jar.from(variant.compileTask.outputs);
         jar.destinationDir = project.file("$project.buildDir/$DIR_BUNDLES/${variant.dirName}")
         jar.baseName = "classes"
+        String packageName = variantConfig.getPackageFromManifest().replace('.', '/');
+        jar.exclude(packageName + "/R.class")
+        jar.exclude(packageName + "/R\$*.class")
+        jar.exclude(packageName + "/Manifest.class")
+        jar.exclude(packageName + "/Manifest\$*.class")
 
         // merge the resources into the bundle folder
         Copy mergeRes = project.tasks.add("merge${variant.name}Res",
@@ -104,10 +114,55 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
         bundle.setDescription("Assembles a bundle containing the library in ${variant.name}.");
         bundle.destinationDir = project.file("$project.buildDir/libs")
         bundle.extension = "alb"
-        bundle.baseName = variant.baseName
+        bundle.baseName = "${project.archivesBaseName}-${variant.baseName}"
         bundle.from(project.file("$project.buildDir/$DIR_BUNDLES/${variant.dirName}"))
 
         buildTypeData.assembleTask.dependsOn bundle
+        variant.assembleTask = bundle
+
+        // configure the variant to be testable.
+        variantConfig.output = new AndroidDependency() {
+
+            @Override
+            List<AndroidDependency> getDependencies() {
+                return null;
+            }
+
+            @Override
+            File getJarFile() {
+                return jar.archivePath
+            }
+
+            @Override
+            File getManifest() {
+                return processManifestTask.processedManifest
+            }
+
+            @Override
+            File getResFolder() {
+                return mergeRes.destinationDir
+            }
+
+            @Override
+            File getAssetsFolder() {
+                return null
+            }
+
+            @Override
+            File getJniFolder() {
+                return null
+            }
+
+            @Override
+            File getProguardRules() {
+                return null
+            }
+
+            @Override
+            File getLintJar() {
+                return null
+            }
+        };
 
         return variant
     }
@@ -116,11 +171,13 @@ class AndroidLibraryPlugin extends AndroidBasePlugin implements Plugin<Project>
         ProductFlavorData defaultConfigData = getDefaultConfigData();
 
         def testVariantConfig = new VariantConfiguration(
-                defaultConfigData.productFlavor, defaultConfigData.androidSourceSet,
+                defaultConfigData.productFlavor, defaultConfigData.androidTestSourceSet,
                 debugBuildTypeData.buildType, null,
-                VariantConfiguration.Type.TEST)
+                VariantConfiguration.Type.TEST, testedVariant.config)
+        // TODO: add actual dependencies
+        testVariantConfig.setAndroidDependencies(null)
 
-        def testVariant = new TestAppVariant(testVariantConfig, testedVariant.config)
+        def testVariant = new TestAppVariant(testVariantConfig,)
         createTestTasks(testVariant, testedVariant)
     }
 
index ecd958a..ddb04a9 100644 (file)
@@ -142,6 +142,8 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
             def variantConfig = new VariantConfiguration(
                     defaultConfigData.productFlavor, defaultConfigData.androidSourceSet,
                     buildTypeData.buildType, buildTypeData.androidSourceSet)
+            // TODO: add actual dependencies
+            variantConfig.setAndroidDependencies(null)
 
             boolean isTestedVariant = (buildTypeData == testData)
 
@@ -160,6 +162,9 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
                 testData.buildType, null,
                 VariantConfiguration.Type.TEST, testedVariant.config)
 
+        // TODO: add actual dependencies
+        testVariantConfig.setAndroidDependencies(null)
+
         def testVariant = new TestAppVariant(testVariantConfig)
         createTestTasks(testVariant, testedVariant)
     }
@@ -187,6 +192,9 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
             variantConfig.addProductFlavor(productFlavorData.productFlavor,
                     productFlavorData.androidSourceSet)
 
+            // TODO: add actual dependencies
+            variantConfig.setAndroidDependencies(null)
+
             boolean isTestedVariant = (buildTypeData == testData)
 
             ProductionAppVariant productionAppVariant = addVariant(variantConfig, null,
@@ -209,6 +217,9 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
         testVariantConfig.addProductFlavor(productFlavorData.productFlavor,
                 productFlavorData.androidTestSourceSet)
 
+        // TODO: add actual dependencies
+        testVariantConfig.setAndroidDependencies(null)
+
         def testVariant = new TestAppVariant(testVariantConfig)
         createTestTasks(testVariant, testedVariant)
     }
@@ -227,7 +238,7 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
         def variant = new ProductionAppVariant(variantConfig)
 
         // Add a task to process the manifest(s)
-        ProcessManifest processManifestTask = createProcessManifestTask(variant)
+        ProcessManifest processManifestTask = createProcessManifestTask(variant, "manifests")
 
         // Add a task to crunch resource files
         def crunchTask = createCrunchResTask(variant)
@@ -253,9 +264,4 @@ class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
     String getTarget() {
         return extension.target;
     }
-
-    protected String getManifestOutDir() {
-        return "manifests"
-    }
-
 }
index ba1560d..510129b 100644 (file)
@@ -28,6 +28,9 @@ class Dex extends BaseAndroidTask {
     @InputFiles
     Iterable<File> sourceFiles
 
+    @InputFiles
+    Iterable<File> libraries
+
     @Input
     DexOptions dexOptions
 
@@ -40,6 +43,13 @@ class Dex extends BaseAndroidTask {
             }
         }
 
-        getBuilder().convertBytecode(files, getOutputFile().absolutePath, dexOptions)
+        List<String> libs = new ArrayList<String>();
+        for (File f : getLibraries()) {
+            if (f != null && f.exists()) {
+                libs.add(f.absolutePath)
+            }
+        }
+
+        getBuilder().convertBytecode(files, libs, getOutputFile().absolutePath, getDexOptions())
     }
 }
index bda064a..b5437df 100644 (file)
@@ -23,10 +23,10 @@ import org.gradle.api.tasks.TaskAction
 class ProcessManifest extends BaseAndroidTask {
 
     @OutputFile
-    File mergedManifest
+    File processedManifest
 
     @TaskAction
     void generate() {
-        getBuilder().processManifest(getMergedManifest().absolutePath)
+        getBuilder().processManifest(getProcessedManifest().absolutePath)
     }
 }
index bbe9348..11126c9 100644 (file)
@@ -23,6 +23,7 @@ import org.gradle.api.tasks.Optional
 import org.gradle.api.tasks.OutputDirectory
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.InputFiles
 
 class ProcessResources extends BaseAndroidTask {
 
@@ -32,6 +33,9 @@ class ProcessResources extends BaseAndroidTask {
     @InputDirectory @Optional
     File crunchDir
 
+    @InputFiles
+    Iterable<File> resDirectories
+
     @OutputDirectory @Optional
     File sourceOutputDir
 
@@ -50,6 +54,7 @@ class ProcessResources extends BaseAndroidTask {
         getBuilder().processResources(
                 getManifestFile().absolutePath,
                 getCrunchDir()?.absolutePath,
+                getResDirectories(),
                 getSourceOutputDir()?.absolutePath,
                 getPackageFile()?.absolutePath,
                 getProguardFile()?.absolutePath,
index 71cb98a..70b4976 100644 (file)
@@ -39,6 +39,11 @@ public class AndroidSourceSet implements com.android.builder.SourceSet {
     }
 
     @Override
+    Iterable<File> getCompileClasspath() {
+        return sourceSet.compileClasspath
+    }
+
+    @Override
     File getAndroidResources() {
         // FIXME: make this configurable by the SourceSet
         return project.file("src/$name/res")
index 99d37df..54b3039 100644 (file)
@@ -24,32 +24,33 @@ import com.android.builder.VariantConfiguration
 /**
  * Represents something that can be packaged into an APK and installed.
  */
-public interface ApplicationVariant {
-    String getName()
+public abstract class ApplicationVariant {
 
-    String getDescription()
+    final VariantConfiguration config
+    FileCollection runtimeClasspath
+    FileCollection packagedClasspath
+    FileCollection resourcePackage
+    Compile compileTask
 
-    String getDirName()
+    ApplicationVariant(VariantConfiguration config) {
+        this.config = config
+    }
 
-    String getBaseName()
+    abstract String getDescription()
 
-    VariantConfiguration getConfig()
+    abstract String getDirName()
 
-    boolean getZipAlign()
+    abstract String getBaseName()
 
-    boolean isSigned()
+    abstract boolean getZipAlign()
 
-    boolean getRunProguard()
+    abstract boolean isSigned()
 
-    FileCollection getRuntimeClasspath()
+    abstract boolean getRunProguard()
 
-    FileCollection getResourcePackage()
+    abstract List<String> getRunCommand()
 
-    Compile getCompileTask()
+    abstract String getPackage()
 
-    List<String> getRunCommand()
-
-    String getPackage()
-
-    AndroidBuilder createBuilder(AndroidBasePlugin androidBasePlugin)
+    abstract AndroidBuilder createBuilder(AndroidBasePlugin androidBasePlugin)
 }
index 9ee9e31..f3e78e7 100644 (file)
@@ -19,19 +19,13 @@ import com.android.build.gradle.AndroidBasePlugin
 import com.android.builder.AndroidBuilder
 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 {
+class ProductionAppVariant extends ApplicationVariant {
     final String name
-    final VariantConfiguration config
-    FileCollection runtimeClasspath
-    FileCollection resourcePackage
-    Compile compileTask
     Task assembleTask
 
     ProductionAppVariant(VariantConfiguration config) {
-        this.config = config
+        super(config)
         if (config.hasFlavors()) {
             this.name = "${config.firstFlavor.name.capitalize()}${config.buildType.name.capitalize()}"
         } else {
index d059001..2b7931e 100644 (file)
@@ -17,19 +17,13 @@ package com.android.build.gradle.internal
 
 import com.android.build.gradle.AndroidBasePlugin
 import com.android.builder.AndroidBuilder
-import org.gradle.api.file.FileCollection
-import org.gradle.api.tasks.compile.Compile
 import com.android.builder.VariantConfiguration
 
-class TestAppVariant implements ApplicationVariant {
+class TestAppVariant extends ApplicationVariant {
     final String name
-    final VariantConfiguration config
-    FileCollection runtimeClasspath
-    FileCollection resourcePackage
-    Compile compileTask
 
     TestAppVariant(VariantConfiguration config) {
-        this.config = config
+        super(config)
         if (config.hasFlavors()) {
             this.name = "${config.firstFlavor.name.capitalize()}Test"
         } else {
diff --git a/testapps/applibtest/app/build.gradle b/testapps/applibtest/app/build.gradle
new file mode 100644 (file)
index 0000000..1faee70
--- /dev/null
@@ -0,0 +1,8 @@
+apply plugin: 'android'
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+//    compile project(':lib')
+}
diff --git a/testapps/applibtest/app/proguard-project.txt b/testapps/applibtest/app/proguard-project.txt
new file mode 100644 (file)
index 0000000..f2fe155
--- /dev/null
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/testapps/applibtest/app/src/main/AndroidManifest.xml b/testapps/applibtest/app/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..41e6b82
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tests.testprojecttest.app"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/testapps/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png b/testapps/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..96a442e
Binary files /dev/null and b/testapps/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/testapps/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png b/testapps/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..9923872
Binary files /dev/null and b/testapps/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png differ
diff --git a/testapps/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png b/testapps/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..359047d
Binary files /dev/null and b/testapps/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/testapps/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png b/testapps/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..71c6d76
Binary files /dev/null and b/testapps/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/testapps/applibtest/app/src/main/res/values/strings.xml b/testapps/applibtest/app/src/main/res/values/strings.xml
new file mode 100644 (file)
index 0000000..c933032
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">TestProjectTest-app</string>
+
+</resources>
\ No newline at end of file
diff --git a/testapps/applibtest/app/src/test/AndroidManifest.xml b/testapps/applibtest/app/src/test/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..5252972
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tests.testprojecttest.test"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="15" />
+
+    <!--
+         We add an application tag here just so that we can indicate that
+         this package needs to link against the android.test library,
+         which is needed when building test cases.
+    -->
+    <application android:label="testProjectTest-testapp">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!--
+    This declares that this app uses the instrumentation test runner targeting
+    the package of com.android.tests.testprojecttest.app.  To run the tests use the command:
+    "adb shell am instrument -w com.android.tests.testprojecttest.test/android.test.InstrumentationTestRunner"
+    -->
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.tests.testprojecttest.app" />
+
+</manifest>
\ No newline at end of file
diff --git a/testapps/applibtest/app/src/test/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/testapps/applibtest/app/src/test/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
new file mode 100644 (file)
index 0000000..9be6f97
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 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.tests.testprojecttest.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.testprojecttest.app.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test.  This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events.  See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface.  When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class LibActivityTest extends ActivityInstrumentationTestCase2<LibActivity> {
+
+    private TextView mTextView;
+
+    /**
+     * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+     */
+    public LibActivityTest() {
+        super(LibActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final LibActivity a = getActivity();
+        // ensure a valid handle to the activity has been returned
+        assertNotNull(a);
+        mTextView = (TextView) a.findViewById(R.id.text);
+    }
+
+    /**
+     * The name 'test preconditions' is a convention to signal that if this
+     * test doesn't pass, the test case was not set up properly and it might
+     * explain any and all failures in other tests.  This is not guaranteed
+     * to run before other tests, as junit uses reflection to find the tests.
+     */
+    @MediumTest
+    public void testPreconditions() {
+        assertNotNull(mTextView);
+    }
+}
diff --git a/testapps/applibtest/app/src/test/java/com/android/tests/testprojecttest/test/AllTests.java b/testapps/applibtest/app/src/test/java/com/android/tests/testprojecttest/test/AllTests.java
new file mode 100644 (file)
index 0000000..a77b53c
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 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.tests.testprojecttest.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+/**
+ * A test suite containing all tests for ApiDemos.
+ *
+ * To run all suites found in this apk:
+ * $ adb shell am instrument -w \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run just this suite from the command line:
+ * $ adb shell am instrument -w \
+ *   -e class com.example.android.apis.AllTests \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test case, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest}:
+ * $ adb shell am instrument -w \
+ *   -e class com.example.android.apis.os.MorseCodeConverterTest \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest#testCharacterS()}:
+ * $ adb shell am instrument -w \
+ *   -e class com.example.android.apis.os.MorseCodeConverterTest#testCharacterS \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ */
+public class AllTests extends TestSuite {
+
+    public static Test suite() {
+        return new TestSuiteBuilder(AllTests.class)
+                .includeAllPackagesUnderHere()
+                .build();
+    }
+}
diff --git a/testapps/applibtest/build.gradle b/testapps/applibtest/build.gradle
new file mode 100644 (file)
index 0000000..0c73a9f
--- /dev/null
@@ -0,0 +1,8 @@
+buildscript {
+    repositories {
+        maven { url '../../repo' }
+    }
+    dependencies {
+        classpath 'com.android.build:gradle-android:0.1-SNAPSHOT'
+    }
+}
diff --git a/testapps/applibtest/lib/build.gradle b/testapps/applibtest/lib/build.gradle
new file mode 100644 (file)
index 0000000..5ca87af
--- /dev/null
@@ -0,0 +1,9 @@
+apply plugin: 'android-library'
+
+android {
+    target = "android-15"
+
+       defaultConfig {
+               testPackageName = "com.android.tests.testprojecttest.testlib"
+       }
+}
\ No newline at end of file
diff --git a/testapps/applibtest/lib/proguard-project.txt b/testapps/applibtest/lib/proguard-project.txt
new file mode 100644 (file)
index 0000000..f2fe155
--- /dev/null
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/testapps/applibtest/lib/src/main/AndroidManifest.xml b/testapps/applibtest/lib/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..ba33def
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tests.testprojecttest.lib">
+    <application>
+        <activity
+            android:name="com.android.tests.testprojecttest.lib.LibActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/testapps/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java b/testapps/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
new file mode 100644 (file)
index 0000000..7d7f607
--- /dev/null
@@ -0,0 +1,13 @@
+package com.android.tests.testprojecttest.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class LibActivity extends Activity {
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+}
\ No newline at end of file
diff --git a/testapps/applibtest/lib/src/main/res/layout/main.xml b/testapps/applibtest/lib/src/main/res/layout/main.xml
new file mode 100644 (file)
index 0000000..14a9c4b
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="some string"
+        tools:ignore="HardcodedText" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/testapps/applibtest/lib/src/main/res/values/strings.xml b/testapps/applibtest/lib/src/main/res/values/strings.xml
new file mode 100644 (file)
index 0000000..fdb2272
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">TestProjectTest-lib</string>
+
+</resources>
\ No newline at end of file
diff --git a/testapps/applibtest/lib/src/test/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/testapps/applibtest/lib/src/test/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
new file mode 100644 (file)
index 0000000..6632c58
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 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.tests.testprojecttest.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.testprojecttest.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test.  This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events.  See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface.  When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class LibActivityTest extends ActivityInstrumentationTestCase2<LibActivity> {
+
+    private TextView mTextView;
+
+    /**
+     * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+     */
+    public LibActivityTest() {
+        super(LibActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final LibActivity a = getActivity();
+        // ensure a valid handle to the activity has been returned
+        assertNotNull(a);
+        mTextView = (TextView) a.findViewById(R.id.text);
+    }
+
+    /**
+     * The name 'test preconditions' is a convention to signal that if this
+     * test doesn't pass, the test case was not set up properly and it might
+     * explain any and all failures in other tests.  This is not guaranteed
+     * to run before other tests, as junit uses reflection to find the tests.
+     */
+    @MediumTest
+    public void testPreconditions() {
+        assertNotNull(mTextView);
+    }
+}
diff --git a/testapps/applibtest/lib/src/test/java/com/android/tests/testprojecttest/test/AllTests.java b/testapps/applibtest/lib/src/test/java/com/android/tests/testprojecttest/test/AllTests.java
new file mode 100644 (file)
index 0000000..a77b53c
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 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.tests.testprojecttest.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+/**
+ * A test suite containing all tests for ApiDemos.
+ *
+ * To run all suites found in this apk:
+ * $ adb shell am instrument -w \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run just this suite from the command line:
+ * $ adb shell am instrument -w \
+ *   -e class com.example.android.apis.AllTests \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test case, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest}:
+ * $ adb shell am instrument -w \
+ *   -e class com.example.android.apis.os.MorseCodeConverterTest \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest#testCharacterS()}:
+ * $ adb shell am instrument -w \
+ *   -e class com.example.android.apis.os.MorseCodeConverterTest#testCharacterS \
+ *   com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ */
+public class AllTests extends TestSuite {
+
+    public static Test suite() {
+        return new TestSuiteBuilder(AllTests.class)
+                .includeAllPackagesUnderHere()
+                .build();
+    }
+}
diff --git a/testapps/applibtest/lib/src/test/res/values/strings.xml b/testapps/applibtest/lib/src/test/res/values/strings.xml
new file mode 100644 (file)
index 0000000..ef42478
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="hello">Hello World!</string>
+    <string name="app_name">TestProjectTest-testTest</string>
+
+</resources>
\ No newline at end of file
diff --git a/testapps/applibtest/settings.gradle b/testapps/applibtest/settings.gradle
new file mode 100644 (file)
index 0000000..5ed7972
--- /dev/null
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
\ No newline at end of file