Manifest merger in Ant build.

This merges the manifest of the libraries into the application's
manifest.

Change-Id: I425e7b75f71d3f50c6422cdb62bb5ec6811ce99d
diff --git a/anttasks/.classpath b/anttasks/.classpath
index b936073..283606e 100644
--- a/anttasks/.classpath
+++ b/anttasks/.classpath
@@ -5,5 +5,6 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
 	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/ant/ant.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/ManifestMerger"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/anttasks/Android.mk b/anttasks/Android.mk
index e75a7cd..5139400 100644
--- a/anttasks/Android.mk
+++ b/anttasks/Android.mk
@@ -23,6 +23,7 @@
 
 LOCAL_JAVA_LIBRARIES := \
         sdklib \
+        manifmerger \
         ant
 
 LOCAL_MODULE := anttasks
diff --git a/anttasks/etc/manifest.txt b/anttasks/etc/manifest.txt
index 257f2aa..9118df7 100644
--- a/anttasks/etc/manifest.txt
+++ b/anttasks/etc/manifest.txt
@@ -1 +1 @@
-Class-Path: common.jar sdklib.jar
+Class-Path: common.jar sdklib.jar manifmerger.jar
diff --git a/anttasks/src/com/android/ant/ComputeDependencyTask.java b/anttasks/src/com/android/ant/ComputeDependencyTask.java
index 17b68d6..62d5917 100644
--- a/anttasks/src/com/android/ant/ComputeDependencyTask.java
+++ b/anttasks/src/com/android/ant/ComputeDependencyTask.java
@@ -53,6 +53,7 @@
  */
 public class ComputeDependencyTask extends GetLibraryListTask {
 
+    private String mLibraryManifestFilePathOut;
     private String mLibraryResFolderPathOut;
     private String mLibraryPackagesOut;
     private String mJarLibraryPathOut;
@@ -60,6 +61,10 @@
     private int mTargetApi = -1;
     private boolean mVerbose = false;
 
+    public void setLibraryManifestFilePathOut(String libraryManifestFilePathOut) {
+        mLibraryManifestFilePathOut = libraryManifestFilePathOut;
+    }
+
     public void setLibraryResFolderPathOut(String libraryResFolderPathOut) {
         mLibraryResFolderPathOut = libraryResFolderPathOut;
     }
@@ -90,6 +95,9 @@
 
     @Override
     public void execute() throws BuildException {
+        if (mLibraryManifestFilePathOut == null) {
+            throw new BuildException("Missing attribute libraryManifestFilePathOut");
+        }
         if (mLibraryResFolderPathOut == null) {
             throw new BuildException("Missing attribute libraryResFolderPathOut");
         }
@@ -112,6 +120,7 @@
         File sdkDir = TaskHelper.getSdkLocation(antProject);
 
         // prepare several paths for future tasks
+        final Path manifestFilePath = new Path(antProject);
         final Path resFolderPath = new Path(antProject);
         final Path nativeFolderPath = new Path(antProject);
         final StringBuilder packageStrBuilder = new StringBuilder();
@@ -122,9 +131,14 @@
                 // let the super class handle the jar files
                 super.processLibrary(libRootPath);
 
-                // get the res path. Always $PROJECT/res as well as the crunch cache.
-                // FIXME: support renamed folder.
-                PathElement element = resFolderPath.createPathElement();
+                // get the AndroidManifest.xml path.
+                // FIXME: support renamed location.
+                PathElement element = manifestFilePath.createPathElement();
+                element.setPath(libRootPath + "/" + SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+                // get the res path. $PROJECT/res as well as the crunch cache.
+                // FIXME: support renamed folders.
+                element = resFolderPath.createPathElement();
                 element.setPath(libRootPath + "/" + SdkConstants.FD_OUTPUT +
                         "/" + SdkConstants.FD_RES);
                 element = resFolderPath.createPathElement();
@@ -191,6 +205,7 @@
         // even with no libraries, always setup these so that various tasks in Ant don't complain
         // (the task themselves can handle a ref to an empty Path)
         antProject.addReference(mLibraryNativeFolderPathOut, nativeFolderPath);
+        antProject.addReference(mLibraryManifestFilePathOut, manifestFilePath);
 
         // the rest is done only if there's a library.
         if (hasLibraries) {
diff --git a/anttasks/src/com/android/ant/ManifestMergerTask.java b/anttasks/src/com/android/ant/ManifestMergerTask.java
new file mode 100644
index 0000000..1b145b0
--- /dev/null
+++ b/anttasks/src/com/android/ant/ManifestMergerTask.java
@@ -0,0 +1,143 @@
+/*
+ * 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.ant;
+
+import com.android.manifmerger.ManifestMerger;
+import com.android.sdklib.StdSdkLog;
+import com.android.sdklib.io.FileOp;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Path;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ManifestMergerTask extends SingleDependencyTask {
+
+    private String mAppManifest;
+    private String mOutManifest;
+
+    private ArrayList<Path> mLibraryPaths;
+    private boolean mDisable = true;
+
+    public void setAppManifest(Path appManifest) {
+        mAppManifest = TaskHelper.checkSinglePath("appManifest", appManifest);
+    }
+
+    public void setOutManifest(Path outManifest) {
+        mOutManifest = TaskHelper.checkSinglePath("outManifest", outManifest);
+    }
+
+    public void setDisable(boolean disable) {
+        mDisable  = disable;
+    }
+
+    /**
+     * Returns an object representing a nested <var>library</var> element.
+     */
+    public Object createLibrary() {
+        if (mLibraryPaths == null) {
+            mLibraryPaths = new ArrayList<Path>();
+        }
+
+        Path path = new Path(getProject());
+        mLibraryPaths.add(path);
+
+        return path;
+    }
+
+    @Override
+    public void execute() throws BuildException {
+        if (mAppManifest == null) {
+            throw new BuildException("Missing attribute appManifest");
+        }
+        if (mOutManifest == null) {
+            throw new BuildException("Missing attribute outManifest");
+        }
+
+        // if we merge, then get the rest of the input paths.
+        List<File> libraries = new ArrayList<File>();
+        if (mLibraryPaths != null) {
+            for (Path pathList : mLibraryPaths) {
+                for (String path : pathList.list()) {
+                    libraries.add(new File(path));
+                }
+            }
+        }
+
+        // prepare input files
+        ArrayList<File> allInputs = new ArrayList<File>(libraries.size() + 1);
+
+        // always: the input manifest.
+        File appManifestFile = new File(mAppManifest);
+        allInputs.add(appManifestFile);
+
+        // if not disabled, the libraries
+        if (mDisable == false) {
+            allInputs.addAll(libraries);
+        }
+
+        // figure out the path to the dependency file.
+        String depFile = mOutManifest + ".d";
+
+        // get InputPath with no extension restrictions
+        List<InputPath> inputPaths = getInputPaths(allInputs, null /*extensionsToCheck*/,
+                null /*factory*/);
+
+        if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) {
+            System.out.println(
+                    "No changes in the AndroidManifest files.");
+            return;
+        }
+
+        System.out.println("Merging AndroidManifest files into one.");
+
+        if (mDisable || libraries.size() == 0) {
+            if (mDisable) {
+                System.out.println("Manifest merger disabled. Using project manifest only.");
+            } else {
+                System.out.println("No libraries. Using app manifest only.");
+            }
+            // no merge (disabled or nothing to merge)? do a simple copy.
+            try {
+                new FileOp().copyFile(appManifestFile, new File(mOutManifest));
+            } catch (IOException e) {
+                throw new BuildException(e);
+            }
+        } else {
+            System.out.println(String.format("Merging manifests from project and %d libraries.",
+                    libraries.size()));
+            ManifestMerger merger = new ManifestMerger(new StdSdkLog());
+            if (merger.process(
+                    new File(mOutManifest),
+                    appManifestFile,
+                    libraries.toArray(new File[libraries.size()])) == false) {
+                throw new BuildException();
+            }
+        }
+
+        // generate the dependency file.
+        generateDependencyFile(depFile, inputPaths, mOutManifest);
+    }
+
+    @Override
+    protected String getExecTaskName() {
+        return "ManifestMerger";
+    }
+}
diff --git a/anttasks/src/com/android/ant/SingleDependencyTask.java b/anttasks/src/com/android/ant/SingleDependencyTask.java
index c179b3e..4cc8f3e 100644
--- a/anttasks/src/com/android/ant/SingleDependencyTask.java
+++ b/anttasks/src/com/android/ant/SingleDependencyTask.java
@@ -26,7 +26,7 @@
 import java.util.Set;
 
 /**
- * A base class for ant tasks that use a single dependency files to control (re)execution.
+ * A base class for ant tasks that use a single dependency file to control (re)execution.
  */
 public abstract class SingleDependencyTask extends BuildTypedTask {
 
diff --git a/build/tools.atree b/build/tools.atree
index 89401e2..16fdaf5 100644
--- a/build/tools.atree
+++ b/build/tools.atree
@@ -107,6 +107,8 @@
 framework/lint.jar               tools/lib/lint.jar
 framework/lint_api.jar           tools/lib/lint_api.jar
 framework/lint_checks.jar        tools/lib/lint_checks.jar
+framework/manifmerger.jar        tools/lib/manifmerger.jar
+
 
 # 3rd Party java libraries
 framework/commons-compress-1.0.jar                            tools/lib/commons-compress-1.0.jar
@@ -162,6 +164,5 @@
 framework/sdkuilib-tests.jar      tests/libtests/sdkuilib-tests.jar
 framework/layoutlib_api.jar       tests/libtests/layoutlib_api.jar
 #FIXME breaks build, manifmerger jar files not properly built
-#framework/manifmerger.jar         tests/libtests/manifmerger.jar
 #framework/manifmerger-tests.jar   tests/libtests/manifmerger-tests.jar
 
diff --git a/files/ant/build.xml b/files/ant/build.xml
index db47263..fb8a783 100644
--- a/files/ant/build.xml
+++ b/files/ant/build.xml
@@ -56,6 +56,9 @@
     <property name="renderscript.debug.opt.level" value="O0" />
     <property name="renderscript.release.opt.level" value="O3" />
 
+    <!-- manifest merger default value -->
+    <property name="manifestmerger.disable" value="true" />
+
     <!-- instrumentation options -->
     <property name="emma.filter" value="" />
 
@@ -100,6 +103,10 @@
             classname="com.android.ant.GetEmmaFilterTask"
             classpathref="android.antlibs" />
 
+    <taskdef name="mergemanifest"
+            classname="com.android.ant.ManifestMergerTask"
+            classpathref="android.antlibs" />
+
     <taskdef name="aapt"
             classname="com.android.ant.AaptExecTask"
             classpathref="android.antlibs" />
@@ -171,11 +178,15 @@
     <property name="jar.libs.absolute.dir" location="${jar.libs.dir}" />
     <property name="native.libs.absolute.dir" location="libs" />
 
+    <property name="manifest.file" value="AndroidManifest.xml" />
+    <property name="manifest.abs.file" location="${manifest.file}" />
+
     <!-- Output directories -->
     <property name="out.dir" value="bin" />
     <property name="out.absolute.dir" location="${out.dir}" />
     <property name="out.classes.absolute.dir" location="${out.dir}/classes" />
     <property name="out.res.absolute.dir" location="${out.dir}/res" />
+    <property name="out.manifest.abs.file" location="${out.dir}/AndroidManifest.xml" />
 
     <!-- tools location -->
     <property name="android.tools.dir" location="${sdk.dir}/tools" />
@@ -456,8 +467,44 @@
             </then>
         </if>
 
+        <!-- If the "debug" build type changed, clear out the compiled code.
+             This is to make sure the new BuildConfig.DEBUG value is picked up
+             as javac can't deal with this type of change in its dependency computation. -->
+        <if>
+            <condition>
+                <and>
+                    <length string="${build.last.is.packaging.debug}" trim="true" when="greater" length="0" />
+                    <not><equals
+                            arg1="${build.is.packaging.debug}"
+                            arg2="${build.last.is.packaging.debug}" /></not>
+                </and>
+            </condition>
+            <then>
+                <echo level="info">Switching between debug and non debug build: Deleting previous compilation output...</echo>
+                <delete dir="${out.classes.absolute.dir}" verbose="${verbose}" />
+            </then>
+            <else>
+                <!-- Else, we may still need to clean the code, for another reason.
+                     special case for instrumented: if the previous build was
+                     instrumented but not this one, clear out the compiled code -->
+                <if>
+                    <condition>
+                        <and>
+                            <istrue value="${build.last.is.instrumented}" />
+                            <isfalse value="${build.is.instrumented}" />
+                        </and>
+                    </condition>
+                    <then>
+                        <echo level="info">Switching from instrumented to non-instrumented build: Deleting previous compilation output...</echo>
+                        <delete dir="${out.classes.absolute.dir}" verbose="${verbose}" />
+                    </then>
+                </if>
+            </else>
+        </if>
+
+
         <!-- get the project manifest package -->
-        <xpath input="AndroidManifest.xml"
+        <xpath input="${manifest.abs.file}"
                 expression="/manifest/@package" output="project.app.package" />
 
     </target>
@@ -532,7 +579,7 @@
                 minSdkVersionOut="project.minSdkVersion" />
 
         <!-- Value of the hasCode attribute (Application node) extracted from manifest file -->
-        <xpath input="AndroidManifest.xml" expression="/manifest/application/@android:hasCode"
+        <xpath input="${manifest.abs.file}" expression="/manifest/application/@android:hasCode"
                     output="manifest.hasCode" default="true"/>
 
         <echo level="info">----------</echo>
@@ -551,6 +598,7 @@
         <dependency
                 libraryFolderPathOut="project.library.folder.path"
                 libraryPackagesOut="project.library.packages"
+                libraryManifestFilePathOut="project.library.manifest.file.path"
                 libraryResFolderPathOut="project.library.res.folder.path"
                 libraryNativeFolderPathOut="project.library.native.folder.path"
                 jarLibraryPathOut="project.all.jars.path"
@@ -613,42 +661,6 @@
                 <path id="tested.project.classpath" />
             </else>
         </if>
-
-
-        <!-- If the "debug" build type changed, clear out the compiled code.
-             This is to make sure the new BuildConfig.DEBUG value is picked up
-             as javac can't deal with this type of change in its dependency computation. -->
-        <if>
-            <condition>
-                <and>
-                    <length string="${build.last.is.packaging.debug}" trim="true" when="greater" length="0" />
-                    <not><equals
-                            arg1="${build.is.packaging.debug}"
-                            arg2="${build.last.is.packaging.debug}" /></not>
-                </and>
-            </condition>
-            <then>
-                <echo level="info">Switching between debug and non debug build: Deleting previous compilation output...</echo>
-                <delete dir="${out.classes.absolute.dir}" verbose="${verbose}" />
-            </then>
-            <else>
-                <!-- Else, we may still need to clean the code, for another reason.
-                     special case for instrumented: if the previous build was
-                     instrumented but not this one, clear out the compiled code -->
-                <if>
-                    <condition>
-                        <and>
-                            <istrue value="${build.last.is.instrumented}" />
-                            <isfalse value="${build.is.instrumented}" />
-                        </and>
-                    </condition>
-                    <then>
-                        <echo level="info">Switching from instrumented to non-instrumented build: Deleting previous compilation output...</echo>
-                        <delete dir="${out.classes.absolute.dir}" verbose="${verbose}" />
-                    </then>
-                </if>
-            </else>
-        </if>
     </target>
 
     <!-- empty default pre-build target. Create a similar target in
@@ -657,6 +669,14 @@
 
     <!-- Code Generation: compile resources (aapt -> R.java), aidl, renderscript -->
     <target name="-code-gen">
+        <!-- always merge manifest -->
+        <mergemanifest
+                appManifest="${manifest.abs.file}"
+                outManifest="${out.manifest.abs.file}"
+                disable="${manifestmerger.disable}">
+            <library refid="project.library.manifest.file.path" />
+        </mergemanifest>
+    
         <do-only-if-manifest-hasCode
                 elseText="hasCode = false. Skipping aidl/renderscript/R.java">
             <echo level="info">Handling aidl files...</echo>
@@ -684,7 +704,7 @@
             <aapt executable="${aapt}"
                     command="package"
                     verbose="${verbose}"
-                    manifest="AndroidManifest.xml"
+                    manifest="${out.manifest.abs.file}"
                     androidjar="${project.target.android.jar}"
                     rfolder="${gen.absolute.dir}"
                     nonConstantId="${android.library}"
@@ -922,7 +942,7 @@
                     versioncode="${version.code}"
                     versionname="${version.name}"
                     debug="${build.is.packaging.debug}"
-                    manifest="AndroidManifest.xml"
+                    manifest="${out.manifest.abs.file}"
                     assets="${asset.absolute.dir}"
                     androidjar="${project.target.android.jar}"
                     apkfolder="${out.absolute.dir}"
@@ -1094,7 +1114,7 @@
 
         <!-- release mode is only valid if the manifest does not explicitly
              set debuggable to true. default is false. -->
-        <xpath input="AndroidManifest.xml" expression="/manifest/application/@android:debuggable"
+        <xpath input="${manifest.abs.file}" expression="/manifest/application/@android:debuggable"
                 output="build.is.packaging.debug" default="false"/>
 
         <!-- signing mode: release -->
diff --git a/testapps/testProjectTest/app/AndroidManifest.xml b/testapps/testProjectTest/app/AndroidManifest.xml
index 821ae78..41e6b82 100644
--- a/testapps/testProjectTest/app/AndroidManifest.xml
+++ b/testapps/testProjectTest/app/AndroidManifest.xml
@@ -9,15 +9,6 @@
     <application
         android:icon="@drawable/ic_launcher"
         android:label="@string/app_name" >
-        <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/testProjectTest/lib/AndroidManifest.xml b/testapps/testProjectTest/lib/AndroidManifest.xml
index f8cc83d..b8bc11c 100644
--- a/testapps/testProjectTest/lib/AndroidManifest.xml
+++ b/testapps/testProjectTest/lib/AndroidManifest.xml
@@ -6,4 +6,15 @@
 
     <uses-sdk android:minSdkVersion="15" />
 
+    <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/testProjectTest/lib/res/values/strings.xml b/testapps/testProjectTest/lib/res/values/strings.xml
new file mode 100644
index 0000000..fdb2272
--- /dev/null
+++ b/testapps/testProjectTest/lib/res/values/strings.xml
@@ -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/testProjectTest/testlib/AndroidManifest.xml b/testapps/testProjectTest/testlib/AndroidManifest.xml
index acfe650..93b02e4 100644
--- a/testapps/testProjectTest/testlib/AndroidManifest.xml
+++ b/testapps/testProjectTest/testlib/AndroidManifest.xml
@@ -13,17 +13,6 @@
     -->
     <application android:label="testProjectTest-testlib">
         <uses-library android:name="android.test.runner" />
-
-        <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>
 
     <!--