From 41be2f4925cad7e2219445cf802f0d69e29a9192 Mon Sep 17 00:00:00 2001 From: Xavier Ducrohet Date: Thu, 6 Dec 2012 17:54:53 -0800 Subject: Refactor manifestmerger folder structure. Move folders around to match default gradle/maven folder structure. The custom structure confused some IDEs when importing the project as a Gradle project. Change-Id: Iac1bcbeaaf2928681707f809c3718b87f7c5334a --- manifmerger/.classpath | 3 +- manifmerger/Android.mk | 17 +- manifmerger/build.gradle | 18 +- .../src/com/android/manifmerger/ArgvParser.java | 116 -- .../src/com/android/manifmerger/ICallback.java | 37 - .../src/com/android/manifmerger/IMergerLog.java | 164 -- manifmerger/src/com/android/manifmerger/Main.java | 92 -- .../com/android/manifmerger/ManifestMerger.java | 1563 -------------------- .../src/com/android/manifmerger/MergerLog.java | 151 -- .../com/android/manifmerger/MergerXmlUtils.java | 915 ------------ .../java/com/android/manifmerger/ArgvParser.java | 116 ++ .../java/com/android/manifmerger/ICallback.java | 37 + .../java/com/android/manifmerger/IMergerLog.java | 164 ++ .../main/java/com/android/manifmerger/Main.java | 92 ++ .../com/android/manifmerger/ManifestMerger.java | 1563 ++++++++++++++++++++ .../java/com/android/manifmerger/MergerLog.java | 151 ++ .../com/android/manifmerger/MergerXmlUtils.java | 915 ++++++++++++ .../android/manifmerger/ManifestMergerTest.java | 188 +++ .../manifmerger/ManifestMergerTestCase.java | 462 ++++++ .../java/com/android/manifmerger/data/00_noop.xml | 229 +++ .../manifmerger/data/01_ignore_app_attr.xml | 69 + .../manifmerger/data/02_ignore_instrumentation.xml | 62 + .../manifmerger/data/03_inject_attributes.xml | 53 + .../manifmerger/data/04_inject_attributes.xml | 63 + .../android/manifmerger/data/10_activity_merge.xml | 378 +++++ .../android/manifmerger/data/11_activity_dup.xml | 406 +++++ .../com/android/manifmerger/data/12_alias_dup.xml | 211 +++ .../android/manifmerger/data/13_service_dup.xml | 167 +++ .../android/manifmerger/data/14_receiver_dup.xml | 186 +++ .../android/manifmerger/data/15_provider_dup.xml | 153 ++ .../com/android/manifmerger/data/16_fqcn_merge.xml | 129 ++ .../android/manifmerger/data/17_fqcn_conflict.xml | 120 ++ .../android/manifmerger/data/20_uses_lib_merge.xml | 176 +++ .../manifmerger/data/21_uses_lib_errors.xml | 202 +++ .../manifmerger/data/25_permission_merge.xml | 259 ++++ .../android/manifmerger/data/26_permission_dup.xml | 314 ++++ .../manifmerger/data/28_uses_perm_merge.xml | 156 ++ .../android/manifmerger/data/30_uses_sdk_ok.xml | 86 ++ .../manifmerger/data/32_uses_sdk_minsdk_ok.xml | 70 + .../data/33_uses_sdk_minsdk_conflict.xml | 148 ++ .../data/36_uses_sdk_targetsdk_warning.xml | 77 + .../manifmerger/data/40_uses_feat_merge.xml | 178 +++ .../manifmerger/data/41_uses_feat_errors.xml | 205 +++ .../manifmerger/data/45_uses_feat_gles_once.xml | 126 ++ .../data/47_uses_feat_gles_conflict.xml | 162 ++ .../manifmerger/data/50_uses_conf_warning.xml | 162 ++ .../data/52_support_screens_warning.xml | 162 ++ .../manifmerger/data/54_compat_screens_warning.xml | 204 +++ .../manifmerger/data/56_support_gltext_warning.xml | 151 ++ .../android/manifmerger/data/60_merge_order.xml | 318 ++++ .../android/manifmerger/data/65_override_app.xml | 197 +++ .../com/android/manifmerger/data/66_remove_app.xml | 53 + .../manifmerger/data/67_override_activities.xml | 159 ++ .../android/manifmerger/data/68_override_uses.xml | 205 +++ .../android/manifmerger/data/69_remove_uses.xml | 177 +++ .../android/manifmerger/data/70_expand_fqcns.xml | 84 ++ .../data/71_prefixes_enable_extractprefix.xml | 84 ++ manifmerger/tests/Android.mk | 28 - .../android/manifmerger/ManifestMergerTest.java | 188 --- .../manifmerger/ManifestMergerTestCase.java | 462 ------ .../src/com/android/manifmerger/data/00_noop.xml | 229 --- .../manifmerger/data/01_ignore_app_attr.xml | 69 - .../manifmerger/data/02_ignore_instrumentation.xml | 62 - .../manifmerger/data/03_inject_attributes.xml | 53 - .../manifmerger/data/04_inject_attributes.xml | 63 - .../android/manifmerger/data/10_activity_merge.xml | 378 ----- .../android/manifmerger/data/11_activity_dup.xml | 406 ----- .../com/android/manifmerger/data/12_alias_dup.xml | 211 --- .../android/manifmerger/data/13_service_dup.xml | 167 --- .../android/manifmerger/data/14_receiver_dup.xml | 186 --- .../android/manifmerger/data/15_provider_dup.xml | 153 -- .../com/android/manifmerger/data/16_fqcn_merge.xml | 129 -- .../android/manifmerger/data/17_fqcn_conflict.xml | 120 -- .../android/manifmerger/data/20_uses_lib_merge.xml | 176 --- .../manifmerger/data/21_uses_lib_errors.xml | 202 --- .../manifmerger/data/25_permission_merge.xml | 259 ---- .../android/manifmerger/data/26_permission_dup.xml | 314 ---- .../manifmerger/data/28_uses_perm_merge.xml | 156 -- .../android/manifmerger/data/30_uses_sdk_ok.xml | 86 -- .../manifmerger/data/32_uses_sdk_minsdk_ok.xml | 70 - .../data/33_uses_sdk_minsdk_conflict.xml | 148 -- .../data/36_uses_sdk_targetsdk_warning.xml | 77 - .../manifmerger/data/40_uses_feat_merge.xml | 178 --- .../manifmerger/data/41_uses_feat_errors.xml | 205 --- .../manifmerger/data/45_uses_feat_gles_once.xml | 126 -- .../data/47_uses_feat_gles_conflict.xml | 162 -- .../manifmerger/data/50_uses_conf_warning.xml | 162 -- .../data/52_support_screens_warning.xml | 162 -- .../manifmerger/data/54_compat_screens_warning.xml | 204 --- .../manifmerger/data/56_support_gltext_warning.xml | 151 -- .../android/manifmerger/data/60_merge_order.xml | 318 ---- .../android/manifmerger/data/65_override_app.xml | 197 --- .../com/android/manifmerger/data/66_remove_app.xml | 53 - .../manifmerger/data/67_override_activities.xml | 159 -- .../android/manifmerger/data/68_override_uses.xml | 205 --- .../android/manifmerger/data/69_remove_uses.xml | 177 --- .../android/manifmerger/data/70_expand_fqcns.xml | 84 -- .../data/71_prefixes_enable_extractprefix.xml | 84 -- 98 files changed, 10047 insertions(+), 10077 deletions(-) delete mode 100755 manifmerger/src/com/android/manifmerger/ArgvParser.java delete mode 100755 manifmerger/src/com/android/manifmerger/ICallback.java delete mode 100755 manifmerger/src/com/android/manifmerger/IMergerLog.java delete mode 100644 manifmerger/src/com/android/manifmerger/Main.java delete mode 100755 manifmerger/src/com/android/manifmerger/ManifestMerger.java delete mode 100755 manifmerger/src/com/android/manifmerger/MergerLog.java delete mode 100755 manifmerger/src/com/android/manifmerger/MergerXmlUtils.java create mode 100755 manifmerger/src/main/java/com/android/manifmerger/ArgvParser.java create mode 100755 manifmerger/src/main/java/com/android/manifmerger/ICallback.java create mode 100755 manifmerger/src/main/java/com/android/manifmerger/IMergerLog.java create mode 100644 manifmerger/src/main/java/com/android/manifmerger/Main.java create mode 100755 manifmerger/src/main/java/com/android/manifmerger/ManifestMerger.java create mode 100755 manifmerger/src/main/java/com/android/manifmerger/MergerLog.java create mode 100755 manifmerger/src/main/java/com/android/manifmerger/MergerXmlUtils.java create mode 100755 manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTest.java create mode 100755 manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTestCase.java create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/00_noop.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/13_service_dup.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/60_merge_order.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/65_override_app.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/66_remove_app.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/67_override_activities.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/68_override_uses.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml create mode 100755 manifmerger/src/test/java/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml delete mode 100755 manifmerger/tests/Android.mk delete mode 100755 manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java delete mode 100755 manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/00_noop.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/03_inject_attributes.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/04_inject_attributes.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/16_fqcn_merge.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/17_fqcn_conflict.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/65_override_app.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/66_remove_app.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/67_override_activities.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/68_override_uses.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/69_remove_uses.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/70_expand_fqcns.xml delete mode 100755 manifmerger/tests/src/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml (limited to 'manifmerger') diff --git a/manifmerger/.classpath b/manifmerger/.classpath index d042f23..c0005b9 100644 --- a/manifmerger/.classpath +++ b/manifmerger/.classpath @@ -1,7 +1,6 @@ - - + diff --git a/manifmerger/Android.mk b/manifmerger/Android.mk index 093d5b3..28dbe82 100644 --- a/manifmerger/Android.mk +++ b/manifmerger/Android.mk @@ -3,10 +3,9 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java) LOCAL_JAVA_RESOURCE_DIRS := -LOCAL_SRC_FILES := $(call all-java-files-under,src) - LOCAL_JAR_MANIFEST := etc/manifest.txt LOCAL_JAVA_LIBRARIES := \ common \ @@ -15,3 +14,17 @@ LOCAL_MODULE := manifmerger LOCAL_MODULE_TAGS := optional include $(BUILD_HOST_JAVA_LIBRARY) + +# Build tests +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src/test/java) +LOCAL_JAVA_RESOURCE_DIRS := src/test/java + +LOCAL_MODULE := manifmerger-tests +LOCAL_MODULE_TAGS := optional + +LOCAL_JAVA_LIBRARIES := manifmerger sdklib-tests junit + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/manifmerger/build.gradle b/manifmerger/build.gradle index 1db9c04..9e9bef2 100644 --- a/manifmerger/build.gradle +++ b/manifmerger/build.gradle @@ -20,22 +20,8 @@ group = 'com.android.tools.build' archivesBaseName = 'manifest-merger' sourceSets { - main { - java { - srcDir 'src' - } - resources { - srcDir 'src' - } - } - test { - java { - srcDir 'tests/src' - } - resources { - srcDir 'tests/src' - } - } + main.resources.srcDir 'src/main/java' + test.resources.srcDir 'src/test/java' } uploadArchives { diff --git a/manifmerger/src/com/android/manifmerger/ArgvParser.java b/manifmerger/src/com/android/manifmerger/ArgvParser.java deleted file mode 100755 index 6d22f57..0000000 --- a/manifmerger/src/com/android/manifmerger/ArgvParser.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.manifmerger; - -import com.android.sdklib.util.CommandLineParser; -import com.android.utils.ILogger; - -import java.util.List; - - -/** - * Specific command-line flags for the {@link ManifestMerger}. - */ -class ArgvParser extends CommandLineParser { - - /* - * Steps needed to add a new action: - * - Each action is defined as a "verb object" followed by parameters. - * - Either reuse a VERB_ constant or define a new one. - * - Either reuse an OBJECT_ constant or define a new one. - * - Add a new entry to mAction with a one-line help summary. - * - In the constructor, add a define() call for each parameter (either mandatory - * or optional) for the given action. - */ - - public final static String VERB_MERGE = "merge"; //$NON-NLS-1$ - public static final String KEY_OUT = "out"; //$NON-NLS-1$ - public static final String KEY_MAIN = "main"; //$NON-NLS-1$ - public static final String KEY_LIBS = "libs"; //$NON-NLS-1$ - - /** - * Action definitions for ManifestMerger command line. - *

- * This list serves two purposes: first it is used to know which verb/object - * actions are acceptable on the command-line; second it provides a summary - * for each action that is printed in the help. - *

- * Each entry is a string array with: - *

    - *
  • the verb. - *
  • an object (use #NO_VERB_OBJECT if there's no object). - *
  • a description. - *
  • an alternate form for the object (e.g. plural). - *
- */ - private final static String[][] ACTIONS = { - - { VERB_MERGE, NO_VERB_OBJECT, - "Merge two or more manifests." }, - - }; - - public ArgvParser(ILogger logger) { - super(logger, ACTIONS); - - // The following defines the parameters of the actions defined in mAction. - - // --- merge manifest --- - - define(Mode.STRING, true, - VERB_MERGE, NO_VERB_OBJECT, "o", KEY_OUT, //$NON-NLS-1$ - "Output path (where to write the merged manifest). Use - for stdout.", null); - - define(Mode.STRING, true, - VERB_MERGE, NO_VERB_OBJECT, "1", KEY_MAIN, //$NON-NLS-1$ - "Path of the main manifest (what to merge *into*)", null); - - define(Mode.STRING_ARRAY, true, - VERB_MERGE, NO_VERB_OBJECT, "2", KEY_LIBS, //$NON-NLS-1$ - "Paths of library manifests to be merged into the main one.", - null); - } - - @Override - public boolean acceptLackOfVerb() { - return true; - } - - // -- some helpers for generic action flags - - /** Helper to retrieve the --out value. */ - public String getParamOut() { - return (String) getValue(null, null, KEY_OUT); - } - - /** Helper to retrieve the --main value. */ - public String getParamMain() { - return (String) getValue(null, null, KEY_MAIN); - } - - /** - * Helper to retrieve the --libs values. - */ - public String[] getParamLibs() { - Object v = getValue(null, null, KEY_LIBS); - if (v instanceof List) { - List a = (List) v; - return a.toArray(new String[a.size()]); - } - return null; - } -} diff --git a/manifmerger/src/com/android/manifmerger/ICallback.java b/manifmerger/src/com/android/manifmerger/ICallback.java deleted file mode 100755 index 26ae40d..0000000 --- a/manifmerger/src/com/android/manifmerger/ICallback.java +++ /dev/null @@ -1,37 +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.manifmerger; - -import com.android.annotations.NonNull; - -/** - * Callback used by the ManifestMerger to query the caller. - */ -public interface ICallback { - - public static final int UNKNOWN_CODENAME = 0; - - /** - * Queries the caller to find the API level for a given provisional API codename, - * as used in the <uses-sdk> {@code minSdkVersion} field. - * - * @param codename A non-null codename string. - * @return The integer API > 0 for the given codename, or {@link #UNKNOWN_CODENAME}. - */ - public int queryCodenameApiLevel(@NonNull String codename); - -} diff --git a/manifmerger/src/com/android/manifmerger/IMergerLog.java b/manifmerger/src/com/android/manifmerger/IMergerLog.java deleted file mode 100755 index 5402dd4..0000000 --- a/manifmerger/src/com/android/manifmerger/IMergerLog.java +++ /dev/null @@ -1,164 +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.manifmerger; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; - - -/** - * Logger interface for the {@link ManifestMerger}. - */ -public interface IMergerLog { - - /** Severity of the error message. */ - public enum Severity { - /** - * A very low severity information. This does not stop processing. - * Clients might want to have a "not verbose" flag to not display this. - */ - INFO, - /** - * A warning. This does not stop processing. - * */ - WARNING, - /** - * A fatal error. - * The merger does not stop on errors, in an attempt to accumulate as much - * info as possible to return to the user. However in case even one error - * is generated the output should not be used, if any. - */ - ERROR - } - - /** - * Logs an error that occurred at a specific single manifest. - * - * @param severity Whether this is an actual error or a mere warning. - * @param location A file and line location of where the error was detected. - * @param message A message string, suitable for {@link String#format(String, Object...)}. - * @param msgParams The optional parameters for the {@code message} string. - */ - public abstract void error( - @NonNull Severity severity, - @NonNull FileAndLine location, - @NonNull String message, - Object...msgParams); - - /** - * Logs a conflict, that is an error that happens when comparing 2 manifests. - * - * @param severity Whether this is an actual error or a mere warning. - * @param location1 A file and line location of where the error was detected. - * By convention, location1 is generally the main manifest location. - * @param location2 A file and line location of where the error was detected. - * By convention, location2 is generally a library location. - * @param message A message string, suitable for {@link String#format(String, Object...)}. - * @param msgParams The optional parameters for the {@code message} string. - */ - public abstract void conflict( - @NonNull Severity severity, - @NonNull FileAndLine location1, - @NonNull FileAndLine location2, - @NonNull String message, - Object...msgParams); - - /** - * Information about the file and line number where an error ocurred. - */ - public static class FileAndLine { - private final String mFilePath; - private final int mLine; - - /** - * Constructs a new {@link FileAndLine}. - * - * @param filePath The file path. This is typically a file path when the - * merge is initiated via the {@code process(File...)} interface. - * When using the {@code process(Document...)} interface, this will be - * one of the magic constants {@link IMergerLog#LIBRARY} or - * {@link IMergerLog#MAIN_MANIFEST}. When that fails, null is used. - * @param line The line number where the error occurred in the XML file. - * Zero is used when the line number isn't known (e.g. when the error - * happens at the document level rather than on a specific element.) - */ - public FileAndLine(@Nullable String filePath, int line) { - mFilePath = filePath; - mLine = line; - } - - /** - * Returns the file path. - *

- * This is typically a file path when the merge is initiated via the - * {@code process(File...)} interface. - * When using the {@code process(Document...)} interface, this will be - * one of the magic constants {@link IMergerLog#LIBRARY} or - * {@link IMergerLog#MAIN_MANIFEST}. - * When that fails, null is used. - */ - public @Nullable String getFileName() { - return mFilePath; - } - - /** - * Returns the line number where the error occurred in the XML file. - * Zero is used when the line number isn't known (e.g. when the error - * happens at the document level rather than on a specific element.) - */ - public int getLine() { - return mLine; - } - - /** - * Displays the information in the form "file:line". - */ - @Override - public String toString() { - String name = mFilePath; - if (MAIN_MANIFEST.equals(name)) { - name = "main manifest"; // translatable - } else if (LIBRARY.equals(name)) { - name = "library"; // translatable - } else if (name == null) { - name = "(Unknown)"; // translatable - } - if (mLine <= 0) { - return name; - } else { - return name + ':' + mLine; - } - } - } - - /** - * The reference to the "main manifest" used in {@link FileAndLine} when the - * path to the main manifest file isn't known. This happens when the - * {@link ManifestMerger} is called with the {@code process(Document...)} - * interface. - */ - public static final String MAIN_MANIFEST = "@main"; //$NON-NLS-1$ - - /** - * The reference to "a library" used in {@link FileAndLine} when the - * path to the library file isn't known. This happens when the - * {@link ManifestMerger} is called with the {@code process(Document...)} - * interface. - */ - public static final String LIBRARY = "@library"; //$NON-NLS-1$ - -} diff --git a/manifmerger/src/com/android/manifmerger/Main.java b/manifmerger/src/com/android/manifmerger/Main.java deleted file mode 100644 index 2a6460e..0000000 --- a/manifmerger/src/com/android/manifmerger/Main.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.manifmerger; - -import com.android.utils.ILogger; -import com.android.utils.StdLogger; - -import java.io.File; -import java.util.Map; - -/** - * Command-line entry point of the Manifest Merger. - * The goal of the manifest merger is to merge library manifest into a main application manifest. - * See {@link ManifestMerger} for the exact merging rules. - *

- * The command-line version creates a {@link ManifestMerger} - * which takes file arguments from the command-line and dumps all errors and warnings on the - * stdout/stderr console. - *

- * Usage:
- * {@code $ manifmerger merge --main main_manifest.xml --libs lib1.xml lib2.xml --out result.xml} - *

- * When used as a library, please call {@link ManifestMerger#process(File, File, File[], Map)} - * directly. - */ -public class Main { - - /** Logger object. Use this to print normal output, warnings or errors. Never null. */ - private ILogger mSdkLog; - /** Command line parser. Never null. */ - private ArgvParser mArgvParser; - - public static void main(String[] args) { - new Main().run(args); - } - - /** - * Runs the sdk manager app - */ - private void run(String[] args) { - createLogger(); - - mArgvParser = new ArgvParser(mSdkLog); - mArgvParser.parseArgs(args); - - // Create a new ManifestMerger and call its process method. - // It will take care of validating its own arguments. - ManifestMerger mm = new ManifestMerger(MergerLog.wrapSdkLog(mSdkLog), null); - - String[] libPaths = mArgvParser.getParamLibs(); - File[] libFiles = new File[libPaths.length]; - for (int n = libPaths.length - 1; n >= 0; n--) { - libFiles[n] = new File(libPaths[n]); - } - - boolean ok = mm.process( - new File(mArgvParser.getParamOut()), - new File(mArgvParser.getParamMain()), - libFiles, - null /*injectAttributes*/ - ); - System.exit(ok ? 0 : 1); - } - - /** - * Creates the {@link #mSdkLog} object. - * This logger prints to the attached console. - */ - private void createLogger() { - mSdkLog = new StdLogger(StdLogger.Level.VERBOSE); - } - - /** For testing */ - public void setLogger(ILogger logger) { - mSdkLog = logger; - } - -} diff --git a/manifmerger/src/com/android/manifmerger/ManifestMerger.java b/manifmerger/src/com/android/manifmerger/ManifestMerger.java deleted file mode 100755 index 073c768..0000000 --- a/manifmerger/src/com/android/manifmerger/ManifestMerger.java +++ /dev/null @@ -1,1563 +0,0 @@ -/* - * 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.manifmerger; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.manifmerger.IMergerLog.FileAndLine; -import com.android.manifmerger.IMergerLog.Severity; -import com.android.utils.XmlUtils; -import com.android.xml.AndroidXPathFactory; - -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; - -/** - * Merges a library manifest into a main application manifest. - *

- * To use, create with {@link ManifestMerger#ManifestMerger(IMergerLog, ICallback)} then - * call {@link ManifestMerger#process(File, File, File[], Map)}. - *

- *

 Merge operations:
- * - root manifest: attributes ignored, warn if defined.
- * - application:
- *      G- {@code @attributes}: most attributes are ignored in libs
- *          except: application:name if defined, it must match.
- *          except: application:agentBackup if defined, it must match.
- *          (these represent class names and we don't want a lib to assume their app or backup
- *           classes are being used when that will never be the case.)
- *      C- activity / activity-alias / service / receiver / provider
- *          => Merge as-is. Error if exists in the destination (same {@code @name})
- *             unless the definitions are exactly the same.
- *             New elements are always merged at the end of the application element.
- *          => Indicate if there's a dup.
- *      D- uses-library
- *          => Merge. OK if already exists same {@code @name}.
- *          => Merge {@code @required}: true>false.
- * A- instrumentation:
- *      => Do not merge. ignore the ones from libs.
- * C- permission / permission-group / permission-tree:
- *      => Merge as-is. Error if exists in the destination (same {@code @name})
- *         unless the definitions are exactly the same.
- * C- uses-permission:
- *      => Add. OK if already defined.
- * E- uses-sdk:
- *      {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
- *                              Codenames are accepted if we can resolve their API level.
- *      {@code @targetSdkVersion}: warning if dest<lib.
- *                                 Never automatically change dest targetsdk.
- *      {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
- * D- uses-feature with {@code @name}:
- *      => Merge with same {@code @name}
- *      => Merge {@code @required}: true>false.
- *      - Do not merge any {@code @glEsVersion} attribute at this point.
- * F- uses-feature with {@code @glEsVersion}:
- *      => Error if defined in lib+dest with dest<lib. Never automatically change dest.
- * B- uses-configuration:
- *      => There can be many. Error if source defines one that is not an exact match in dest.
- *      (e.g. right now app must manually define something that matches exactly each lib)
- * B- supports-screens / compatible-screens:
- *      => Do not merge.
- *      => Error (warn?) if defined in lib and not strictly the same as in dest.
- * B- supports-gl-texture:
- *      => Do not merge. Can have more than one.
- *      => Error (warn?) if defined in lib and not present as-is in dest.
- *
- * Strategies:
- * A = Ignore, do not merge (no-op).
- * B = Do not merge but if defined in both must match equally.
- * C = Must not exist in dest or be exactly the same (key is the {@code @name} attribute).
- * D = Add new or merge with same key {@code @name}, adjust {@code @required} true>false.
- * E, F, G = Custom strategies; see above.
- *
- * What happens when merging libraries with conflicting information?
- * Say for example a main manifest has a minSdkVersion of 3, whereas libraries have
- * a minSdkVersion of 4 and 11. We could have 2 point of views:
- * - Play it safe: If we have a library with a minSdkVersion of 11, it means this
- *   library code knows it can't work reliably on a lower API level. So the safest end
- *   result would be a merged manifest with the highest minSdkVersion of all libraries.
- * - Trust the main manifest: When an app declares a given minSdkVersion, it also expects
- *   to run a given range of devices. If we change the final minSdkVersion, the app won't
- *   be available on as many devices as the developer might expect. And as a counterpoint
- *   to issue 1, the app may be careful and not call the library without checking the
- *   necessary features or APIs are available before hand.
- * Both points of views are conflicting. The solution taken here is to be conservative
- * and generate an error rather than merge and change a value that might be surprising.
- * On the other hand this can be problematic and force a developer to keep the main
- * manifest in sync with the libraries ones, in essence reducing the usefulness of the
- * automated merge to pure trivial cases. The idea is to just start this way and enhance
- * or revisit the mechanism later.
- * 
- */ -public class ManifestMerger { - - /** Logger object. Never null. */ - private final IMergerLog mLog; - /** An optional callback that the merger can use to query the calling SDK. */ - private final ICallback mCallback; - private XPath mXPath; - private Document mMainDoc; - /** Option to extract our package prefixes in the merged manifest */ - private boolean mExtractPackagePrefix; - - /** Namespace for Android attributes in an AndroidManifest.xml */ - private static final String NS_URI = SdkConstants.NS_RESOURCES; - /** Prefix for the Android namespace to use in XPath expressions. */ - private static final String NS_PREFIX = AndroidXPathFactory.DEFAULT_NS_PREFIX; - /** Namespace used in XML files for Android Tooling attributes */ - private static final String TOOLS_URI = SdkConstants.TOOLS_URI; - /** The name of the tool:merge attribute, to either override or ignore merges. */ - private static final String MERGE_ATTR = "merge"; //$NON-NLS-1$ - /** tool:merge="override" means to ignore what comes from libraries and only keep the - * version from the main manifest. No conflict can be generated. */ - private static final String MERGE_OVERRIDE = "override"; //$NON-NLS-1$ - /** tool:merge="remove" means to remove a node and prevent merging -- not only is the - * node from the libraries not merged, but the element is removed from the main manifest. */ - private static final String MERGE_REMOVE = "remove"; //$NON-NLS-1$ - - /** - * Sets of element/attribute that need to be treated as class names. - * The attribute name must be the local name for the Android namespace. - * For example "application/name" maps to <application android:name=...>. - */ - private static final String[] sClassAttributes = { - "application/name", - "application/backupAgent", - "activity/name", - "activity-alias/name", - "receiver/name", - "service/name", - "provider/name", - "instrumentation/name" - }; - - /** - * Creates a new {@link ManifestMerger}. - * - * @param log A non-null merger log to capture all warnings, errors and their location. - * @param callback An optional callback that the merger can use to query the calling SDK. - */ - public ManifestMerger(@NonNull IMergerLog log, @Nullable ICallback callback) { - mLog = log; - mCallback = callback; - } - - /** - * Sets whether the manifest merger should extract package prefixes - * - * @param extract if true, extract package prefixes - * @return this, for constructor chaining - */ - public ManifestMerger setExtractPackagePrefix(boolean extract) { - mExtractPackagePrefix = extract; - return this; - } - - /** - * Performs the merge operation. - *

- * This does NOT stop on errors, in an attempt to accumulate as much - * info as possible to return to the user. - * Unless it failed to read the main manifest, a result file will be - * created. However if process() returns false, the file should not - * be used except for debugging purposes. - * - * @param outputFile The output path to generate. Can be the same as the main path. - * @param mainFile The main manifest paths to read. What we merge into. - * @param libraryFiles The library manifest paths to read. Must not be null. - * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value. - * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name", - * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion". - * (note the space separator between the attribute URI and its local name.) - * The elements will be created if they don't exists. Existing attributes will be modified. - * The replacement is done on the main document before merging. - * @return True if the merge was completed, false otherwise. - */ - public boolean process( - File outputFile, - File mainFile, - File[] libraryFiles, - Map injectAttributes) { - Document mainDoc = MergerXmlUtils.parseDocument(mainFile, mLog); - if (mainDoc == null) { - return false; - } - - boolean success = process(mainDoc, libraryFiles, injectAttributes); - - if (!MergerXmlUtils.printXmlFile(mainDoc, outputFile, mLog)) { - success = false; - } - return success; - } - - /** - * Performs the merge operation in-place in the given DOM. - *

- * This does NOT stop on errors, in an attempt to accumulate as much - * info as possible to return to the user. - *

- * The method might modify the input XML document in-place for its own processing. - * - * @param mainDoc The document to merge into. Will be modified in-place. - * @param libraryFiles The library manifest paths to read. Must not be null. - * These will be modified in-place. - * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value. - * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name", - * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion". - * (note the space separator between the attribute URI and its local name.) - * The elements will be created if they don't exists. Existing attributes will be modified. - * The replacement is done on the main document before merging. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - public boolean process( - Document mainDoc, - File[] libraryFiles, - Map injectAttributes) { - - boolean success = true; - mMainDoc = mainDoc; - MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST); - MergerXmlUtils.injectAttributes(mainDoc, injectAttributes, mLog); - - String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES); - mXPath = AndroidXPathFactory.newXPath(prefix); - - expandFqcns(mainDoc); - for (File libFile : libraryFiles) { - Document libDoc = MergerXmlUtils.parseDocument(libFile, mLog); - if (libDoc == null || !mergeLibDoc(cleanupToolsAttributes(libDoc))) { - success = false; - } - } - - cleanupToolsAttributes(mainDoc); - - if (mExtractPackagePrefix) { - extractFqcns(mainDoc); - } - - mXPath = null; - mMainDoc = null; - return success; - } - - /** - * Performs the merge operation in-place in the given DOM. - *

- * This does NOT stop on errors, in an attempt to accumulate as much - * info as possible to return to the user. - *

- * The method might modify the input XML documents in-place for its own processing. - * - * @param mainDoc The document to merge into. Will be modified in-place. - * @param libraryDocs The library manifest documents to merge in. Must not be null. - * These will be modified in-place. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - public boolean process(@NonNull Document mainDoc, @NonNull Document... libraryDocs) { - - boolean success = true; - mMainDoc = mainDoc; - MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST); - - String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES); - mXPath = AndroidXPathFactory.newXPath(prefix); - - expandFqcns(mainDoc); - for (Document libDoc : libraryDocs) { - MergerXmlUtils.decorateDocument(libDoc, IMergerLog.LIBRARY); - if (!mergeLibDoc(cleanupToolsAttributes(libDoc))) { - success = false; - } - } - - cleanupToolsAttributes(mainDoc); - mXPath = null; - mMainDoc = null; - return success; - } - - // -------- - - /** - * Merges the given library manifest into the destination manifest. - * See {@link ManifestMerger} for merge details. - * - * @param libDoc The library document to merge from. Must not be null. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - private boolean mergeLibDoc(Document libDoc) { - - boolean err = false; - - expandFqcns(libDoc); - - // Strategy G (check is compatible) - err |= !checkApplication(libDoc); - - // Strategy B - err |= !doNotMergeCheckEqual("/manifest/uses-configuration", libDoc); //$NON-NLS-1$ - err |= !doNotMergeCheckEqual("/manifest/supports-screens", libDoc); //$NON-NLS-1$ - err |= !doNotMergeCheckEqual("/manifest/compatible-screens", libDoc); //$NON-NLS-1$ - err |= !doNotMergeCheckEqual("/manifest/supports-gl-texture", libDoc); //$NON-NLS-1$ - - boolean skipApplication = hasOverrideOrRemoveTag( - findFirstElement(mMainDoc, "/manifest/application")); //$NON-NLS-1$ - - // Strategy C - if (!skipApplication) { - err |= !mergeNewOrEqual( - "/manifest/application/activity", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - true); - err |= !mergeNewOrEqual( - "/manifest/application/activity-alias", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - true); - err |= !mergeNewOrEqual( - "/manifest/application/service", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - true); - err |= !mergeNewOrEqual( - "/manifest/application/receiver", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - true); - err |= !mergeNewOrEqual( - "/manifest/application/provider", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - true); - } - err |= !mergeNewOrEqual( - "/manifest/permission", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - false); - err |= !mergeNewOrEqual( - "/manifest/permission-group", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - false); - err |= !mergeNewOrEqual( - "/manifest/permission-tree", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - false); - err |= !mergeNewOrEqual( - "/manifest/uses-permission", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - libDoc, - false); - - // Strategy D - if (!skipApplication) { - err |= !mergeAdjustRequired( - "/manifest/application/uses-library", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - "required", //$NON-NLS-1$ - libDoc, - null /*alternateKeyAttr*/); - } - err |= !mergeAdjustRequired( - "/manifest/uses-feature", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - "required", //$NON-NLS-1$ - libDoc, - "glEsVersion" /*alternateKeyAttr*/); - - // Strategy E - err |= !checkSdkVersion(libDoc); - - // Strategy F - err |= !checkGlEsVersion(libDoc); - - return !err; - } - - /** - * Expand all possible class names attributes in the given document. - *

- * Some manifest attributes represent class names. These can be specified as fully - * qualified class names or use a short notation consisting of just the terminal - * class simple name or a dot followed by a partial class name. Unfortunately this - * makes textual comparison of the attributes impossible. To simplify this, we can - * modify the document to fully expand all these class names. The list of elements - * and attributes to process is listed by {@link #sClassAttributes} and the expansion - * simply consists of appending the manifest' package if defined. - * - * @param doc The document in which to expand potential FQCNs. - */ - private void expandFqcns(Document doc) { - // Find the package attribute of the manifest. - String pkg = null; - Element manifest = findFirstElement(doc, "/manifest"); - if (manifest != null) { - pkg = manifest.getAttribute("package"); - } - - if (pkg == null || pkg.length() == 0) { - // We can't adjust FQCNs if we don't know the root package name. - // It's not a proper manifest if this is missing anyway. - assert manifest != null; - mLog.error(Severity.WARNING, - xmlFileAndLine(manifest), - "Missing 'package' attribute in manifest."); - return; - } - - for (String elementAttr : sClassAttributes) { - String[] names = elementAttr.split("/"); - if (names.length != 2) { - continue; - } - String elemName = names[0]; - String attrName = names[1]; - NodeList elements = doc.getElementsByTagName(elemName); - for (int i = 0; i < elements.getLength(); i++) { - Node elem = elements.item(i); - if (elem instanceof Element) { - Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName); - if (attr != null) { - String value = attr.getNodeValue(); - - // We know it's a shortened FQCN if it starts with a dot - // or does not contain any dot. - if (value != null && value.length() > 0 && - (value.indexOf('.') == -1 || value.charAt(0) == '.')) { - if (value.charAt(0) == '.') { - value = pkg + value; - } else { - value = pkg + '.' + value; - } - attr.setNodeValue(value); - } - } - } - } - } - } - - /** - * Extracts the fully qualified class names from the manifest and uses the - * prefix notation relative to the manifest package. This basically reverses - * the effects of {@link #expandFqcns(Document)}, though of course it may - * also remove prefixes which were inlined in the original documents. - * - * @param doc the document in which to extract the FQCNs. - */ - private void extractFqcns(Document doc) { - // Find the package attribute of the manifest. - String pkg = null; - Element manifest = findFirstElement(doc, "/manifest"); - if (manifest != null) { - pkg = manifest.getAttribute("package"); - } - - if (pkg == null || pkg.length() == 0) { - return; - } - - int pkgLength = pkg.length(); - for (String elementAttr : sClassAttributes) { - String[] names = elementAttr.split("/"); - if (names.length != 2) { - continue; - } - String elemName = names[0]; - String attrName = names[1]; - NodeList elements = doc.getElementsByTagName(elemName); - for (int i = 0; i < elements.getLength(); i++) { - Node elem = elements.item(i); - if (elem instanceof Element) { - Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName); - if (attr != null) { - String value = attr.getNodeValue(); - - // We know it's a shortened FQCN if it starts with a dot - // or does not contain any dot. - if (value != null && value.length() > pkgLength && - value.startsWith(pkg) && value.charAt(pkgLength) == '.') { - value = value.substring(pkgLength); - attr.setNodeValue(value); - } - } - } - } - } - } - - /** - * Checks (but does not merge) the application attributes using the following rules: - *

-     * - {@code @name}: Ignore if empty. Warning if its expanded FQCN doesn't match the main doc.
-     * - {@code @backupAgent}:  Ignore if empty. Warning if its expanded FQCN doesn't match main doc.
-     * - All other attributes are ignored.
-     * 
- * The name and backupAgent represent classes and the merger will warn since if a lib has - * these defined they will never be used anyway. - * @param libDoc The library document to merge from. Must not be null. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - private boolean checkApplication(Document libDoc) { - - Element mainApp = findFirstElement(mMainDoc, "/manifest/application"); //$NON-NLS-1$ - Element libApp = findFirstElement(libDoc, "/manifest/application"); //$NON-NLS-1$ - - // A manifest does not necessarily define an application. - // If the lib has none, there's nothing to check for. - if (libApp == null) { - return true; - } - if (hasOverrideOrRemoveTag(mainApp)) { - // Don't check the element since it is tagged with override or remove. - return true; - } - - for (String attrName : new String[] { "name", "backupAgent" }) { - String libValue = getAttributeValue(libApp, attrName); - if (libValue == null || libValue.length() == 0) { - // Nothing to do if the attribute is not defined in the lib. - continue; - } - // The main doc does not have to have an application node. - String mainValue = mainApp == null ? "" : getAttributeValue(mainApp, attrName); - if (!libValue.equals(mainValue)) { - assert mainApp != null; - mLog.conflict(Severity.WARNING, - xmlFileAndLine(mainApp), - xmlFileAndLine(libApp), - mainApp == null ? - "Library has but main manifest has no application element." : - "Main manifest has but library uses %1$s='%3$s'.", - attrName, - mainValue, - libValue); - } - } - - return true; - } - - /** - * Do not merge anything. Instead it checks that the requested elements from the - * given library are all present and equal in the destination and prints a warning - * if it's not the case. - *

- * For example if a library supports a given screen configuration, print a - * warning if the main manifest doesn't indicate the app supports the same configuration. - * We should not merge it since we don't want to silently give the impression an app - * supports a configuration just because it uses a library which does. - * On the other hand we don't want to silently ignore this fact. - *

- * TODO there should be a way to silence this warning. - * The current behavior is certainly arbitrary and needs to be tweaked somehow. - * - * @param path The XPath of the elements to merge from the library. Must not be null. - * @param libDoc The library document to merge from. Must not be null. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - private boolean doNotMergeCheckEqual(String path, Document libDoc) { - - for (Element src : findElements(libDoc, path)) { - - boolean found = false; - - for (Element dest : findElements(mMainDoc, path)) { - if (hasOverrideOrRemoveTag(dest)) { - continue; - } - if (compareElements(dest, src, false, null /*diff*/, null /*keyAttr*/)) { - found = true; - break; - } - } - - if (!found) { - mLog.conflict(Severity.WARNING, - xmlFileAndLine(mMainDoc), - xmlFileAndLine(src), - "%1$s defined in library, missing from main manifest:\n%2$s", - path, - MergerXmlUtils.dump(src, false /*nextSiblings*/)); - } - } - - return true; - } - - /** - * Merges the requested elements from the library in the main document. - * The key attribute name is used to identify the same elements. - * Merged elements must either not exist in the destination or be identical. - *

- * When merging, append to the end of the application element. - * Also merges any preceding whitespace and up to one comment just prior to the merged element. - * - * @param path The XPath of the elements to merge from the library. Must not be null. - * @param keyAttr The Android-namespace attribute used as key to identify similar elements. - * E.g. "name" for "android:name" - * @param libDoc The library document to merge from. Must not be null. - * @param warnDups When true, will print a warning when a library definition is already - * present in the destination and is equal. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - private boolean mergeNewOrEqual( - String path, - String keyAttr, - Document libDoc, - boolean warnDups) { - - // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment" - int pos = path.lastIndexOf('/'); - assert pos > 1; - String parentPath = path.substring(0, pos); - Element parent = findFirstElement(mMainDoc, parentPath); - assert parent != null; - if (parent == null) { - mLog.error(Severity.ERROR, - xmlFileAndLine(mMainDoc), - "Could not find element %1$s.", - parentPath); - return false; - } - - boolean success = true; - - nextSource: for (Element src : findElements(libDoc, path)) { - String name = getAttributeValue(src, keyAttr); - if (name.length() == 0) { - mLog.error(Severity.ERROR, - xmlFileAndLine(src), - "Undefined '%1$s' attribute in %2$s.", - keyAttr, path); - success = false; - continue; - } - - // Look for the same item in the destination - List dests = findElements(mMainDoc, path, keyAttr, name); - if (dests.size() > 1) { - // This should not be happening. We'll just use the first one found in this case. - mLog.error(Severity.WARNING, - xmlFileAndLine(dests.get(0)), - "Manifest has more than one %1$s[@%2$s=%3$s] element.", - path, keyAttr, name); - } - boolean doMerge = true; - for (Element dest : dests) { - // Don't try to merge this element since it has tools:merge=override|remove. - if (hasOverrideOrRemoveTag(dest)) { - doMerge = false; - continue; - } - // If there's already a similar node in the destination, check it's identical. - StringBuilder diff = new StringBuilder(); - if (compareElements(dest, src, false, diff, keyAttr)) { - // Same element. Skip. - if (warnDups) { - mLog.conflict(Severity.INFO, - xmlFileAndLine(dest), - xmlFileAndLine(src), - "Skipping identical %1$s[@%2$s=%3$s] element.", - path, keyAttr, name); - } - continue nextSource; - } else { - // Print the diff we got from the comparison. - mLog.conflict(Severity.ERROR, - xmlFileAndLine(dest), - xmlFileAndLine(src), - "Trying to merge incompatible %1$s[@%2$s=%3$s] element:\n%4$s", - path, keyAttr, name, diff.toString()); - success = false; - continue nextSource; - } - } - - if (doMerge) { - // Ready to merge element src. Select which previous siblings to merge. - Node start = selectPreviousSiblings(src); - - insertAtEndOf(parent, start, src); - } - } - - return success; - } - - /** - * Returns the value of the given "android:attribute" in the given element. - * - * @param element The non-null element where to extract the attribute. - * @param attrName The local name of the attribute. - * It must use the {@link #NS_URI} but no prefix should be specified here. - * @return The value of the attribute or a non-null empty string if not found. - */ - private String getAttributeValue(Element element, String attrName) { - Attr attr = element.getAttributeNodeNS(NS_URI, attrName); - String value = attr == null ? "" : attr.getNodeValue(); //$NON-NLS-1$ - return value; - } - - /** - * Merge elements as identified by their key name attribute. - * The element must have an option boolean "required" attribute which can be either "true" or - * "false". Default is true if the attribute is missisng. When merging, a "false" is superseded - * by a "true" (explicit or implicit). - *

- * When merging, this does NOT merge any other attributes than {@code keyAttr} and - * {@code requiredAttr}. - * - * @param path The XPath of the elements to merge from the library. Must not be null. - * @param keyAttr The Android-namespace attribute used as key to identify similar elements. - * E.g. "name" for "android:name" - * @param requiredAttr The name of the Android-namespace boolean attribute that must be merged. - * Typically should be "required". - * @param libDoc The library document to merge from. Must not be null. - * @param alternateKeyAttr When non-null, this is an alternate valid key attribute. If the - * default key attribute is missing, we won't output a warning if the alternate one is - * present. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - private boolean mergeAdjustRequired( - String path, - String keyAttr, - String requiredAttr, - Document libDoc, - @Nullable String alternateKeyAttr) { - - // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment" - int pos = path.lastIndexOf('/'); - assert pos > 1; - String parentPath = path.substring(0, pos); - Element parent = findFirstElement(mMainDoc, parentPath); - assert parent != null; - if (parent == null) { - mLog.error(Severity.ERROR, - xmlFileAndLine(mMainDoc), - "Could not find element %1$s.", - parentPath); - return false; - } - - boolean success = true; - - for (Element src : findElements(libDoc, path)) { - Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr); - String name = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ - if (name.length() == 0) { - if (alternateKeyAttr != null) { - attr = src.getAttributeNodeNS(NS_URI, alternateKeyAttr); - String s = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ - if (s.length() != 0) { - // This element lacks the keyAttr but has the alternateKeyAttr. Skip it. - continue; - } - } - - mLog.error(Severity.ERROR, - xmlFileAndLine(src), - "Undefined '%1$s' attribute in %2$s.", - keyAttr, path); - success = false; - continue; - } - - // Look for the same item in the destination - List dests = findElements(mMainDoc, path, keyAttr, name); - if (dests.size() > 1) { - // This should not be happening. We'll just use the first one found in this case. - mLog.error(Severity.WARNING, - xmlFileAndLine(dests.get(0)), - "Manifest has more than one %1$s[@%2$s=%3$s] element.", - path, keyAttr, name); - } - if (dests.size() > 0) { - - attr = src.getAttributeNodeNS(NS_URI, requiredAttr); - String value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$ - if (value == null || !(value.equals("true") || value.equals("false"))) { - mLog.error(Severity.WARNING, - xmlFileAndLine(src), - "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.", - requiredAttr, path, keyAttr, name, value); - continue; - } - boolean boolE = Boolean.parseBoolean(value); - - for (Element dest : dests) { - // Don't try to merge this element since it has tools:merge=override|remove. - if (hasOverrideOrRemoveTag(dest)) { - continue; - } - - // Compare the required attributes. - attr = dest.getAttributeNodeNS(NS_URI, requiredAttr); - value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$ - if (value == null || !(value.equals("true") || value.equals("false"))) { - mLog.error(Severity.WARNING, - xmlFileAndLine(dest), - "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.", - requiredAttr, path, keyAttr, name, value); - continue; - } - boolean boolD = Boolean.parseBoolean(value); - - if (!boolD && boolE) { - // Required attributes differ: destination is false and source was true - // so we need to change the destination to true. - - // If attribute was already in the destination, change it in place - if (attr != null) { - attr.setNodeValue("true"); //$NON-NLS-1$ - } else { - // Otherwise, do nothing. The destination doesn't have the - // required=true attribute, and true is the default value. - // Consequently not setting is the right thing to do. - - // -- code snippet for reference -- - // If we wanted to create a new attribute, we'd use the code - // below. There's a simpler call to d.setAttributeNS(ns, name, value) - // but experience shows that it would create a new prefix out of the - // blue instead of looking it up. - // - // Attr a=d.getOwnerDocument().createAttributeNS(NS_URI, requiredAttr); - // String prefix = d.lookupPrefix(NS_URI); - // if (prefix != null) { - // a.setPrefix(prefix); - // } - // a.setValue("true"); //$NON-NLS-1$ - // d.setAttributeNodeNS(attr); - } - } - } - } else { - // Destination doesn't exist. We simply merge the source element. - // Select which previous siblings to merge. - Node start = selectPreviousSiblings(src); - - Node node = insertAtEndOf(parent, start, src); - - NamedNodeMap attrs = node.getAttributes(); - if (attrs != null) { - for (int i = 0; i < attrs.getLength(); i++) { - Node a = attrs.item(i); - if (a.getNodeType() == Node.ATTRIBUTE_NODE) { - boolean keep = NS_URI.equals(a.getNamespaceURI()); - if (keep) { - name = a.getLocalName(); - keep = keyAttr.equals(name) || requiredAttr.equals(name); - } - if (!keep) { - attrs.removeNamedItemNS(NS_URI, name); - // Restart the loop from index 0 since there's no - // guarantee on the order of the nodes in the "map". - // This makes it O(n+2n) at most, where n is [2..3] in - // a typical case. - i = -1; - } - } - } - } - } - } - - return success; - } - - - - /** - * Checks (but does not merge) uses-feature glEsVersion attribute using the following rules: - *

-     * - Error if defined in lib+dest with dest<lib.
-     * - Never automatically change dest.
-     * - Default implied value is 1.0 (0x00010000).
-     * 
- * - * @param libDoc The library document to merge from. Must not be null. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - private boolean checkGlEsVersion(Document libDoc) { - - String parentPath = "/manifest"; //$NON-NLS-1$ - Element parent = findFirstElement(mMainDoc, parentPath); - assert parent != null; - if (parent == null) { - mLog.error(Severity.ERROR, - xmlFileAndLine(mMainDoc), - "Could not find element %1$s.", - parentPath); - return false; - } - - // Find the max glEsVersion on the destination side - String path = "/manifest/uses-feature"; //$NON-NLS-1$ - String keyAttr = "glEsVersion"; //$NON-NLS-1$ - long destGlEsVersion = 0x00010000L; // default minimum is 1.0 - Element destNode = null; - boolean result = true; - for (Element dest : findElements(mMainDoc, path)) { - Attr attr = dest.getAttributeNodeNS(NS_URI, keyAttr); - String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ - if (value.length() != 0) { - try { - // Note that the value can be an hex number such as 0x00020001 so we - // need Integer.decode instead of Integer.parseInt. - // Note: Integer.decode cannot handle "ffffffff", see JDK issue 6624867 - // so we just treat the version as a long and test like this, ignoring - // the fact that a value of 0xFFFF/.0xFFFF is probably invalid anyway - // in the context of glEsVersion. - long version = Long.decode(value); - if (version >= destGlEsVersion) { - destGlEsVersion = version; - destNode = dest; - } else if (version < 0x00010000) { - mLog.error(Severity.WARNING, - xmlFileAndLine(dest), - "Ignoring because it's smaller than 1.0.", - value); - } - } catch (NumberFormatException e) { - // Note: NumberFormatException.toString() has no interesting information - // so we don't output it. - mLog.error(Severity.ERROR, - xmlFileAndLine(dest), - "Failed to parse : must be an integer in the form 0x00020001.", - value); - result = false; - } - } - } - - // If we found at least one valid with no error, use that, otherwise bail out. - if (!result && destNode == null) { - return false; - } - - // Now find the max glEsVersion on the source side. - - long srcGlEsVersion = 0x00010000L; // default minimum is 1.0 - Element srcNode = null; - result = true; - for (Element src : findElements(libDoc, path)) { - Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr); - String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ - if (value.length() != 0) { - try { - // See comment on Long.decode above. - long version = Long.decode(value); - if (version >= srcGlEsVersion) { - srcGlEsVersion = version; - srcNode = src; - } else if (version < 0x00010000) { - mLog.error(Severity.WARNING, - xmlFileAndLine(src), - "Ignoring because it's smaller than 1.0.", - value); - } - } catch (NumberFormatException e) { - // Note: NumberFormatException.toString() has no interesting information - // so we don't output it. - mLog.error(Severity.ERROR, - xmlFileAndLine(src), - "Failed to parse : must be an integer in the form 0x00020001.", - value); - result = false; - } - } - } - - if (srcNode != null && destGlEsVersion < srcGlEsVersion) { - mLog.conflict(Severity.WARNING, - xmlFileAndLine(destNode == null ? mMainDoc : destNode), - xmlFileAndLine(srcNode), - "Main manifest has but library uses glEsVersion='0x%2$08x'%3$s", - destGlEsVersion, - srcGlEsVersion, - destNode != null ? "" : //$NON-NLS-1$ - "\nNote: main manifest lacks a declaration, and thus defaults to glEsVersion=0x00010000." - ); - result = false; - } - - return result; - } - - /** - * Checks (but does not merge) uses-sdk attributes using the following rules: - *
-     * - {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
-     * - {@code @targetSdkVersion}: warning if dest<lib. Never automatically change destination.
-     * - {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
-     * - The API level can be a codename if we have a callback that can convert it to an integer.
-     * 
- * @param libDoc The library document to merge from. Must not be null. - * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). - */ - private boolean checkSdkVersion(Document libDoc) { - - boolean result = true; - - Element destUsesSdk = findFirstElement(mMainDoc, "/manifest/uses-sdk"); //$NON-NLS-1$ - - if (hasOverrideOrRemoveTag(destUsesSdk)) { - // Don't try to check this element since it has tools:merge=override|remove. - return true; - } - - Element srcUsesSdk = findFirstElement(libDoc, "/manifest/uses-sdk"); //$NON-NLS-1$ - - AtomicInteger destValue = new AtomicInteger(1); - AtomicInteger srcValue = new AtomicInteger(1); - AtomicBoolean destImplied = new AtomicBoolean(true); - AtomicBoolean srcImplied = new AtomicBoolean(true); - - // Check minSdkVersion - int destMinSdk = 1; - result = extractSdkVersionAttribute( - libDoc, - destUsesSdk, srcUsesSdk, - "min", //$NON-NLS-1$ - destValue, srcValue, - destImplied, srcImplied); - - if (result) { - // Make it an error for an application to use a library with a greater - // minSdkVersion. This means the library code may crash unexpectedly. - // TODO it would be nice to be able to work around this in case the - // user think s/he knows what s/he's doing. - // We could define a simple XML comment flag: - - destMinSdk = destValue.get(); - - if (destMinSdk < srcValue.get()) { - mLog.conflict(Severity.ERROR, - xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk), - xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk), - "Main manifest has but library uses minSdkVersion='%2$d'%3$s", - destMinSdk, - srcValue.get(), - !destImplied.get() ? "" : //$NON-NLS-1$ - "\nNote: main manifest lacks a declaration, which defaults to value 1." - ); - result = false; - } - } - - // Check targetSdkVersion. - - // Note that destValue/srcValue purposely defaults to whatever minSdkVersion was last read - // since that's their definition when missing. - destImplied.set(true); - srcImplied.set(true); - - boolean result2 = extractSdkVersionAttribute( - libDoc, - destUsesSdk, srcUsesSdk, - "target", //$NON-NLS-1$ - destValue, srcValue, - destImplied, srcImplied); - - result &= result2; - if (result2) { - // Make it a warning for an application to use a library with a greater - // targetSdkVersion. - - int destTargetSdk = destImplied.get() ? destMinSdk : destValue.get(); - - if (destTargetSdk < srcValue.get()) { - mLog.conflict(Severity.WARNING, - xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk), - xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk), - "Main manifest has but library uses targetSdkVersion='%2$d'%3$s", - destTargetSdk, - srcValue.get(), - !destImplied.get() ? "" : //$NON-NLS-1$ - "\nNote: main manifest lacks a declaration, which defaults to value minSdkVersion or 1." - ); - result = false; - } - } - - return result; - } - - /** - * Implementation detail for {@link #checkSdkVersion(Document)}. - * Note that the various atomic out-variables must be preset to their default before - * the call. - *

- * destValue/srcValue will be filled with the integer value of the field, if present - * and a correct number, in which case destImplied/destImplied are also set to true. - * Otherwise the values and the implied variables are left untouched. - */ - private boolean extractSdkVersionAttribute( - Document libDoc, - Element destUsesSdk, - Element srcUsesSdk, - String attr, - AtomicInteger destValue, - AtomicInteger srcValue, - AtomicBoolean destImplied, - AtomicBoolean srcImplied) { - String s = destUsesSdk == null ? "" //$NON-NLS-1$ - : destUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$ - - boolean result = true; - assert s != null; - s = s.trim(); - try { - if (s.length() > 0) { - destValue.set(Integer.parseInt(s)); - destImplied.set(false); - } - } catch (NumberFormatException e) { - boolean error = true; - if (mCallback != null) { - // Versions can contain codenames such as "JellyBean". - // We'll accept it only if have a callback that can give us the API level for it. - int apiLevel = mCallback.queryCodenameApiLevel(s); - if (apiLevel > ICallback.UNKNOWN_CODENAME) { - destValue.set(apiLevel); - destImplied.set(false); - error = false; - } - } - if (error) { - // Note: NumberFormatException.toString() has no interesting information - // so we don't output it. - mLog.error(Severity.ERROR, - xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk), - "Failed to parse : must be an integer number or codename.", - attr, - s); - result = false; - } - } - - s = srcUsesSdk == null ? "" //$NON-NLS-1$ - : srcUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$ - assert s != null; - s = s.trim(); - try { - if (s.length() > 0) { - srcValue.set(Integer.parseInt(s)); - srcImplied.set(false); - } - } catch (NumberFormatException e) { - boolean error = true; - if (mCallback != null) { - // Versions can contain codenames such as "JellyBean". - // We'll accept it only if have a callback that can give us the API level for it. - int apiLevel = mCallback.queryCodenameApiLevel(s); - if (apiLevel > ICallback.UNKNOWN_CODENAME) { - srcValue.set(apiLevel); - srcImplied.set(false); - error = false; - } - } - if (error) { - mLog.error(Severity.ERROR, - xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk), - "Failed to parse : must be an integer number or codename.", - attr, - s); - result = false; - } - } - - return result; - } - - - // ----- - - - /** - * Given an element E, select which previous siblings we want to merge. - * We want to include any whitespace up to the closing of the previous element. - * We also want to include up preceding comment nodes and their preceding whitespace. - *

- * This may returns either {@code end} or a previous sibling. Never returns null. - */ - @NonNull - private Node selectPreviousSiblings(Node end) { - - Node start = end; - Node prev = start.getPreviousSibling(); - while (prev != null) { - short t = prev.getNodeType(); - if (t == Node.TEXT_NODE) { - String text = prev.getNodeValue(); - if (text == null || text.trim().length() != 0) { - // Not whitespace, we don't want it. - break; - } - } else if (t == Node.COMMENT_NODE) { - // It's a comment. We'll take it. - } else { - // Not a comment node nor a whitespace text. We don't want it. - break; - } - start = prev; - prev = start.getPreviousSibling(); - } - - return start; - } - - /** - * Inserts all siblings from {@code start} to {@code end} at the end - * of the given destination element. - *

- * Implementation detail: this clones the source nodes into the destination. - * - * @param dest The destination at the end of which to insert. Cannot be null. - * @param start The first element to insert. Must not be null. - * @param end The last element to insert (included). Must not be null. - * Must be a direct "next sibling" of the start node. - * Can be equal to the start node to insert just that one node. - * @return The copy of the {@code end} node in the destination document or null - * if no such copy was created and added to the destination. - */ - private Node insertAtEndOf(Element dest, Node start, Node end) { - // Check whether we'll need to adjust URI prefixes - String destPrefix = XmlUtils.lookupNamespacePrefix(mMainDoc, NS_URI); - String srcPrefix = XmlUtils.lookupNamespacePrefix(start.getOwnerDocument(), NS_URI); - boolean needPrefixChange = destPrefix != null && !destPrefix.equals(srcPrefix); - - // First let's figure out the insertion point. - // We want the end of the last 'content' element of the - // destination element and basically we want to insert right - // before the last whitespace of the destination element. - Node target = dest.getLastChild(); - while (target != null) { - if (target.getNodeType() == Node.TEXT_NODE) { - String text = target.getNodeValue(); - if (text == null || text.trim().length() != 0) { - // Not whitespace, insert after. - break; - } - } else { - // Not text. Insert after - break; - } - target = target.getPreviousSibling(); - } - if (target != null) { - target = target.getNextSibling(); - } - - // Destination and start..end must not be part of the same document - // because we try to import below. If they were, it would mess the - // structure. - assert dest.getOwnerDocument() == mMainDoc; - assert dest.getOwnerDocument() != start.getOwnerDocument(); - assert start.getOwnerDocument() == end.getOwnerDocument(); - - while (start != null) { - Node node = mMainDoc.importNode(start, true /*deep*/); - if (needPrefixChange) { - changePrefix(node, srcPrefix, destPrefix); - } - dest.insertBefore(node, target); - - if (start == end) { - return node; - } - start = start.getNextSibling(); - } - return null; - } - - /** - * Changes the namespace prefix of all nodes, recursively. - * - * @param node The node to process, as well as all it's descendants. Can be null. - * @param srcPrefix The prefix to match. - * @param destPrefix The new prefix to replace with. - */ - private void changePrefix(Node node, String srcPrefix, String destPrefix) { - for (; node != null; node = node.getNextSibling()) { - if (srcPrefix.equals(node.getPrefix())) { - node.setPrefix(destPrefix); - } - Node child = node.getFirstChild(); - if (child != null) { - changePrefix(child, srcPrefix, destPrefix); - } - } - } - - /** - * Compares two {@link Element}s recursively. - * They must be identical with the same structure. - * Order should not matter. - * Whitespace and comments are ignored. - * - * @param expected The first element to compare. - * @param actual The second element to compare with. - * @param nextSiblings If true, will also compare the following siblings. - * If false, it will just compare the given node. - * @param diff An optional {@link StringBuilder} where to accumulate a diff output. - * @param keyAttr An optional key attribute to always add to elements when dumping a diff. - * @return True if {@code e1} and {@code e2} are equal. - */ - private boolean compareElements( - @NonNull Node expected, - @NonNull Node actual, - boolean nextSiblings, - @Nullable StringBuilder diff, - @Nullable String keyAttr) { - Map nsPrefixE = new HashMap(); - Map nsPrefixA = new HashMap(); - String sE = MergerXmlUtils.printElement(expected, nsPrefixE, ""); //$NON-NLS-1$ - String sA = MergerXmlUtils.printElement(actual, nsPrefixA, ""); //$NON-NLS-1$ - if (sE.equals(sA)) { - return true; - } else { - if (diff != null) { - MergerXmlUtils.printXmlDiff(diff, sE, sA, nsPrefixE, nsPrefixA, NS_URI + ':' + keyAttr); - } - return false; - } - } - - /** - * Finds the first element matching the given XPath expression in the given document. - * - * @param doc The document where to find the expression. - * @param path The XPath expression. It must yield an {@link Element} node type. - * @return The {@link Element} found or null. - */ - @Nullable - private Element findFirstElement( - @NonNull Document doc, - @NonNull String path) { - Node result; - try { - result = (Node) mXPath.evaluate(path, doc, XPathConstants.NODE); - if (result instanceof Element) { - return (Element) result; - } - - if (result != null) { - mLog.error(Severity.ERROR, - xmlFileAndLine(doc), - "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$ - result.getClass().getName(), path); - } - } catch (XPathExpressionException e) { - mLog.error(Severity.ERROR, - xmlFileAndLine(doc), - "XPath error on expr %s: %s", //$NON-NLS-1$ - path, e.toString()); - } - return null; - } - - /** - * Finds zero or more elements matching the given XPath expression in the given document. - * - * @param doc The document where to find the expression. - * @param path The XPath expression. Only {@link Element}s nodes will be returned. - * @return A list of {@link Element} found, possibly empty but never null. - */ - private List findElements( - @NonNull Document doc, - @NonNull String path) { - return findElements(doc, path, null, null); - } - - - /** - * Finds zero or more elements matching the given XPath expression in the given document. - *

- * Furthermore, the elements must have an attribute matching the given attribute name - * and value if provided. (If you don't need to match an attribute, use the other version.) - *

- * Note that if you provide {@code attrName} as non-null then the {@code attrValue} - * must be non-null too. In this case the XPath expression will be modified to add - * the check by naively appending a "[name='value']" filter. - * - * @param doc The document where to find the expression. - * @param path The XPath expression. Only {@link Element}s nodes will be returned. - * @param attrName The name of the optional attribute to match. Can be null. - * @param attrValue The value of the optional attribute to match. - * Can be null if {@code attrName} is null, otherwise must be non-null. - * @return A list of {@link Element} found, possibly empty but never null. - * - * @see #findElements(Document, String) - */ - private List findElements( - @NonNull Document doc, - @NonNull String path, - @Nullable String attrName, - @Nullable String attrValue) { - List elements = new ArrayList(); - - if (attrName != null) { - assert attrValue != null; - // Generate expression /manifest/application/activity[@android:name='my.fqcn'] - path = String.format("%1$s[@%2$s:%3$s='%4$s']", //$NON-NLS-1$ - path, NS_PREFIX, attrName, attrValue); - } - - try { - NodeList results = (NodeList) mXPath.evaluate(path, doc, XPathConstants.NODESET); - if (results != null && results.getLength() > 0) { - for (int i = 0; i < results.getLength(); i++) { - Node n = results.item(i); - assert n instanceof Element; - if (n instanceof Element) { - elements.add((Element) n); - } else { - mLog.error(Severity.ERROR, - xmlFileAndLine(doc), - "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$ - n.getClass().getName(), path); - } - } - } - - } catch (XPathExpressionException e) { - mLog.error(Severity.ERROR, - xmlFileAndLine(doc), - "XPath error on expr %s: %s", //$NON-NLS-1$ - path, e.toString()); - } - - return elements; - } - - /** - * Returns a new {@link FileAndLine} structure that identifies - * the base filename & line number from which the XML node was parsed. - *

- * When the line number is unknown (e.g. if a {@link Document} instance is given) - * then line number 0 will be used. - * - * @param node The node or document where the error occurs. Must not be null. - * @return A new non-null {@link FileAndLine} combining the file name and line number. - */ - private @NonNull FileAndLine xmlFileAndLine(@NonNull Node node) { - return MergerXmlUtils.xmlFileAndLine(node); - } - - /** - * Checks whether the given element has a tools:merge=override or tools:merge=remove attribute. - * @param node The node to check. - * @return True if the element has a tools:merge=override or tools:merge=remove attribute. - */ - private boolean hasOverrideOrRemoveTag(@Nullable Node node) { - if (node == null || node.getNodeType() != Node.ELEMENT_NODE) { - return false; - } - NamedNodeMap attrs = node.getAttributes(); - Node merge = attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR); - String value = merge == null ? null : merge.getNodeValue(); - return MERGE_OVERRIDE.equals(value) || MERGE_REMOVE.equals(value); - } - - /** - * Cleans up all tools attributes from the given node hierarchy. - *

- * If an element is marked with tools:merge=override, this attribute is removed. - * If an element is marked with tools:merge=remove, the whole element is removed. - * - * @param root The root node to parse and edit, recursively. - */ - private void cleanupToolsAttributes(@Nullable Node root) { - if (root == null) { - return; - } - NamedNodeMap attrs = root.getAttributes(); - if (attrs != null) { - for (int i = attrs.getLength() - 1; i >= 0; i--) { - Node attr = attrs.item(i); - if (SdkConstants.XMLNS_URI.equals(attr.getNamespaceURI()) && - TOOLS_URI.equals(attr.getNodeValue())) { - attrs.removeNamedItem(attr.getNodeName()); - } else if (TOOLS_URI.equals(attr.getNamespaceURI()) && - MERGE_ATTR.equals(attr.getLocalName())) { - attrs.removeNamedItem(attr.getNodeName()); - } - } - assert attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR) == null; - } - - for (Node child = root.getFirstChild(); child != null; ) { - if (child.getNodeType() != Node.ELEMENT_NODE) { - child = child.getNextSibling(); - continue; - } - attrs = child.getAttributes(); - Node merge = attrs == null ? null : attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR); - String value = merge == null ? null : merge.getNodeValue(); - Node sibling = child.getNextSibling(); - if (MERGE_REMOVE.equals(value)) { - // Note: save the previous sibling since removing the child will clear its siblings. - Node prev = child.getPreviousSibling(); - root.removeChild(child); - // If there's some whitespace just before that element, clean it up too. - while (prev != null && prev.getNodeType() == Node.TEXT_NODE) { - if (prev.getNodeValue().trim().length() == 0) { - Node prevPrev = prev.getPreviousSibling(); - root.removeChild(prev); - prev = prevPrev; - } else { - break; - } - } - } else { - cleanupToolsAttributes(child); - } - child = sibling; - } - } - - /** - * @see #cleanupToolsAttributes(Node) - */ - private Document cleanupToolsAttributes(@NonNull Document doc) { - cleanupToolsAttributes(doc.getFirstChild()); - return doc; - } -} diff --git a/manifmerger/src/com/android/manifmerger/MergerLog.java b/manifmerger/src/com/android/manifmerger/MergerLog.java deleted file mode 100755 index 446898c..0000000 --- a/manifmerger/src/com/android/manifmerger/MergerLog.java +++ /dev/null @@ -1,151 +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.manifmerger; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.utils.ILogger; - - -/** - * Helper to create {@link IMergerLog} instances with specific purposes. - */ -public abstract class MergerLog { - - /** - * Create a new instance of a {@link MergerLog} that prints to an {@link ILogger}. - * - * @param sdkLog A non-null {@link ILogger}. - * @return A new IMergerLog. - */ - public static IMergerLog wrapSdkLog(final @NonNull ILogger sdkLog) { - return new IMergerLog() { - @Override - public void error( - @NonNull Severity severity, - @NonNull FileAndLine location, - @NonNull String message, - Object...msgParams) { - - switch(severity) { - case INFO: - sdkLog.info( - "[%1$s] %2$s", //$NON-NLS-1$ - location, - String.format(message, msgParams)); - break; - case WARNING: - sdkLog.warning( - "[%1$s] %2$s", //$NON-NLS-1$ - location, - String.format(message, msgParams)); - break; - case ERROR: - sdkLog.error(null /*throwable*/, - "[%1$s] %2$s", //$NON-NLS-1$ - location, - String.format(message, msgParams)); - break; - } - } - - @Override - public void conflict(@NonNull Severity severity, - @NonNull FileAndLine location1, - @NonNull FileAndLine location2, - @NonNull String message, - Object...msgParams) { - - switch(severity) { - case INFO: - sdkLog.info( - "[%1$s, %2$s] %3$s", //$NON-NLS-1$ - location1, - location2, - String.format(message, msgParams)); - break; - case WARNING: - sdkLog.warning( - "[%1$s, %2$s] %3$s", //$NON-NLS-1$ - location1, - location2, - String.format(message, msgParams)); - break; - case ERROR: - sdkLog.error(null /*throwable*/, - "[%1$s, %2$s] %3$s", //$NON-NLS-1$ - location1, - location2, - String.format(message, msgParams)); - break; - } - }; - }; - } - - /* - * Creates a new instance of a {@link MergerLog} that wraps another {@link IMergerLog} - * and overrides the {@link FileAndLine} locations with the arguments specified. - *

- * An example of usage would be merging temporary files yet associating the errors - * with the original files. - * - * @param parentLog A non-null IMergerLog to wrap. - * @param filePath1 The file path to override in location1 (for errors and conflicts). - * @param filePath2 An optional file path to override in location2 (for conflicts). - * @return A new IMergerLog. - */ - public static IMergerLog mergerLogOverrideLocation( - final @NonNull IMergerLog parentLog, - final @Nullable String filePath1, - final @Nullable String... filePath2) { - return new IMergerLog() { - @Override - public void error( - @NonNull Severity severity, - @NonNull FileAndLine location, - @NonNull String message, - Object...msgParams) { - - if (filePath1 != null) { - location = new FileAndLine(filePath1, location.getLine()); - } - - parentLog.error(severity, location, message, msgParams); - } - - @Override - public void conflict(@NonNull Severity severity, - @NonNull FileAndLine location1, - @NonNull FileAndLine location2, - @NonNull String message, - Object...msgParams) { - - if (filePath1 != null) { - location1 = new FileAndLine(filePath1, location1.getLine()); - } - - if (filePath2 != null && filePath2.length > 0) { - location2 = new FileAndLine(filePath2[0], location2.getLine()); - } - - parentLog.conflict(severity, location1, location2, message, msgParams); - }; - }; - } - -} diff --git a/manifmerger/src/com/android/manifmerger/MergerXmlUtils.java b/manifmerger/src/com/android/manifmerger/MergerXmlUtils.java deleted file mode 100755 index bb60464..0000000 --- a/manifmerger/src/com/android/manifmerger/MergerXmlUtils.java +++ /dev/null @@ -1,915 +0,0 @@ -/* - * 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.manifmerger; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.manifmerger.IMergerLog.FileAndLine; -import com.android.manifmerger.IMergerLog.Severity; -import com.android.utils.ILogger; -import com.android.utils.XmlUtils; - -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.xml.sax.ErrorHandler; -import org.xml.sax.InputSource; -import org.xml.sax.SAXParseException; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.Reader; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -/** - * A few XML handling utilities. - */ -class MergerXmlUtils { - - private static final String DATA_ORIGIN_FILE = "manif.merger.file"; //$NON-NLS-1$ - private static final String DATA_FILE_NAME = "manif.merger.filename"; //$NON-NLS-1$ - private static final String DATA_LINE_NUMBER = "manif.merger.line#"; //$NON-NLS-1$ - - /** - * Parses the given XML file as a DOM document. - * The parser does not validate the DTD nor any kind of schema. - * It is namespace aware. - *

- * This adds a user tag with the original {@link File} to the returned document. - * You can retrieve this file later by using {@link #extractXmlFilename(Node)}. - * - * @param xmlFile The XML {@link File} to parse. Must not be null. - * @param log An {@link ILogger} for reporting errors. Must not be null. - * @return A new DOM {@link Document}, or null. - */ - @Nullable - static Document parseDocument(@NonNull final File xmlFile, @NonNull final IMergerLog log) { - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - Reader reader = new BufferedReader(new FileReader(xmlFile)); - InputSource is = new InputSource(reader); - factory.setNamespaceAware(true); - factory.setValidating(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - - // We don't want the default handler which prints errors to stderr. - builder.setErrorHandler(new ErrorHandler() { - @Override - public void warning(SAXParseException e) { - log.error(Severity.WARNING, - new FileAndLine(xmlFile.getAbsolutePath(), 0), - "Warning when parsing: %1$s", - e.toString()); - } - @Override - public void fatalError(SAXParseException e) { - log.error(Severity.ERROR, - new FileAndLine(xmlFile.getAbsolutePath(), 0), - "Fatal error when parsing: %1$s", - xmlFile.getName(), e.toString()); - } - @Override - public void error(SAXParseException e) { - log.error(Severity.ERROR, - new FileAndLine(xmlFile.getAbsolutePath(), 0), - "Error when parsing: %1$s", - e.toString()); - } - }); - - Document doc = builder.parse(is); - doc.setUserData(DATA_ORIGIN_FILE, xmlFile, null /*handler*/); - findLineNumbers(doc, 1); - - return doc; - - } catch (FileNotFoundException e) { - log.error(Severity.ERROR, - new FileAndLine(xmlFile.getAbsolutePath(), 0), - "XML file not found"); - - } catch (Exception e) { - log.error(Severity.ERROR, - new FileAndLine(xmlFile.getAbsolutePath(), 0), - "Failed to parse XML file: %1$s", - e.toString()); - } - - return null; - } - - /** - * Parses the given XML string as a DOM document. - * The parser does not validate the DTD nor any kind of schema. - * It is namespace aware. - * - * @param xml The XML string to parse. Must not be null. - * @param log An {@link ILogger} for reporting errors. Must not be null. - * @return A new DOM {@link Document}, or null. - */ - @Nullable - static Document parseDocument(@NonNull String xml, - @NonNull IMergerLog log, - @NonNull FileAndLine errorContext) { - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - InputSource is = new InputSource(new StringReader(xml)); - factory.setNamespaceAware(true); - factory.setValidating(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(is); - findLineNumbers(doc, 1); - return doc; - } catch (Exception e) { - log.error(Severity.ERROR, errorContext, "Failed to parse XML string"); - } - - return null; - } - - /** - * Decorates the document with the specified file name, which can be - * retrieved later by calling {@link #extractLineNumber(Node)}. - *

- * It also tries to add line number information, with the caveat that the - * current implementation is a gross approximation. - *

- * There is no need to call this after calling one of the {@code parseDocument()} - * methods since they already decorated their own document. - * - * @param doc The document to decorate. - * @param fileName The name to retrieve later for that document. - */ - static void decorateDocument(@NonNull Document doc, @NonNull String fileName) { - doc.setUserData(DATA_FILE_NAME, fileName, null /*handler*/); - findLineNumbers(doc, 1); - } - - /** - * Returns a new {@link FileAndLine} structure that identifies - * the base filename & line number from which the XML node was parsed. - *

- * When the line number is unknown (e.g. if a {@link Document} instance is given) - * then line number 0 will be used. - * - * @param node The node or document where the error occurs. Must not be null. - * @return A new non-null {@link FileAndLine} combining the file name and line number. - */ - @NonNull - static FileAndLine xmlFileAndLine(@NonNull Node node) { - String name = extractXmlFilename(node); - int line = extractLineNumber(node); // 0 in case of error or unknown - return new FileAndLine(name, line); - } - - /** - * Extracts the origin {@link File} that {@link #parseDocument(File, IMergerLog)} - * added to the XML document or the string added by - * - * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog)}. - * @return The {@link File} object used to create the document or null. - */ - @Nullable - static String extractXmlFilename(@Nullable Node xmlNode) { - if (xmlNode != null && xmlNode.getNodeType() != Node.DOCUMENT_NODE) { - xmlNode = xmlNode.getOwnerDocument(); - } - if (xmlNode != null) { - Object data = xmlNode.getUserData(DATA_ORIGIN_FILE); - if (data instanceof File) { - return ((File) data).getName(); - } - data = xmlNode.getUserData(DATA_FILE_NAME); - if (data instanceof String) { - return (String) data; - } - } - - return null; - } - - /** - * This is a CRUDE INEXACT HACK to decorate the DOM with some kind of line number - * information for elements. It's inexact because by the time we get the DOM we - * already have lost all the information about whitespace between attributes. - *

- * Also we don't even try to deal with \n vs \r vs \r\n insanity. This only counts - * the \n occurring in text nodes to determine line advances, which is clearly flawed. - *

- * However it's good enough for testing, and we'll replace it by a PositionXmlParser - * once it's moved into com.android.util. - */ - private static int findLineNumbers(Node node, int line) { - for (; node != null; node = node.getNextSibling()) { - node.setUserData(DATA_LINE_NUMBER, Integer.valueOf(line), null /*handler*/); - - if (node.getNodeType() == Node.TEXT_NODE) { - String text = node.getNodeValue(); - if (text.length() > 0) { - for (int pos = 0; (pos = text.indexOf('\n', pos)) != -1; pos++) { - ++line; - } - } - } - - Node child = node.getFirstChild(); - if (child != null) { - line = findLineNumbers(child, line); - } - } - return line; - } - - /** - * Extracts the line number that {@link #findLineNumbers} added to the XML nodes. - * - * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog)}. - * @return The line number if found or 0. - */ - static int extractLineNumber(@Nullable Node xmlNode) { - if (xmlNode != null) { - Object data = xmlNode.getUserData(DATA_LINE_NUMBER); - if (data instanceof Integer) { - return ((Integer) data).intValue(); - } - } - - return 0; - } - - /** - * Outputs the given XML {@link Document} to the file {@code outFile}. - * - * TODO right now reformats the document. Needs to output as-is, respecting white-space. - * - * @param doc The document to output. Must not be null. - * @param outFile The {@link File} where to write the document. - * @param log A log in case of error. - * @return True if the file was written, false in case of error. - */ - static boolean printXmlFile( - @NonNull Document doc, - @NonNull File outFile, - @NonNull IMergerLog log) { - // Quick thing based on comments from http://stackoverflow.com/questions/139076 - try { - Transformer tf = TransformerFactory.newInstance().newTransformer(); - tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$ - tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ - tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ - tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$ - "4"); //$NON-NLS-1$ - tf.transform(new DOMSource(doc), new StreamResult(outFile)); - return true; - } catch (TransformerException e) { - log.error(Severity.ERROR, - new FileAndLine(outFile.getName(), 0), - "Failed to write XML file: %1$s", - e.toString()); - return false; - } - } - - /** - * Outputs the given XML {@link Document} as a string. - * - * TODO right now reformats the document. Needs to output as-is, respecting white-space. - * - * @param doc The document to output. Must not be null. - * @param log A log in case of error. - * @return A string representation of the XML. Null in case of error. - */ - static String printXmlString( - @NonNull Document doc, - @NonNull IMergerLog log) { - try { - Transformer tf = TransformerFactory.newInstance().newTransformer(); - tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$ - tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ - tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ - tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$ - "4"); //$NON-NLS-1$ - StringWriter sw = new StringWriter(); - tf.transform(new DOMSource(doc), new StreamResult(sw)); - return sw.toString(); - } catch (TransformerException e) { - log.error(Severity.ERROR, - new FileAndLine(extractXmlFilename(doc), 0), - "Failed to write XML file: %1$s", - e.toString()); - return null; - } - } - - /** - * Dumps the structure of the DOM to a simple text string. - * - * @param node The first node to dump (recursively). Can be null. - * @param nextSiblings If true, will also dump the following siblings. - * If false, it will just process the given node. - * @return A string representation of the Node structure, useful for debugging. - */ - @NonNull - static String dump(@Nullable Node node, boolean nextSiblings) { - return dump(node, 0 /*offset*/, nextSiblings, true /*deep*/, null /*keyAttr*/); - } - - - /** - * Dumps the structure of the DOM to a simple text string. - * Each line is terminated with a \n separator. - * - * @param node The first node to dump. Can be null. - * @param offsetIndex The offset to add at the begining of each line. Each offset is - * converted into 2 space characters. - * @param nextSiblings If true, will also dump the following siblings. - * If false, it will just process the given node. - * @param deep If true, this will recurse into children. - * @param keyAttr An optional attribute *local* name to insert when writing an element. - * For example when writing an Activity, it helps to always insert "name" attribute. - * @return A string representation of the Node structure, useful for debugging. - */ - @NonNull - static String dump( - @Nullable Node node, - int offsetIndex, - boolean nextSiblings, - boolean deep, - @Nullable String keyAttr) { - StringBuilder sb = new StringBuilder(); - - String offset = ""; //$NON-NLS-1$ - for (int i = 0; i < offsetIndex; i++) { - offset += " "; //$NON-NLS-1$ - } - - if (node == null) { - sb.append(offset).append("(end reached)\n"); - - } else { - for (; node != null; node = node.getNextSibling()) { - String type = null; - short t = node.getNodeType(); - switch(t) { - case Node.ELEMENT_NODE: - String attr = ""; - if (keyAttr != null) { - NamedNodeMap attrs = node.getAttributes(); - if (attrs != null) { - for (int i = 0; i < attrs.getLength(); i++) { - Node a = attrs.item(i); - if (a != null && keyAttr.equals(a.getLocalName())) { - attr = String.format(" %1$s=%2$s", - a.getNodeName(), a.getNodeValue()); - break; - } - } - } - } - sb.append(String.format("%1$s<%2$s%3$s>\n", - offset, node.getNodeName(), attr)); - break; - case Node.COMMENT_NODE: - sb.append(String.format("%1$s\n", - offset, node.getNodeValue())); - break; - case Node.TEXT_NODE: - String txt = node.getNodeValue().trim(); - if (txt.length() == 0) { - // Keep this for debugging. TODO make it a flag - // to dump whitespace on debugging. Otherwise ignore it. - // txt = "[whitespace]"; - break; - } - sb.append(String.format("%1$s%2$s\n", offset, txt)); - break; - case Node.ATTRIBUTE_NODE: - sb.append(String.format("%1$s @%2$s = %3$s\n", - offset, node.getNodeName(), node.getNodeValue())); - break; - case Node.CDATA_SECTION_NODE: - type = "cdata"; //$NON-NLS-1$ - break; - case Node.DOCUMENT_NODE: - type = "document"; //$NON-NLS-1$ - break; - case Node.PROCESSING_INSTRUCTION_NODE: - type = "PI"; //$NON-NLS-1$ - break; - default: - type = Integer.toString(t); - } - - if (type != null) { - sb.append(String.format("%1$s[%2$s] <%3$s> %4$s\n", - offset, type, node.getNodeName(), node.getNodeValue())); - } - - if (deep) { - List attrs = sortedAttributeList(node.getAttributes()); - for (Attr attr : attrs) { - sb.append(String.format("%1$s @%2$s = %3$s\n", - offset, attr.getNodeName(), attr.getNodeValue())); - } - - Node child = node.getFirstChild(); - if (child != null) { - sb.append(dump(child, offsetIndex+1, true, true, keyAttr)); - } - } - - if (!nextSiblings) { - break; - } - } - } - return sb.toString(); - } - - /** - * Returns a sorted list of attributes. - * The list is never null and does not contain null items. - * - * @param attrMap A Node map as returned by {@link Node#getAttributes()}. - * Can be null, in which case an empty list is returned. - * @return A non-null, possible empty, list of all nodes that are actual {@link Attr}, - * sorted by increasing attribute name. - */ - @NonNull - static List sortedAttributeList(@Nullable NamedNodeMap attrMap) { - List list = new ArrayList(); - - if (attrMap != null) { - for (int i = 0; i < attrMap.getLength(); i++) { - Node attr = attrMap.item(i); - if (attr instanceof Attr) { - list.add((Attr) attr); - } - } - } - - if (list.size() > 1) { - // Sort it by attribute name - Collections.sort(list, getAttrComparator()); - } - - return list; - } - - /** - * Returns a comparator for {@link Attr}, alphabetically sorted by name. - * The "name" attribute is special and always sorted to the front. - */ - @NonNull - static Comparator getAttrComparator() { - return new Comparator() { - @Override - public int compare(Attr a1, Attr a2) { - String s1 = a1 == null ? "" : a1.getNodeName(); //$NON-NLS-1$ - String s2 = a2 == null ? "" : a2.getNodeValue(); //$NON-NLS-1$ - - int prio1 = s1.equals("name") ? 0 : 1; //$NON-NLS-1$ - int prio2 = s2.equals("name") ? 0 : 1; //$NON-NLS-1$ - if (prio1 == 0 || prio2 == 0) { - return prio1 - prio2; - } - - return s1.compareTo(s2); - } - }; - } - - /** - * Inject attributes into an existing document. - *

- * The map keys are "/manifest/elements...|attribute-ns-uri attribute-local-name", - * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion". - * (note the space separator between the attribute URI and its local name.) - * The elements will be created if they don't exists. Existing attributes will be modified. - * The replacement is done on the main document before merging. - * The value can be null to remove an existing attribute. - * - * @param doc The document to modify in-place. - * @param attributeMap A map of attributes to inject in the form [pseudo-xpath] => value. - * @param log A log in case of error. - */ - static void injectAttributes( - @Nullable Document doc, - @Nullable Map attributeMap, - @NonNull IMergerLog log) { - if (doc == null || attributeMap == null || attributeMap.isEmpty()) { - return; - } - - // 1=path 2=URI 3=local name - final Pattern keyRx = Pattern.compile("^/([^\\|]+)\\|([^ ]*) +(.+)$"); //$NON-NLS-1$ - final FileAndLine docInfo = xmlFileAndLine(doc); - - nextAttribute: for (Entry entry : attributeMap.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - if (key == null || key.isEmpty()) { - continue; - } - - Matcher m = keyRx.matcher(key); - if (!m.matches()) { - log.error(Severity.WARNING, docInfo, "Invalid injected attribute key: %s", key); - continue; - } - String path = m.group(1); - String attrNsUri = m.group(2); - String attrName = m.group(3); - - String[] segment = path.split(Pattern.quote("/")); //$NON-NLS-1$ - - // Get the path elements. Create them as needed if they don't exist. - Node element = doc; - nextSegment: for (int i = 0; i < segment.length; i++) { - // Find a child with the segment's name - String name = segment[i]; - for (Node child = element.getFirstChild(); - child != null; - child = child.getNextSibling()) { - if (child.getNodeType() == Node.ELEMENT_NODE && - child.getNamespaceURI() == null && - child.getNodeName().equals(name)) { - // Found it. Continue to the next inner segment. - element = child; - continue nextSegment; - } - } - // No such element. Create it. - if (value == null) { - // If value is null, we want to remove, not create and if can't find the - // element, then we're done: there's no such attribute to remove. - break nextAttribute; - } - - Element child = doc.createElement(name); - element = element.insertBefore(child, element.getFirstChild()); - } - - if (element == null) { - log.error(Severity.WARNING, docInfo, "Invalid injected attribute path: %s", path); - return; - } - - NamedNodeMap attrs = element.getAttributes(); - if (attrs != null) { - - - if (attrNsUri != null && attrNsUri.isEmpty()) { - attrNsUri = null; - } - Node attr = attrs.getNamedItemNS(attrNsUri, attrName); - - if (value == null) { - // We want to remove the attribute from the attribute map. - if (attr != null) { - attrs.removeNamedItemNS(attrNsUri, attrName); - } - - } else { - // We want to add or replace the attribute. - if (attr == null) { - attr = doc.createAttributeNS(attrNsUri, attrName); - if (attrNsUri != null) { - attr.setPrefix(XmlUtils.lookupNamespacePrefix(element, attrNsUri)); - } - attrs.setNamedItemNS(attr); - } - attr.setNodeValue(value); - } - } - } - } - - // ------- - - /** - * Flatten the element to a string. This "pretty prints" the XML tree starting - * from the given node and all its children and attributes. - *

- * The output is designed to be printed using {@link #printXmlDiff}. - * - * @param node The root node to print. - * @param nsPrefix A map that is filled with all the URI=>prefix found. - * The internal string only contains the expanded URIs but this is rather verbose - * so when printing the diff these will be replaced by the prefixes collected here. - * @param prefix A "space" prefix added at the beginning of each line for indentation - * purposes. The diff printer later relies on this to find out the structure. - */ - @NonNull - static String printElement( - @NonNull Node node, - @NonNull Map nsPrefix, - @NonNull String prefix) { - StringBuilder sb = new StringBuilder(); - sb.append(prefix).append('<'); - String uri = node.getNamespaceURI(); - if (uri != null) { - sb.append(uri).append(':'); - nsPrefix.put(uri, node.getPrefix()); - } - sb.append(node.getLocalName()); - printAttributes(sb, node, nsPrefix, prefix); - sb.append(">\n"); //$NON-NLS-1$ - printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$ - - sb.append(prefix).append("\n"); //$NON-NLS-1$ - - return sb.toString(); - } - - /** - * Flatten several children elements to a string. - * This is an implementation detail for {@link #printElement(Node, Map, String)}. - *

- * If {@code nextSiblings} is false, the string conversion takes only the given - * child element and stops there. - *

- * If {@code nextSiblings} is true, the string conversion also takes _all_ the siblings - * after the given element. The idea is the caller can call this with the first child - * of a parent and get a string showing all the children at the same time. They are - * sorted to avoid the ordering issue. - */ - @NonNull - private static StringBuilder printChildren( - @NonNull StringBuilder sb, - @NonNull Node child, - boolean nextSiblings, - @NonNull Map nsPrefix, - @NonNull String prefix) { - ArrayList children = new ArrayList(); - - boolean hasText = false; - for (; child != null; child = child.getNextSibling()) { - short t = child.getNodeType(); - if (nextSiblings && t == Node.TEXT_NODE) { - // We don't typically have meaningful text nodes in an Android manifest. - // If there are, just dump them as-is into the element representation. - // We do trim whitespace and ignore all-whitespace or empty text nodes. - String s = child.getNodeValue().trim(); - if (s.length() > 0) { - sb.append(s); - hasText = true; - } - } else if (t == Node.ELEMENT_NODE) { - children.add(printElement(child, nsPrefix, prefix)); - if (!nextSiblings) { - break; - } - } - } - - if (hasText) { - sb.append('\n'); - } - - if (!children.isEmpty()) { - Collections.sort(children); - for (String s : children) { - sb.append(s); - } - } - - return sb; - } - - /** - * Flatten several attributes to a string using their alphabethical order. - * This is an implementation detail for {@link #printElement(Node, Map, String)}. - */ - @NonNull - private static StringBuilder printAttributes( - @NonNull StringBuilder sb, - @NonNull Node node, - @NonNull Map nsPrefix, - @NonNull String prefix) { - ArrayList attrs = new ArrayList(); - - NamedNodeMap attrMap = node.getAttributes(); - if (attrMap != null) { - StringBuilder sb2 = new StringBuilder(); - for (int i = 0; i < attrMap.getLength(); i++) { - Node attr = attrMap.item(i); - if (attr instanceof Attr) { - sb2.setLength(0); - sb2.append('@'); - String uri = attr.getNamespaceURI(); - if (uri != null) { - sb2.append(uri).append(':'); - nsPrefix.put(uri, attr.getPrefix()); - } - sb2.append(attr.getLocalName()); - sb2.append("=\"").append(attr.getNodeValue()).append('\"'); //$NON-NLS-1$ - attrs.add(sb2.toString()); - } - } - } - - Collections.sort(attrs); - - for(String attr : attrs) { - sb.append('\n'); - sb.append(prefix).append(" ").append(attr); //$NON-NLS-1$ - } - return sb; - } - - //------------ - - /** - * Computes a quick diff between two strings generated by - * {@link #printElement(Node, Map, String)}. - *

- * This is a not designed to be a full contextual diff. - * It just stops at the first difference found, printing up to 3 lines of diff - * and backtracking to add prior contextual information to understand the - * structure of the element where the first diff line occured (by printing - * each parent found till the root one as well as printing the attribute - * named by {@code keyAttr}). - * - * @param sb The string builder where to output is written. - * @param expected The expected XML tree (as generated by {@link #printElement}.) - * For best result this would be the "destination" XML we're merging into, - * e.g. the main manifest. - * @param actual The actual XML tree (as generated by {@link #printElement}.) - * For best result this would be the "source" XML we're merging from, - * e.g. a library manifest. - * @param nsPrefixE The map of URI=>prefix for the expected XML tree. - * @param nsPrefixA The map of URI=>prefix for the actual XML tree. - * @param keyAttr An optional attribute *full* name (uri:local name) to always - * insert when writing the contextual lines before a diff line. - * For example when writing an Activity, it helps to always insert - * the "name" attribute since that's the key element to help the user - * identify which node is being dumped. - */ - static void printXmlDiff( - StringBuilder sb, - String expected, - String actual, - Map nsPrefixE, - Map nsPrefixA, - String keyAttr) { - String[] aE = expected.split("\n"); - String[] aA = actual.split("\n"); - int lE = aE.length; - int lA = aA.length; - int lm = lE < lA ? lA : lE; - boolean eofE = false; - boolean eofA = false; - boolean contextE = true; - boolean contextA = true; - int numDiff = 0; - - StringBuilder sE = new StringBuilder(); - StringBuilder sA = new StringBuilder(); - - outerLoop: for (int i = 0, iE = 0, iA = 0; i < lm; i++) { - if (iE < lE && iA < lA && aE[iE].equals(aA[iA])) { - if (numDiff > 0) { - // If we found a difference, stop now. - break outerLoop; - } - iE++; - iA++; - continue; - } else { - // Try to print some context for each side based on previous lines's space prefix. - if (contextE) { - if (iE > 0) { - String p = diffGetPrefix(aE[iE]); - for (int kE = iE-1; kE >= 0; kE--) { - if (!aE[kE].startsWith(p)) { - sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " "); - if (p.length() == 0) { - break; - } - p = diffGetPrefix(aE[kE]); - } else if (aE[kE].contains(keyAttr) || kE == 0) { - sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " "); - } - } - } - contextE = false; - } - if (iE >= lE) { - if (!eofE) { - sE.append("--(end reached)\n"); - eofE = true; - } - } else { - sE.append("--").append(diffReplaceNs(aE[iE++], nsPrefixE)).append('\n'); - } - - if (contextA) { - if (iA > 0) { - String p = diffGetPrefix(aA[iA]); - for (int kA = iA-1; kA >= 0; kA--) { - if (!aA[kA].startsWith(p)) { - sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " "); - p = diffGetPrefix(aA[kA]); - if (p.length() == 0) { - break; - } - } else if (aA[kA].contains(keyAttr) || kA == 0) { - sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " "); - } - } - } - contextA = false; - } - if (iA >= lA) { - if (!eofA) { - sA.append("++(end reached)\n"); - eofA = true; - } - } else { - sA.append("++").append(diffReplaceNs(aA[iA++], nsPrefixA)).append('\n'); - } - - // Dump up to 3 lines of difference - numDiff++; - if (numDiff == 3) { - break outerLoop; - } - } - } - - sb.append(sE); - sb.append(sA); - } - - /** - * Returns all the whitespace at the beginning of a string. - * Implementation details for {@link #printXmlDiff} used to find the "parent" - * element and include it in the context of the diff. - */ - private static String diffGetPrefix(String str) { - int pos = 0; - int len = str.length(); - while (pos < len && str.charAt(pos) == ' ') { - pos++; - } - return str.substring(0, pos); - } - - /** - * Simplifies a diff line by replacing NS URIs by their prefix. - * Implementation details for {@link #printXmlDiff}. - */ - private static String diffReplaceNs(String str, Map nsPrefix) { - for (Entry entry : nsPrefix.entrySet()) { - String uri = entry.getKey(); - String prefix = entry.getValue(); - if (prefix != null && str.contains(uri)) { - str = str.replaceAll(Pattern.quote(uri), Matcher.quoteReplacement(prefix)); - } - } - return str; - } - -} diff --git a/manifmerger/src/main/java/com/android/manifmerger/ArgvParser.java b/manifmerger/src/main/java/com/android/manifmerger/ArgvParser.java new file mode 100755 index 0000000..6d22f57 --- /dev/null +++ b/manifmerger/src/main/java/com/android/manifmerger/ArgvParser.java @@ -0,0 +1,116 @@ +/* + * 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.manifmerger; + +import com.android.sdklib.util.CommandLineParser; +import com.android.utils.ILogger; + +import java.util.List; + + +/** + * Specific command-line flags for the {@link ManifestMerger}. + */ +class ArgvParser extends CommandLineParser { + + /* + * Steps needed to add a new action: + * - Each action is defined as a "verb object" followed by parameters. + * - Either reuse a VERB_ constant or define a new one. + * - Either reuse an OBJECT_ constant or define a new one. + * - Add a new entry to mAction with a one-line help summary. + * - In the constructor, add a define() call for each parameter (either mandatory + * or optional) for the given action. + */ + + public final static String VERB_MERGE = "merge"; //$NON-NLS-1$ + public static final String KEY_OUT = "out"; //$NON-NLS-1$ + public static final String KEY_MAIN = "main"; //$NON-NLS-1$ + public static final String KEY_LIBS = "libs"; //$NON-NLS-1$ + + /** + * Action definitions for ManifestMerger command line. + *

+ * This list serves two purposes: first it is used to know which verb/object + * actions are acceptable on the command-line; second it provides a summary + * for each action that is printed in the help. + *

+ * Each entry is a string array with: + *

    + *
  • the verb. + *
  • an object (use #NO_VERB_OBJECT if there's no object). + *
  • a description. + *
  • an alternate form for the object (e.g. plural). + *
+ */ + private final static String[][] ACTIONS = { + + { VERB_MERGE, NO_VERB_OBJECT, + "Merge two or more manifests." }, + + }; + + public ArgvParser(ILogger logger) { + super(logger, ACTIONS); + + // The following defines the parameters of the actions defined in mAction. + + // --- merge manifest --- + + define(Mode.STRING, true, + VERB_MERGE, NO_VERB_OBJECT, "o", KEY_OUT, //$NON-NLS-1$ + "Output path (where to write the merged manifest). Use - for stdout.", null); + + define(Mode.STRING, true, + VERB_MERGE, NO_VERB_OBJECT, "1", KEY_MAIN, //$NON-NLS-1$ + "Path of the main manifest (what to merge *into*)", null); + + define(Mode.STRING_ARRAY, true, + VERB_MERGE, NO_VERB_OBJECT, "2", KEY_LIBS, //$NON-NLS-1$ + "Paths of library manifests to be merged into the main one.", + null); + } + + @Override + public boolean acceptLackOfVerb() { + return true; + } + + // -- some helpers for generic action flags + + /** Helper to retrieve the --out value. */ + public String getParamOut() { + return (String) getValue(null, null, KEY_OUT); + } + + /** Helper to retrieve the --main value. */ + public String getParamMain() { + return (String) getValue(null, null, KEY_MAIN); + } + + /** + * Helper to retrieve the --libs values. + */ + public String[] getParamLibs() { + Object v = getValue(null, null, KEY_LIBS); + if (v instanceof List) { + List a = (List) v; + return a.toArray(new String[a.size()]); + } + return null; + } +} diff --git a/manifmerger/src/main/java/com/android/manifmerger/ICallback.java b/manifmerger/src/main/java/com/android/manifmerger/ICallback.java new file mode 100755 index 0000000..26ae40d --- /dev/null +++ b/manifmerger/src/main/java/com/android/manifmerger/ICallback.java @@ -0,0 +1,37 @@ +/* + * 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.manifmerger; + +import com.android.annotations.NonNull; + +/** + * Callback used by the ManifestMerger to query the caller. + */ +public interface ICallback { + + public static final int UNKNOWN_CODENAME = 0; + + /** + * Queries the caller to find the API level for a given provisional API codename, + * as used in the <uses-sdk> {@code minSdkVersion} field. + * + * @param codename A non-null codename string. + * @return The integer API > 0 for the given codename, or {@link #UNKNOWN_CODENAME}. + */ + public int queryCodenameApiLevel(@NonNull String codename); + +} diff --git a/manifmerger/src/main/java/com/android/manifmerger/IMergerLog.java b/manifmerger/src/main/java/com/android/manifmerger/IMergerLog.java new file mode 100755 index 0000000..5402dd4 --- /dev/null +++ b/manifmerger/src/main/java/com/android/manifmerger/IMergerLog.java @@ -0,0 +1,164 @@ +/* + * 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.manifmerger; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; + + +/** + * Logger interface for the {@link ManifestMerger}. + */ +public interface IMergerLog { + + /** Severity of the error message. */ + public enum Severity { + /** + * A very low severity information. This does not stop processing. + * Clients might want to have a "not verbose" flag to not display this. + */ + INFO, + /** + * A warning. This does not stop processing. + * */ + WARNING, + /** + * A fatal error. + * The merger does not stop on errors, in an attempt to accumulate as much + * info as possible to return to the user. However in case even one error + * is generated the output should not be used, if any. + */ + ERROR + } + + /** + * Logs an error that occurred at a specific single manifest. + * + * @param severity Whether this is an actual error or a mere warning. + * @param location A file and line location of where the error was detected. + * @param message A message string, suitable for {@link String#format(String, Object...)}. + * @param msgParams The optional parameters for the {@code message} string. + */ + public abstract void error( + @NonNull Severity severity, + @NonNull FileAndLine location, + @NonNull String message, + Object...msgParams); + + /** + * Logs a conflict, that is an error that happens when comparing 2 manifests. + * + * @param severity Whether this is an actual error or a mere warning. + * @param location1 A file and line location of where the error was detected. + * By convention, location1 is generally the main manifest location. + * @param location2 A file and line location of where the error was detected. + * By convention, location2 is generally a library location. + * @param message A message string, suitable for {@link String#format(String, Object...)}. + * @param msgParams The optional parameters for the {@code message} string. + */ + public abstract void conflict( + @NonNull Severity severity, + @NonNull FileAndLine location1, + @NonNull FileAndLine location2, + @NonNull String message, + Object...msgParams); + + /** + * Information about the file and line number where an error ocurred. + */ + public static class FileAndLine { + private final String mFilePath; + private final int mLine; + + /** + * Constructs a new {@link FileAndLine}. + * + * @param filePath The file path. This is typically a file path when the + * merge is initiated via the {@code process(File...)} interface. + * When using the {@code process(Document...)} interface, this will be + * one of the magic constants {@link IMergerLog#LIBRARY} or + * {@link IMergerLog#MAIN_MANIFEST}. When that fails, null is used. + * @param line The line number where the error occurred in the XML file. + * Zero is used when the line number isn't known (e.g. when the error + * happens at the document level rather than on a specific element.) + */ + public FileAndLine(@Nullable String filePath, int line) { + mFilePath = filePath; + mLine = line; + } + + /** + * Returns the file path. + *

+ * This is typically a file path when the merge is initiated via the + * {@code process(File...)} interface. + * When using the {@code process(Document...)} interface, this will be + * one of the magic constants {@link IMergerLog#LIBRARY} or + * {@link IMergerLog#MAIN_MANIFEST}. + * When that fails, null is used. + */ + public @Nullable String getFileName() { + return mFilePath; + } + + /** + * Returns the line number where the error occurred in the XML file. + * Zero is used when the line number isn't known (e.g. when the error + * happens at the document level rather than on a specific element.) + */ + public int getLine() { + return mLine; + } + + /** + * Displays the information in the form "file:line". + */ + @Override + public String toString() { + String name = mFilePath; + if (MAIN_MANIFEST.equals(name)) { + name = "main manifest"; // translatable + } else if (LIBRARY.equals(name)) { + name = "library"; // translatable + } else if (name == null) { + name = "(Unknown)"; // translatable + } + if (mLine <= 0) { + return name; + } else { + return name + ':' + mLine; + } + } + } + + /** + * The reference to the "main manifest" used in {@link FileAndLine} when the + * path to the main manifest file isn't known. This happens when the + * {@link ManifestMerger} is called with the {@code process(Document...)} + * interface. + */ + public static final String MAIN_MANIFEST = "@main"; //$NON-NLS-1$ + + /** + * The reference to "a library" used in {@link FileAndLine} when the + * path to the library file isn't known. This happens when the + * {@link ManifestMerger} is called with the {@code process(Document...)} + * interface. + */ + public static final String LIBRARY = "@library"; //$NON-NLS-1$ + +} diff --git a/manifmerger/src/main/java/com/android/manifmerger/Main.java b/manifmerger/src/main/java/com/android/manifmerger/Main.java new file mode 100644 index 0000000..2a6460e --- /dev/null +++ b/manifmerger/src/main/java/com/android/manifmerger/Main.java @@ -0,0 +1,92 @@ +/* + * 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.manifmerger; + +import com.android.utils.ILogger; +import com.android.utils.StdLogger; + +import java.io.File; +import java.util.Map; + +/** + * Command-line entry point of the Manifest Merger. + * The goal of the manifest merger is to merge library manifest into a main application manifest. + * See {@link ManifestMerger} for the exact merging rules. + *

+ * The command-line version creates a {@link ManifestMerger} + * which takes file arguments from the command-line and dumps all errors and warnings on the + * stdout/stderr console. + *

+ * Usage:
+ * {@code $ manifmerger merge --main main_manifest.xml --libs lib1.xml lib2.xml --out result.xml} + *

+ * When used as a library, please call {@link ManifestMerger#process(File, File, File[], Map)} + * directly. + */ +public class Main { + + /** Logger object. Use this to print normal output, warnings or errors. Never null. */ + private ILogger mSdkLog; + /** Command line parser. Never null. */ + private ArgvParser mArgvParser; + + public static void main(String[] args) { + new Main().run(args); + } + + /** + * Runs the sdk manager app + */ + private void run(String[] args) { + createLogger(); + + mArgvParser = new ArgvParser(mSdkLog); + mArgvParser.parseArgs(args); + + // Create a new ManifestMerger and call its process method. + // It will take care of validating its own arguments. + ManifestMerger mm = new ManifestMerger(MergerLog.wrapSdkLog(mSdkLog), null); + + String[] libPaths = mArgvParser.getParamLibs(); + File[] libFiles = new File[libPaths.length]; + for (int n = libPaths.length - 1; n >= 0; n--) { + libFiles[n] = new File(libPaths[n]); + } + + boolean ok = mm.process( + new File(mArgvParser.getParamOut()), + new File(mArgvParser.getParamMain()), + libFiles, + null /*injectAttributes*/ + ); + System.exit(ok ? 0 : 1); + } + + /** + * Creates the {@link #mSdkLog} object. + * This logger prints to the attached console. + */ + private void createLogger() { + mSdkLog = new StdLogger(StdLogger.Level.VERBOSE); + } + + /** For testing */ + public void setLogger(ILogger logger) { + mSdkLog = logger; + } + +} diff --git a/manifmerger/src/main/java/com/android/manifmerger/ManifestMerger.java b/manifmerger/src/main/java/com/android/manifmerger/ManifestMerger.java new file mode 100755 index 0000000..073c768 --- /dev/null +++ b/manifmerger/src/main/java/com/android/manifmerger/ManifestMerger.java @@ -0,0 +1,1563 @@ +/* + * 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.manifmerger; + +import com.android.SdkConstants; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.manifmerger.IMergerLog.FileAndLine; +import com.android.manifmerger.IMergerLog.Severity; +import com.android.utils.XmlUtils; +import com.android.xml.AndroidXPathFactory; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; + +/** + * Merges a library manifest into a main application manifest. + *

+ * To use, create with {@link ManifestMerger#ManifestMerger(IMergerLog, ICallback)} then + * call {@link ManifestMerger#process(File, File, File[], Map)}. + *

+ *

 Merge operations:
+ * - root manifest: attributes ignored, warn if defined.
+ * - application:
+ *      G- {@code @attributes}: most attributes are ignored in libs
+ *          except: application:name if defined, it must match.
+ *          except: application:agentBackup if defined, it must match.
+ *          (these represent class names and we don't want a lib to assume their app or backup
+ *           classes are being used when that will never be the case.)
+ *      C- activity / activity-alias / service / receiver / provider
+ *          => Merge as-is. Error if exists in the destination (same {@code @name})
+ *             unless the definitions are exactly the same.
+ *             New elements are always merged at the end of the application element.
+ *          => Indicate if there's a dup.
+ *      D- uses-library
+ *          => Merge. OK if already exists same {@code @name}.
+ *          => Merge {@code @required}: true>false.
+ * A- instrumentation:
+ *      => Do not merge. ignore the ones from libs.
+ * C- permission / permission-group / permission-tree:
+ *      => Merge as-is. Error if exists in the destination (same {@code @name})
+ *         unless the definitions are exactly the same.
+ * C- uses-permission:
+ *      => Add. OK if already defined.
+ * E- uses-sdk:
+ *      {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
+ *                              Codenames are accepted if we can resolve their API level.
+ *      {@code @targetSdkVersion}: warning if dest<lib.
+ *                                 Never automatically change dest targetsdk.
+ *      {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
+ * D- uses-feature with {@code @name}:
+ *      => Merge with same {@code @name}
+ *      => Merge {@code @required}: true>false.
+ *      - Do not merge any {@code @glEsVersion} attribute at this point.
+ * F- uses-feature with {@code @glEsVersion}:
+ *      => Error if defined in lib+dest with dest<lib. Never automatically change dest.
+ * B- uses-configuration:
+ *      => There can be many. Error if source defines one that is not an exact match in dest.
+ *      (e.g. right now app must manually define something that matches exactly each lib)
+ * B- supports-screens / compatible-screens:
+ *      => Do not merge.
+ *      => Error (warn?) if defined in lib and not strictly the same as in dest.
+ * B- supports-gl-texture:
+ *      => Do not merge. Can have more than one.
+ *      => Error (warn?) if defined in lib and not present as-is in dest.
+ *
+ * Strategies:
+ * A = Ignore, do not merge (no-op).
+ * B = Do not merge but if defined in both must match equally.
+ * C = Must not exist in dest or be exactly the same (key is the {@code @name} attribute).
+ * D = Add new or merge with same key {@code @name}, adjust {@code @required} true>false.
+ * E, F, G = Custom strategies; see above.
+ *
+ * What happens when merging libraries with conflicting information?
+ * Say for example a main manifest has a minSdkVersion of 3, whereas libraries have
+ * a minSdkVersion of 4 and 11. We could have 2 point of views:
+ * - Play it safe: If we have a library with a minSdkVersion of 11, it means this
+ *   library code knows it can't work reliably on a lower API level. So the safest end
+ *   result would be a merged manifest with the highest minSdkVersion of all libraries.
+ * - Trust the main manifest: When an app declares a given minSdkVersion, it also expects
+ *   to run a given range of devices. If we change the final minSdkVersion, the app won't
+ *   be available on as many devices as the developer might expect. And as a counterpoint
+ *   to issue 1, the app may be careful and not call the library without checking the
+ *   necessary features or APIs are available before hand.
+ * Both points of views are conflicting. The solution taken here is to be conservative
+ * and generate an error rather than merge and change a value that might be surprising.
+ * On the other hand this can be problematic and force a developer to keep the main
+ * manifest in sync with the libraries ones, in essence reducing the usefulness of the
+ * automated merge to pure trivial cases. The idea is to just start this way and enhance
+ * or revisit the mechanism later.
+ * 
+ */ +public class ManifestMerger { + + /** Logger object. Never null. */ + private final IMergerLog mLog; + /** An optional callback that the merger can use to query the calling SDK. */ + private final ICallback mCallback; + private XPath mXPath; + private Document mMainDoc; + /** Option to extract our package prefixes in the merged manifest */ + private boolean mExtractPackagePrefix; + + /** Namespace for Android attributes in an AndroidManifest.xml */ + private static final String NS_URI = SdkConstants.NS_RESOURCES; + /** Prefix for the Android namespace to use in XPath expressions. */ + private static final String NS_PREFIX = AndroidXPathFactory.DEFAULT_NS_PREFIX; + /** Namespace used in XML files for Android Tooling attributes */ + private static final String TOOLS_URI = SdkConstants.TOOLS_URI; + /** The name of the tool:merge attribute, to either override or ignore merges. */ + private static final String MERGE_ATTR = "merge"; //$NON-NLS-1$ + /** tool:merge="override" means to ignore what comes from libraries and only keep the + * version from the main manifest. No conflict can be generated. */ + private static final String MERGE_OVERRIDE = "override"; //$NON-NLS-1$ + /** tool:merge="remove" means to remove a node and prevent merging -- not only is the + * node from the libraries not merged, but the element is removed from the main manifest. */ + private static final String MERGE_REMOVE = "remove"; //$NON-NLS-1$ + + /** + * Sets of element/attribute that need to be treated as class names. + * The attribute name must be the local name for the Android namespace. + * For example "application/name" maps to <application android:name=...>. + */ + private static final String[] sClassAttributes = { + "application/name", + "application/backupAgent", + "activity/name", + "activity-alias/name", + "receiver/name", + "service/name", + "provider/name", + "instrumentation/name" + }; + + /** + * Creates a new {@link ManifestMerger}. + * + * @param log A non-null merger log to capture all warnings, errors and their location. + * @param callback An optional callback that the merger can use to query the calling SDK. + */ + public ManifestMerger(@NonNull IMergerLog log, @Nullable ICallback callback) { + mLog = log; + mCallback = callback; + } + + /** + * Sets whether the manifest merger should extract package prefixes + * + * @param extract if true, extract package prefixes + * @return this, for constructor chaining + */ + public ManifestMerger setExtractPackagePrefix(boolean extract) { + mExtractPackagePrefix = extract; + return this; + } + + /** + * Performs the merge operation. + *

+ * This does NOT stop on errors, in an attempt to accumulate as much + * info as possible to return to the user. + * Unless it failed to read the main manifest, a result file will be + * created. However if process() returns false, the file should not + * be used except for debugging purposes. + * + * @param outputFile The output path to generate. Can be the same as the main path. + * @param mainFile The main manifest paths to read. What we merge into. + * @param libraryFiles The library manifest paths to read. Must not be null. + * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value. + * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name", + * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion". + * (note the space separator between the attribute URI and its local name.) + * The elements will be created if they don't exists. Existing attributes will be modified. + * The replacement is done on the main document before merging. + * @return True if the merge was completed, false otherwise. + */ + public boolean process( + File outputFile, + File mainFile, + File[] libraryFiles, + Map injectAttributes) { + Document mainDoc = MergerXmlUtils.parseDocument(mainFile, mLog); + if (mainDoc == null) { + return false; + } + + boolean success = process(mainDoc, libraryFiles, injectAttributes); + + if (!MergerXmlUtils.printXmlFile(mainDoc, outputFile, mLog)) { + success = false; + } + return success; + } + + /** + * Performs the merge operation in-place in the given DOM. + *

+ * This does NOT stop on errors, in an attempt to accumulate as much + * info as possible to return to the user. + *

+ * The method might modify the input XML document in-place for its own processing. + * + * @param mainDoc The document to merge into. Will be modified in-place. + * @param libraryFiles The library manifest paths to read. Must not be null. + * These will be modified in-place. + * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value. + * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name", + * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion". + * (note the space separator between the attribute URI and its local name.) + * The elements will be created if they don't exists. Existing attributes will be modified. + * The replacement is done on the main document before merging. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + public boolean process( + Document mainDoc, + File[] libraryFiles, + Map injectAttributes) { + + boolean success = true; + mMainDoc = mainDoc; + MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST); + MergerXmlUtils.injectAttributes(mainDoc, injectAttributes, mLog); + + String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES); + mXPath = AndroidXPathFactory.newXPath(prefix); + + expandFqcns(mainDoc); + for (File libFile : libraryFiles) { + Document libDoc = MergerXmlUtils.parseDocument(libFile, mLog); + if (libDoc == null || !mergeLibDoc(cleanupToolsAttributes(libDoc))) { + success = false; + } + } + + cleanupToolsAttributes(mainDoc); + + if (mExtractPackagePrefix) { + extractFqcns(mainDoc); + } + + mXPath = null; + mMainDoc = null; + return success; + } + + /** + * Performs the merge operation in-place in the given DOM. + *

+ * This does NOT stop on errors, in an attempt to accumulate as much + * info as possible to return to the user. + *

+ * The method might modify the input XML documents in-place for its own processing. + * + * @param mainDoc The document to merge into. Will be modified in-place. + * @param libraryDocs The library manifest documents to merge in. Must not be null. + * These will be modified in-place. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + public boolean process(@NonNull Document mainDoc, @NonNull Document... libraryDocs) { + + boolean success = true; + mMainDoc = mainDoc; + MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST); + + String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES); + mXPath = AndroidXPathFactory.newXPath(prefix); + + expandFqcns(mainDoc); + for (Document libDoc : libraryDocs) { + MergerXmlUtils.decorateDocument(libDoc, IMergerLog.LIBRARY); + if (!mergeLibDoc(cleanupToolsAttributes(libDoc))) { + success = false; + } + } + + cleanupToolsAttributes(mainDoc); + mXPath = null; + mMainDoc = null; + return success; + } + + // -------- + + /** + * Merges the given library manifest into the destination manifest. + * See {@link ManifestMerger} for merge details. + * + * @param libDoc The library document to merge from. Must not be null. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + private boolean mergeLibDoc(Document libDoc) { + + boolean err = false; + + expandFqcns(libDoc); + + // Strategy G (check is compatible) + err |= !checkApplication(libDoc); + + // Strategy B + err |= !doNotMergeCheckEqual("/manifest/uses-configuration", libDoc); //$NON-NLS-1$ + err |= !doNotMergeCheckEqual("/manifest/supports-screens", libDoc); //$NON-NLS-1$ + err |= !doNotMergeCheckEqual("/manifest/compatible-screens", libDoc); //$NON-NLS-1$ + err |= !doNotMergeCheckEqual("/manifest/supports-gl-texture", libDoc); //$NON-NLS-1$ + + boolean skipApplication = hasOverrideOrRemoveTag( + findFirstElement(mMainDoc, "/manifest/application")); //$NON-NLS-1$ + + // Strategy C + if (!skipApplication) { + err |= !mergeNewOrEqual( + "/manifest/application/activity", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + true); + err |= !mergeNewOrEqual( + "/manifest/application/activity-alias", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + true); + err |= !mergeNewOrEqual( + "/manifest/application/service", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + true); + err |= !mergeNewOrEqual( + "/manifest/application/receiver", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + true); + err |= !mergeNewOrEqual( + "/manifest/application/provider", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + true); + } + err |= !mergeNewOrEqual( + "/manifest/permission", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + false); + err |= !mergeNewOrEqual( + "/manifest/permission-group", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + false); + err |= !mergeNewOrEqual( + "/manifest/permission-tree", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + false); + err |= !mergeNewOrEqual( + "/manifest/uses-permission", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + libDoc, + false); + + // Strategy D + if (!skipApplication) { + err |= !mergeAdjustRequired( + "/manifest/application/uses-library", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + "required", //$NON-NLS-1$ + libDoc, + null /*alternateKeyAttr*/); + } + err |= !mergeAdjustRequired( + "/manifest/uses-feature", //$NON-NLS-1$ + "name", //$NON-NLS-1$ + "required", //$NON-NLS-1$ + libDoc, + "glEsVersion" /*alternateKeyAttr*/); + + // Strategy E + err |= !checkSdkVersion(libDoc); + + // Strategy F + err |= !checkGlEsVersion(libDoc); + + return !err; + } + + /** + * Expand all possible class names attributes in the given document. + *

+ * Some manifest attributes represent class names. These can be specified as fully + * qualified class names or use a short notation consisting of just the terminal + * class simple name or a dot followed by a partial class name. Unfortunately this + * makes textual comparison of the attributes impossible. To simplify this, we can + * modify the document to fully expand all these class names. The list of elements + * and attributes to process is listed by {@link #sClassAttributes} and the expansion + * simply consists of appending the manifest' package if defined. + * + * @param doc The document in which to expand potential FQCNs. + */ + private void expandFqcns(Document doc) { + // Find the package attribute of the manifest. + String pkg = null; + Element manifest = findFirstElement(doc, "/manifest"); + if (manifest != null) { + pkg = manifest.getAttribute("package"); + } + + if (pkg == null || pkg.length() == 0) { + // We can't adjust FQCNs if we don't know the root package name. + // It's not a proper manifest if this is missing anyway. + assert manifest != null; + mLog.error(Severity.WARNING, + xmlFileAndLine(manifest), + "Missing 'package' attribute in manifest."); + return; + } + + for (String elementAttr : sClassAttributes) { + String[] names = elementAttr.split("/"); + if (names.length != 2) { + continue; + } + String elemName = names[0]; + String attrName = names[1]; + NodeList elements = doc.getElementsByTagName(elemName); + for (int i = 0; i < elements.getLength(); i++) { + Node elem = elements.item(i); + if (elem instanceof Element) { + Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName); + if (attr != null) { + String value = attr.getNodeValue(); + + // We know it's a shortened FQCN if it starts with a dot + // or does not contain any dot. + if (value != null && value.length() > 0 && + (value.indexOf('.') == -1 || value.charAt(0) == '.')) { + if (value.charAt(0) == '.') { + value = pkg + value; + } else { + value = pkg + '.' + value; + } + attr.setNodeValue(value); + } + } + } + } + } + } + + /** + * Extracts the fully qualified class names from the manifest and uses the + * prefix notation relative to the manifest package. This basically reverses + * the effects of {@link #expandFqcns(Document)}, though of course it may + * also remove prefixes which were inlined in the original documents. + * + * @param doc the document in which to extract the FQCNs. + */ + private void extractFqcns(Document doc) { + // Find the package attribute of the manifest. + String pkg = null; + Element manifest = findFirstElement(doc, "/manifest"); + if (manifest != null) { + pkg = manifest.getAttribute("package"); + } + + if (pkg == null || pkg.length() == 0) { + return; + } + + int pkgLength = pkg.length(); + for (String elementAttr : sClassAttributes) { + String[] names = elementAttr.split("/"); + if (names.length != 2) { + continue; + } + String elemName = names[0]; + String attrName = names[1]; + NodeList elements = doc.getElementsByTagName(elemName); + for (int i = 0; i < elements.getLength(); i++) { + Node elem = elements.item(i); + if (elem instanceof Element) { + Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName); + if (attr != null) { + String value = attr.getNodeValue(); + + // We know it's a shortened FQCN if it starts with a dot + // or does not contain any dot. + if (value != null && value.length() > pkgLength && + value.startsWith(pkg) && value.charAt(pkgLength) == '.') { + value = value.substring(pkgLength); + attr.setNodeValue(value); + } + } + } + } + } + } + + /** + * Checks (but does not merge) the application attributes using the following rules: + *

+     * - {@code @name}: Ignore if empty. Warning if its expanded FQCN doesn't match the main doc.
+     * - {@code @backupAgent}:  Ignore if empty. Warning if its expanded FQCN doesn't match main doc.
+     * - All other attributes are ignored.
+     * 
+ * The name and backupAgent represent classes and the merger will warn since if a lib has + * these defined they will never be used anyway. + * @param libDoc The library document to merge from. Must not be null. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + private boolean checkApplication(Document libDoc) { + + Element mainApp = findFirstElement(mMainDoc, "/manifest/application"); //$NON-NLS-1$ + Element libApp = findFirstElement(libDoc, "/manifest/application"); //$NON-NLS-1$ + + // A manifest does not necessarily define an application. + // If the lib has none, there's nothing to check for. + if (libApp == null) { + return true; + } + if (hasOverrideOrRemoveTag(mainApp)) { + // Don't check the element since it is tagged with override or remove. + return true; + } + + for (String attrName : new String[] { "name", "backupAgent" }) { + String libValue = getAttributeValue(libApp, attrName); + if (libValue == null || libValue.length() == 0) { + // Nothing to do if the attribute is not defined in the lib. + continue; + } + // The main doc does not have to have an application node. + String mainValue = mainApp == null ? "" : getAttributeValue(mainApp, attrName); + if (!libValue.equals(mainValue)) { + assert mainApp != null; + mLog.conflict(Severity.WARNING, + xmlFileAndLine(mainApp), + xmlFileAndLine(libApp), + mainApp == null ? + "Library has but main manifest has no application element." : + "Main manifest has but library uses %1$s='%3$s'.", + attrName, + mainValue, + libValue); + } + } + + return true; + } + + /** + * Do not merge anything. Instead it checks that the requested elements from the + * given library are all present and equal in the destination and prints a warning + * if it's not the case. + *

+ * For example if a library supports a given screen configuration, print a + * warning if the main manifest doesn't indicate the app supports the same configuration. + * We should not merge it since we don't want to silently give the impression an app + * supports a configuration just because it uses a library which does. + * On the other hand we don't want to silently ignore this fact. + *

+ * TODO there should be a way to silence this warning. + * The current behavior is certainly arbitrary and needs to be tweaked somehow. + * + * @param path The XPath of the elements to merge from the library. Must not be null. + * @param libDoc The library document to merge from. Must not be null. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + private boolean doNotMergeCheckEqual(String path, Document libDoc) { + + for (Element src : findElements(libDoc, path)) { + + boolean found = false; + + for (Element dest : findElements(mMainDoc, path)) { + if (hasOverrideOrRemoveTag(dest)) { + continue; + } + if (compareElements(dest, src, false, null /*diff*/, null /*keyAttr*/)) { + found = true; + break; + } + } + + if (!found) { + mLog.conflict(Severity.WARNING, + xmlFileAndLine(mMainDoc), + xmlFileAndLine(src), + "%1$s defined in library, missing from main manifest:\n%2$s", + path, + MergerXmlUtils.dump(src, false /*nextSiblings*/)); + } + } + + return true; + } + + /** + * Merges the requested elements from the library in the main document. + * The key attribute name is used to identify the same elements. + * Merged elements must either not exist in the destination or be identical. + *

+ * When merging, append to the end of the application element. + * Also merges any preceding whitespace and up to one comment just prior to the merged element. + * + * @param path The XPath of the elements to merge from the library. Must not be null. + * @param keyAttr The Android-namespace attribute used as key to identify similar elements. + * E.g. "name" for "android:name" + * @param libDoc The library document to merge from. Must not be null. + * @param warnDups When true, will print a warning when a library definition is already + * present in the destination and is equal. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + private boolean mergeNewOrEqual( + String path, + String keyAttr, + Document libDoc, + boolean warnDups) { + + // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment" + int pos = path.lastIndexOf('/'); + assert pos > 1; + String parentPath = path.substring(0, pos); + Element parent = findFirstElement(mMainDoc, parentPath); + assert parent != null; + if (parent == null) { + mLog.error(Severity.ERROR, + xmlFileAndLine(mMainDoc), + "Could not find element %1$s.", + parentPath); + return false; + } + + boolean success = true; + + nextSource: for (Element src : findElements(libDoc, path)) { + String name = getAttributeValue(src, keyAttr); + if (name.length() == 0) { + mLog.error(Severity.ERROR, + xmlFileAndLine(src), + "Undefined '%1$s' attribute in %2$s.", + keyAttr, path); + success = false; + continue; + } + + // Look for the same item in the destination + List dests = findElements(mMainDoc, path, keyAttr, name); + if (dests.size() > 1) { + // This should not be happening. We'll just use the first one found in this case. + mLog.error(Severity.WARNING, + xmlFileAndLine(dests.get(0)), + "Manifest has more than one %1$s[@%2$s=%3$s] element.", + path, keyAttr, name); + } + boolean doMerge = true; + for (Element dest : dests) { + // Don't try to merge this element since it has tools:merge=override|remove. + if (hasOverrideOrRemoveTag(dest)) { + doMerge = false; + continue; + } + // If there's already a similar node in the destination, check it's identical. + StringBuilder diff = new StringBuilder(); + if (compareElements(dest, src, false, diff, keyAttr)) { + // Same element. Skip. + if (warnDups) { + mLog.conflict(Severity.INFO, + xmlFileAndLine(dest), + xmlFileAndLine(src), + "Skipping identical %1$s[@%2$s=%3$s] element.", + path, keyAttr, name); + } + continue nextSource; + } else { + // Print the diff we got from the comparison. + mLog.conflict(Severity.ERROR, + xmlFileAndLine(dest), + xmlFileAndLine(src), + "Trying to merge incompatible %1$s[@%2$s=%3$s] element:\n%4$s", + path, keyAttr, name, diff.toString()); + success = false; + continue nextSource; + } + } + + if (doMerge) { + // Ready to merge element src. Select which previous siblings to merge. + Node start = selectPreviousSiblings(src); + + insertAtEndOf(parent, start, src); + } + } + + return success; + } + + /** + * Returns the value of the given "android:attribute" in the given element. + * + * @param element The non-null element where to extract the attribute. + * @param attrName The local name of the attribute. + * It must use the {@link #NS_URI} but no prefix should be specified here. + * @return The value of the attribute or a non-null empty string if not found. + */ + private String getAttributeValue(Element element, String attrName) { + Attr attr = element.getAttributeNodeNS(NS_URI, attrName); + String value = attr == null ? "" : attr.getNodeValue(); //$NON-NLS-1$ + return value; + } + + /** + * Merge elements as identified by their key name attribute. + * The element must have an option boolean "required" attribute which can be either "true" or + * "false". Default is true if the attribute is missisng. When merging, a "false" is superseded + * by a "true" (explicit or implicit). + *

+ * When merging, this does NOT merge any other attributes than {@code keyAttr} and + * {@code requiredAttr}. + * + * @param path The XPath of the elements to merge from the library. Must not be null. + * @param keyAttr The Android-namespace attribute used as key to identify similar elements. + * E.g. "name" for "android:name" + * @param requiredAttr The name of the Android-namespace boolean attribute that must be merged. + * Typically should be "required". + * @param libDoc The library document to merge from. Must not be null. + * @param alternateKeyAttr When non-null, this is an alternate valid key attribute. If the + * default key attribute is missing, we won't output a warning if the alternate one is + * present. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + private boolean mergeAdjustRequired( + String path, + String keyAttr, + String requiredAttr, + Document libDoc, + @Nullable String alternateKeyAttr) { + + // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment" + int pos = path.lastIndexOf('/'); + assert pos > 1; + String parentPath = path.substring(0, pos); + Element parent = findFirstElement(mMainDoc, parentPath); + assert parent != null; + if (parent == null) { + mLog.error(Severity.ERROR, + xmlFileAndLine(mMainDoc), + "Could not find element %1$s.", + parentPath); + return false; + } + + boolean success = true; + + for (Element src : findElements(libDoc, path)) { + Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr); + String name = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ + if (name.length() == 0) { + if (alternateKeyAttr != null) { + attr = src.getAttributeNodeNS(NS_URI, alternateKeyAttr); + String s = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ + if (s.length() != 0) { + // This element lacks the keyAttr but has the alternateKeyAttr. Skip it. + continue; + } + } + + mLog.error(Severity.ERROR, + xmlFileAndLine(src), + "Undefined '%1$s' attribute in %2$s.", + keyAttr, path); + success = false; + continue; + } + + // Look for the same item in the destination + List dests = findElements(mMainDoc, path, keyAttr, name); + if (dests.size() > 1) { + // This should not be happening. We'll just use the first one found in this case. + mLog.error(Severity.WARNING, + xmlFileAndLine(dests.get(0)), + "Manifest has more than one %1$s[@%2$s=%3$s] element.", + path, keyAttr, name); + } + if (dests.size() > 0) { + + attr = src.getAttributeNodeNS(NS_URI, requiredAttr); + String value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$ + if (value == null || !(value.equals("true") || value.equals("false"))) { + mLog.error(Severity.WARNING, + xmlFileAndLine(src), + "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.", + requiredAttr, path, keyAttr, name, value); + continue; + } + boolean boolE = Boolean.parseBoolean(value); + + for (Element dest : dests) { + // Don't try to merge this element since it has tools:merge=override|remove. + if (hasOverrideOrRemoveTag(dest)) { + continue; + } + + // Compare the required attributes. + attr = dest.getAttributeNodeNS(NS_URI, requiredAttr); + value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$ + if (value == null || !(value.equals("true") || value.equals("false"))) { + mLog.error(Severity.WARNING, + xmlFileAndLine(dest), + "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.", + requiredAttr, path, keyAttr, name, value); + continue; + } + boolean boolD = Boolean.parseBoolean(value); + + if (!boolD && boolE) { + // Required attributes differ: destination is false and source was true + // so we need to change the destination to true. + + // If attribute was already in the destination, change it in place + if (attr != null) { + attr.setNodeValue("true"); //$NON-NLS-1$ + } else { + // Otherwise, do nothing. The destination doesn't have the + // required=true attribute, and true is the default value. + // Consequently not setting is the right thing to do. + + // -- code snippet for reference -- + // If we wanted to create a new attribute, we'd use the code + // below. There's a simpler call to d.setAttributeNS(ns, name, value) + // but experience shows that it would create a new prefix out of the + // blue instead of looking it up. + // + // Attr a=d.getOwnerDocument().createAttributeNS(NS_URI, requiredAttr); + // String prefix = d.lookupPrefix(NS_URI); + // if (prefix != null) { + // a.setPrefix(prefix); + // } + // a.setValue("true"); //$NON-NLS-1$ + // d.setAttributeNodeNS(attr); + } + } + } + } else { + // Destination doesn't exist. We simply merge the source element. + // Select which previous siblings to merge. + Node start = selectPreviousSiblings(src); + + Node node = insertAtEndOf(parent, start, src); + + NamedNodeMap attrs = node.getAttributes(); + if (attrs != null) { + for (int i = 0; i < attrs.getLength(); i++) { + Node a = attrs.item(i); + if (a.getNodeType() == Node.ATTRIBUTE_NODE) { + boolean keep = NS_URI.equals(a.getNamespaceURI()); + if (keep) { + name = a.getLocalName(); + keep = keyAttr.equals(name) || requiredAttr.equals(name); + } + if (!keep) { + attrs.removeNamedItemNS(NS_URI, name); + // Restart the loop from index 0 since there's no + // guarantee on the order of the nodes in the "map". + // This makes it O(n+2n) at most, where n is [2..3] in + // a typical case. + i = -1; + } + } + } + } + } + } + + return success; + } + + + + /** + * Checks (but does not merge) uses-feature glEsVersion attribute using the following rules: + *

+     * - Error if defined in lib+dest with dest<lib.
+     * - Never automatically change dest.
+     * - Default implied value is 1.0 (0x00010000).
+     * 
+ * + * @param libDoc The library document to merge from. Must not be null. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + private boolean checkGlEsVersion(Document libDoc) { + + String parentPath = "/manifest"; //$NON-NLS-1$ + Element parent = findFirstElement(mMainDoc, parentPath); + assert parent != null; + if (parent == null) { + mLog.error(Severity.ERROR, + xmlFileAndLine(mMainDoc), + "Could not find element %1$s.", + parentPath); + return false; + } + + // Find the max glEsVersion on the destination side + String path = "/manifest/uses-feature"; //$NON-NLS-1$ + String keyAttr = "glEsVersion"; //$NON-NLS-1$ + long destGlEsVersion = 0x00010000L; // default minimum is 1.0 + Element destNode = null; + boolean result = true; + for (Element dest : findElements(mMainDoc, path)) { + Attr attr = dest.getAttributeNodeNS(NS_URI, keyAttr); + String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ + if (value.length() != 0) { + try { + // Note that the value can be an hex number such as 0x00020001 so we + // need Integer.decode instead of Integer.parseInt. + // Note: Integer.decode cannot handle "ffffffff", see JDK issue 6624867 + // so we just treat the version as a long and test like this, ignoring + // the fact that a value of 0xFFFF/.0xFFFF is probably invalid anyway + // in the context of glEsVersion. + long version = Long.decode(value); + if (version >= destGlEsVersion) { + destGlEsVersion = version; + destNode = dest; + } else if (version < 0x00010000) { + mLog.error(Severity.WARNING, + xmlFileAndLine(dest), + "Ignoring because it's smaller than 1.0.", + value); + } + } catch (NumberFormatException e) { + // Note: NumberFormatException.toString() has no interesting information + // so we don't output it. + mLog.error(Severity.ERROR, + xmlFileAndLine(dest), + "Failed to parse : must be an integer in the form 0x00020001.", + value); + result = false; + } + } + } + + // If we found at least one valid with no error, use that, otherwise bail out. + if (!result && destNode == null) { + return false; + } + + // Now find the max glEsVersion on the source side. + + long srcGlEsVersion = 0x00010000L; // default minimum is 1.0 + Element srcNode = null; + result = true; + for (Element src : findElements(libDoc, path)) { + Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr); + String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$ + if (value.length() != 0) { + try { + // See comment on Long.decode above. + long version = Long.decode(value); + if (version >= srcGlEsVersion) { + srcGlEsVersion = version; + srcNode = src; + } else if (version < 0x00010000) { + mLog.error(Severity.WARNING, + xmlFileAndLine(src), + "Ignoring because it's smaller than 1.0.", + value); + } + } catch (NumberFormatException e) { + // Note: NumberFormatException.toString() has no interesting information + // so we don't output it. + mLog.error(Severity.ERROR, + xmlFileAndLine(src), + "Failed to parse : must be an integer in the form 0x00020001.", + value); + result = false; + } + } + } + + if (srcNode != null && destGlEsVersion < srcGlEsVersion) { + mLog.conflict(Severity.WARNING, + xmlFileAndLine(destNode == null ? mMainDoc : destNode), + xmlFileAndLine(srcNode), + "Main manifest has but library uses glEsVersion='0x%2$08x'%3$s", + destGlEsVersion, + srcGlEsVersion, + destNode != null ? "" : //$NON-NLS-1$ + "\nNote: main manifest lacks a declaration, and thus defaults to glEsVersion=0x00010000." + ); + result = false; + } + + return result; + } + + /** + * Checks (but does not merge) uses-sdk attributes using the following rules: + *
+     * - {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
+     * - {@code @targetSdkVersion}: warning if dest<lib. Never automatically change destination.
+     * - {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
+     * - The API level can be a codename if we have a callback that can convert it to an integer.
+     * 
+ * @param libDoc The library document to merge from. Must not be null. + * @return True on success, false if any error occurred (printed to the {@link IMergerLog}). + */ + private boolean checkSdkVersion(Document libDoc) { + + boolean result = true; + + Element destUsesSdk = findFirstElement(mMainDoc, "/manifest/uses-sdk"); //$NON-NLS-1$ + + if (hasOverrideOrRemoveTag(destUsesSdk)) { + // Don't try to check this element since it has tools:merge=override|remove. + return true; + } + + Element srcUsesSdk = findFirstElement(libDoc, "/manifest/uses-sdk"); //$NON-NLS-1$ + + AtomicInteger destValue = new AtomicInteger(1); + AtomicInteger srcValue = new AtomicInteger(1); + AtomicBoolean destImplied = new AtomicBoolean(true); + AtomicBoolean srcImplied = new AtomicBoolean(true); + + // Check minSdkVersion + int destMinSdk = 1; + result = extractSdkVersionAttribute( + libDoc, + destUsesSdk, srcUsesSdk, + "min", //$NON-NLS-1$ + destValue, srcValue, + destImplied, srcImplied); + + if (result) { + // Make it an error for an application to use a library with a greater + // minSdkVersion. This means the library code may crash unexpectedly. + // TODO it would be nice to be able to work around this in case the + // user think s/he knows what s/he's doing. + // We could define a simple XML comment flag: + + destMinSdk = destValue.get(); + + if (destMinSdk < srcValue.get()) { + mLog.conflict(Severity.ERROR, + xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk), + xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk), + "Main manifest has but library uses minSdkVersion='%2$d'%3$s", + destMinSdk, + srcValue.get(), + !destImplied.get() ? "" : //$NON-NLS-1$ + "\nNote: main manifest lacks a declaration, which defaults to value 1." + ); + result = false; + } + } + + // Check targetSdkVersion. + + // Note that destValue/srcValue purposely defaults to whatever minSdkVersion was last read + // since that's their definition when missing. + destImplied.set(true); + srcImplied.set(true); + + boolean result2 = extractSdkVersionAttribute( + libDoc, + destUsesSdk, srcUsesSdk, + "target", //$NON-NLS-1$ + destValue, srcValue, + destImplied, srcImplied); + + result &= result2; + if (result2) { + // Make it a warning for an application to use a library with a greater + // targetSdkVersion. + + int destTargetSdk = destImplied.get() ? destMinSdk : destValue.get(); + + if (destTargetSdk < srcValue.get()) { + mLog.conflict(Severity.WARNING, + xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk), + xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk), + "Main manifest has but library uses targetSdkVersion='%2$d'%3$s", + destTargetSdk, + srcValue.get(), + !destImplied.get() ? "" : //$NON-NLS-1$ + "\nNote: main manifest lacks a declaration, which defaults to value minSdkVersion or 1." + ); + result = false; + } + } + + return result; + } + + /** + * Implementation detail for {@link #checkSdkVersion(Document)}. + * Note that the various atomic out-variables must be preset to their default before + * the call. + *

+ * destValue/srcValue will be filled with the integer value of the field, if present + * and a correct number, in which case destImplied/destImplied are also set to true. + * Otherwise the values and the implied variables are left untouched. + */ + private boolean extractSdkVersionAttribute( + Document libDoc, + Element destUsesSdk, + Element srcUsesSdk, + String attr, + AtomicInteger destValue, + AtomicInteger srcValue, + AtomicBoolean destImplied, + AtomicBoolean srcImplied) { + String s = destUsesSdk == null ? "" //$NON-NLS-1$ + : destUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$ + + boolean result = true; + assert s != null; + s = s.trim(); + try { + if (s.length() > 0) { + destValue.set(Integer.parseInt(s)); + destImplied.set(false); + } + } catch (NumberFormatException e) { + boolean error = true; + if (mCallback != null) { + // Versions can contain codenames such as "JellyBean". + // We'll accept it only if have a callback that can give us the API level for it. + int apiLevel = mCallback.queryCodenameApiLevel(s); + if (apiLevel > ICallback.UNKNOWN_CODENAME) { + destValue.set(apiLevel); + destImplied.set(false); + error = false; + } + } + if (error) { + // Note: NumberFormatException.toString() has no interesting information + // so we don't output it. + mLog.error(Severity.ERROR, + xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk), + "Failed to parse : must be an integer number or codename.", + attr, + s); + result = false; + } + } + + s = srcUsesSdk == null ? "" //$NON-NLS-1$ + : srcUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$ + assert s != null; + s = s.trim(); + try { + if (s.length() > 0) { + srcValue.set(Integer.parseInt(s)); + srcImplied.set(false); + } + } catch (NumberFormatException e) { + boolean error = true; + if (mCallback != null) { + // Versions can contain codenames such as "JellyBean". + // We'll accept it only if have a callback that can give us the API level for it. + int apiLevel = mCallback.queryCodenameApiLevel(s); + if (apiLevel > ICallback.UNKNOWN_CODENAME) { + srcValue.set(apiLevel); + srcImplied.set(false); + error = false; + } + } + if (error) { + mLog.error(Severity.ERROR, + xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk), + "Failed to parse : must be an integer number or codename.", + attr, + s); + result = false; + } + } + + return result; + } + + + // ----- + + + /** + * Given an element E, select which previous siblings we want to merge. + * We want to include any whitespace up to the closing of the previous element. + * We also want to include up preceding comment nodes and their preceding whitespace. + *

+ * This may returns either {@code end} or a previous sibling. Never returns null. + */ + @NonNull + private Node selectPreviousSiblings(Node end) { + + Node start = end; + Node prev = start.getPreviousSibling(); + while (prev != null) { + short t = prev.getNodeType(); + if (t == Node.TEXT_NODE) { + String text = prev.getNodeValue(); + if (text == null || text.trim().length() != 0) { + // Not whitespace, we don't want it. + break; + } + } else if (t == Node.COMMENT_NODE) { + // It's a comment. We'll take it. + } else { + // Not a comment node nor a whitespace text. We don't want it. + break; + } + start = prev; + prev = start.getPreviousSibling(); + } + + return start; + } + + /** + * Inserts all siblings from {@code start} to {@code end} at the end + * of the given destination element. + *

+ * Implementation detail: this clones the source nodes into the destination. + * + * @param dest The destination at the end of which to insert. Cannot be null. + * @param start The first element to insert. Must not be null. + * @param end The last element to insert (included). Must not be null. + * Must be a direct "next sibling" of the start node. + * Can be equal to the start node to insert just that one node. + * @return The copy of the {@code end} node in the destination document or null + * if no such copy was created and added to the destination. + */ + private Node insertAtEndOf(Element dest, Node start, Node end) { + // Check whether we'll need to adjust URI prefixes + String destPrefix = XmlUtils.lookupNamespacePrefix(mMainDoc, NS_URI); + String srcPrefix = XmlUtils.lookupNamespacePrefix(start.getOwnerDocument(), NS_URI); + boolean needPrefixChange = destPrefix != null && !destPrefix.equals(srcPrefix); + + // First let's figure out the insertion point. + // We want the end of the last 'content' element of the + // destination element and basically we want to insert right + // before the last whitespace of the destination element. + Node target = dest.getLastChild(); + while (target != null) { + if (target.getNodeType() == Node.TEXT_NODE) { + String text = target.getNodeValue(); + if (text == null || text.trim().length() != 0) { + // Not whitespace, insert after. + break; + } + } else { + // Not text. Insert after + break; + } + target = target.getPreviousSibling(); + } + if (target != null) { + target = target.getNextSibling(); + } + + // Destination and start..end must not be part of the same document + // because we try to import below. If they were, it would mess the + // structure. + assert dest.getOwnerDocument() == mMainDoc; + assert dest.getOwnerDocument() != start.getOwnerDocument(); + assert start.getOwnerDocument() == end.getOwnerDocument(); + + while (start != null) { + Node node = mMainDoc.importNode(start, true /*deep*/); + if (needPrefixChange) { + changePrefix(node, srcPrefix, destPrefix); + } + dest.insertBefore(node, target); + + if (start == end) { + return node; + } + start = start.getNextSibling(); + } + return null; + } + + /** + * Changes the namespace prefix of all nodes, recursively. + * + * @param node The node to process, as well as all it's descendants. Can be null. + * @param srcPrefix The prefix to match. + * @param destPrefix The new prefix to replace with. + */ + private void changePrefix(Node node, String srcPrefix, String destPrefix) { + for (; node != null; node = node.getNextSibling()) { + if (srcPrefix.equals(node.getPrefix())) { + node.setPrefix(destPrefix); + } + Node child = node.getFirstChild(); + if (child != null) { + changePrefix(child, srcPrefix, destPrefix); + } + } + } + + /** + * Compares two {@link Element}s recursively. + * They must be identical with the same structure. + * Order should not matter. + * Whitespace and comments are ignored. + * + * @param expected The first element to compare. + * @param actual The second element to compare with. + * @param nextSiblings If true, will also compare the following siblings. + * If false, it will just compare the given node. + * @param diff An optional {@link StringBuilder} where to accumulate a diff output. + * @param keyAttr An optional key attribute to always add to elements when dumping a diff. + * @return True if {@code e1} and {@code e2} are equal. + */ + private boolean compareElements( + @NonNull Node expected, + @NonNull Node actual, + boolean nextSiblings, + @Nullable StringBuilder diff, + @Nullable String keyAttr) { + Map nsPrefixE = new HashMap(); + Map nsPrefixA = new HashMap(); + String sE = MergerXmlUtils.printElement(expected, nsPrefixE, ""); //$NON-NLS-1$ + String sA = MergerXmlUtils.printElement(actual, nsPrefixA, ""); //$NON-NLS-1$ + if (sE.equals(sA)) { + return true; + } else { + if (diff != null) { + MergerXmlUtils.printXmlDiff(diff, sE, sA, nsPrefixE, nsPrefixA, NS_URI + ':' + keyAttr); + } + return false; + } + } + + /** + * Finds the first element matching the given XPath expression in the given document. + * + * @param doc The document where to find the expression. + * @param path The XPath expression. It must yield an {@link Element} node type. + * @return The {@link Element} found or null. + */ + @Nullable + private Element findFirstElement( + @NonNull Document doc, + @NonNull String path) { + Node result; + try { + result = (Node) mXPath.evaluate(path, doc, XPathConstants.NODE); + if (result instanceof Element) { + return (Element) result; + } + + if (result != null) { + mLog.error(Severity.ERROR, + xmlFileAndLine(doc), + "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$ + result.getClass().getName(), path); + } + } catch (XPathExpressionException e) { + mLog.error(Severity.ERROR, + xmlFileAndLine(doc), + "XPath error on expr %s: %s", //$NON-NLS-1$ + path, e.toString()); + } + return null; + } + + /** + * Finds zero or more elements matching the given XPath expression in the given document. + * + * @param doc The document where to find the expression. + * @param path The XPath expression. Only {@link Element}s nodes will be returned. + * @return A list of {@link Element} found, possibly empty but never null. + */ + private List findElements( + @NonNull Document doc, + @NonNull String path) { + return findElements(doc, path, null, null); + } + + + /** + * Finds zero or more elements matching the given XPath expression in the given document. + *

+ * Furthermore, the elements must have an attribute matching the given attribute name + * and value if provided. (If you don't need to match an attribute, use the other version.) + *

+ * Note that if you provide {@code attrName} as non-null then the {@code attrValue} + * must be non-null too. In this case the XPath expression will be modified to add + * the check by naively appending a "[name='value']" filter. + * + * @param doc The document where to find the expression. + * @param path The XPath expression. Only {@link Element}s nodes will be returned. + * @param attrName The name of the optional attribute to match. Can be null. + * @param attrValue The value of the optional attribute to match. + * Can be null if {@code attrName} is null, otherwise must be non-null. + * @return A list of {@link Element} found, possibly empty but never null. + * + * @see #findElements(Document, String) + */ + private List findElements( + @NonNull Document doc, + @NonNull String path, + @Nullable String attrName, + @Nullable String attrValue) { + List elements = new ArrayList(); + + if (attrName != null) { + assert attrValue != null; + // Generate expression /manifest/application/activity[@android:name='my.fqcn'] + path = String.format("%1$s[@%2$s:%3$s='%4$s']", //$NON-NLS-1$ + path, NS_PREFIX, attrName, attrValue); + } + + try { + NodeList results = (NodeList) mXPath.evaluate(path, doc, XPathConstants.NODESET); + if (results != null && results.getLength() > 0) { + for (int i = 0; i < results.getLength(); i++) { + Node n = results.item(i); + assert n instanceof Element; + if (n instanceof Element) { + elements.add((Element) n); + } else { + mLog.error(Severity.ERROR, + xmlFileAndLine(doc), + "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$ + n.getClass().getName(), path); + } + } + } + + } catch (XPathExpressionException e) { + mLog.error(Severity.ERROR, + xmlFileAndLine(doc), + "XPath error on expr %s: %s", //$NON-NLS-1$ + path, e.toString()); + } + + return elements; + } + + /** + * Returns a new {@link FileAndLine} structure that identifies + * the base filename & line number from which the XML node was parsed. + *

+ * When the line number is unknown (e.g. if a {@link Document} instance is given) + * then line number 0 will be used. + * + * @param node The node or document where the error occurs. Must not be null. + * @return A new non-null {@link FileAndLine} combining the file name and line number. + */ + private @NonNull FileAndLine xmlFileAndLine(@NonNull Node node) { + return MergerXmlUtils.xmlFileAndLine(node); + } + + /** + * Checks whether the given element has a tools:merge=override or tools:merge=remove attribute. + * @param node The node to check. + * @return True if the element has a tools:merge=override or tools:merge=remove attribute. + */ + private boolean hasOverrideOrRemoveTag(@Nullable Node node) { + if (node == null || node.getNodeType() != Node.ELEMENT_NODE) { + return false; + } + NamedNodeMap attrs = node.getAttributes(); + Node merge = attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR); + String value = merge == null ? null : merge.getNodeValue(); + return MERGE_OVERRIDE.equals(value) || MERGE_REMOVE.equals(value); + } + + /** + * Cleans up all tools attributes from the given node hierarchy. + *

+ * If an element is marked with tools:merge=override, this attribute is removed. + * If an element is marked with tools:merge=remove, the whole element is removed. + * + * @param root The root node to parse and edit, recursively. + */ + private void cleanupToolsAttributes(@Nullable Node root) { + if (root == null) { + return; + } + NamedNodeMap attrs = root.getAttributes(); + if (attrs != null) { + for (int i = attrs.getLength() - 1; i >= 0; i--) { + Node attr = attrs.item(i); + if (SdkConstants.XMLNS_URI.equals(attr.getNamespaceURI()) && + TOOLS_URI.equals(attr.getNodeValue())) { + attrs.removeNamedItem(attr.getNodeName()); + } else if (TOOLS_URI.equals(attr.getNamespaceURI()) && + MERGE_ATTR.equals(attr.getLocalName())) { + attrs.removeNamedItem(attr.getNodeName()); + } + } + assert attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR) == null; + } + + for (Node child = root.getFirstChild(); child != null; ) { + if (child.getNodeType() != Node.ELEMENT_NODE) { + child = child.getNextSibling(); + continue; + } + attrs = child.getAttributes(); + Node merge = attrs == null ? null : attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR); + String value = merge == null ? null : merge.getNodeValue(); + Node sibling = child.getNextSibling(); + if (MERGE_REMOVE.equals(value)) { + // Note: save the previous sibling since removing the child will clear its siblings. + Node prev = child.getPreviousSibling(); + root.removeChild(child); + // If there's some whitespace just before that element, clean it up too. + while (prev != null && prev.getNodeType() == Node.TEXT_NODE) { + if (prev.getNodeValue().trim().length() == 0) { + Node prevPrev = prev.getPreviousSibling(); + root.removeChild(prev); + prev = prevPrev; + } else { + break; + } + } + } else { + cleanupToolsAttributes(child); + } + child = sibling; + } + } + + /** + * @see #cleanupToolsAttributes(Node) + */ + private Document cleanupToolsAttributes(@NonNull Document doc) { + cleanupToolsAttributes(doc.getFirstChild()); + return doc; + } +} diff --git a/manifmerger/src/main/java/com/android/manifmerger/MergerLog.java b/manifmerger/src/main/java/com/android/manifmerger/MergerLog.java new file mode 100755 index 0000000..446898c --- /dev/null +++ b/manifmerger/src/main/java/com/android/manifmerger/MergerLog.java @@ -0,0 +1,151 @@ +/* + * 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.manifmerger; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.utils.ILogger; + + +/** + * Helper to create {@link IMergerLog} instances with specific purposes. + */ +public abstract class MergerLog { + + /** + * Create a new instance of a {@link MergerLog} that prints to an {@link ILogger}. + * + * @param sdkLog A non-null {@link ILogger}. + * @return A new IMergerLog. + */ + public static IMergerLog wrapSdkLog(final @NonNull ILogger sdkLog) { + return new IMergerLog() { + @Override + public void error( + @NonNull Severity severity, + @NonNull FileAndLine location, + @NonNull String message, + Object...msgParams) { + + switch(severity) { + case INFO: + sdkLog.info( + "[%1$s] %2$s", //$NON-NLS-1$ + location, + String.format(message, msgParams)); + break; + case WARNING: + sdkLog.warning( + "[%1$s] %2$s", //$NON-NLS-1$ + location, + String.format(message, msgParams)); + break; + case ERROR: + sdkLog.error(null /*throwable*/, + "[%1$s] %2$s", //$NON-NLS-1$ + location, + String.format(message, msgParams)); + break; + } + } + + @Override + public void conflict(@NonNull Severity severity, + @NonNull FileAndLine location1, + @NonNull FileAndLine location2, + @NonNull String message, + Object...msgParams) { + + switch(severity) { + case INFO: + sdkLog.info( + "[%1$s, %2$s] %3$s", //$NON-NLS-1$ + location1, + location2, + String.format(message, msgParams)); + break; + case WARNING: + sdkLog.warning( + "[%1$s, %2$s] %3$s", //$NON-NLS-1$ + location1, + location2, + String.format(message, msgParams)); + break; + case ERROR: + sdkLog.error(null /*throwable*/, + "[%1$s, %2$s] %3$s", //$NON-NLS-1$ + location1, + location2, + String.format(message, msgParams)); + break; + } + }; + }; + } + + /* + * Creates a new instance of a {@link MergerLog} that wraps another {@link IMergerLog} + * and overrides the {@link FileAndLine} locations with the arguments specified. + *

+ * An example of usage would be merging temporary files yet associating the errors + * with the original files. + * + * @param parentLog A non-null IMergerLog to wrap. + * @param filePath1 The file path to override in location1 (for errors and conflicts). + * @param filePath2 An optional file path to override in location2 (for conflicts). + * @return A new IMergerLog. + */ + public static IMergerLog mergerLogOverrideLocation( + final @NonNull IMergerLog parentLog, + final @Nullable String filePath1, + final @Nullable String... filePath2) { + return new IMergerLog() { + @Override + public void error( + @NonNull Severity severity, + @NonNull FileAndLine location, + @NonNull String message, + Object...msgParams) { + + if (filePath1 != null) { + location = new FileAndLine(filePath1, location.getLine()); + } + + parentLog.error(severity, location, message, msgParams); + } + + @Override + public void conflict(@NonNull Severity severity, + @NonNull FileAndLine location1, + @NonNull FileAndLine location2, + @NonNull String message, + Object...msgParams) { + + if (filePath1 != null) { + location1 = new FileAndLine(filePath1, location1.getLine()); + } + + if (filePath2 != null && filePath2.length > 0) { + location2 = new FileAndLine(filePath2[0], location2.getLine()); + } + + parentLog.conflict(severity, location1, location2, message, msgParams); + }; + }; + } + +} diff --git a/manifmerger/src/main/java/com/android/manifmerger/MergerXmlUtils.java b/manifmerger/src/main/java/com/android/manifmerger/MergerXmlUtils.java new file mode 100755 index 0000000..bb60464 --- /dev/null +++ b/manifmerger/src/main/java/com/android/manifmerger/MergerXmlUtils.java @@ -0,0 +1,915 @@ +/* + * 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.manifmerger; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.manifmerger.IMergerLog.FileAndLine; +import com.android.manifmerger.IMergerLog.Severity; +import com.android.utils.ILogger; +import com.android.utils.XmlUtils; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +/** + * A few XML handling utilities. + */ +class MergerXmlUtils { + + private static final String DATA_ORIGIN_FILE = "manif.merger.file"; //$NON-NLS-1$ + private static final String DATA_FILE_NAME = "manif.merger.filename"; //$NON-NLS-1$ + private static final String DATA_LINE_NUMBER = "manif.merger.line#"; //$NON-NLS-1$ + + /** + * Parses the given XML file as a DOM document. + * The parser does not validate the DTD nor any kind of schema. + * It is namespace aware. + *

+ * This adds a user tag with the original {@link File} to the returned document. + * You can retrieve this file later by using {@link #extractXmlFilename(Node)}. + * + * @param xmlFile The XML {@link File} to parse. Must not be null. + * @param log An {@link ILogger} for reporting errors. Must not be null. + * @return A new DOM {@link Document}, or null. + */ + @Nullable + static Document parseDocument(@NonNull final File xmlFile, @NonNull final IMergerLog log) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Reader reader = new BufferedReader(new FileReader(xmlFile)); + InputSource is = new InputSource(reader); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + + // We don't want the default handler which prints errors to stderr. + builder.setErrorHandler(new ErrorHandler() { + @Override + public void warning(SAXParseException e) { + log.error(Severity.WARNING, + new FileAndLine(xmlFile.getAbsolutePath(), 0), + "Warning when parsing: %1$s", + e.toString()); + } + @Override + public void fatalError(SAXParseException e) { + log.error(Severity.ERROR, + new FileAndLine(xmlFile.getAbsolutePath(), 0), + "Fatal error when parsing: %1$s", + xmlFile.getName(), e.toString()); + } + @Override + public void error(SAXParseException e) { + log.error(Severity.ERROR, + new FileAndLine(xmlFile.getAbsolutePath(), 0), + "Error when parsing: %1$s", + e.toString()); + } + }); + + Document doc = builder.parse(is); + doc.setUserData(DATA_ORIGIN_FILE, xmlFile, null /*handler*/); + findLineNumbers(doc, 1); + + return doc; + + } catch (FileNotFoundException e) { + log.error(Severity.ERROR, + new FileAndLine(xmlFile.getAbsolutePath(), 0), + "XML file not found"); + + } catch (Exception e) { + log.error(Severity.ERROR, + new FileAndLine(xmlFile.getAbsolutePath(), 0), + "Failed to parse XML file: %1$s", + e.toString()); + } + + return null; + } + + /** + * Parses the given XML string as a DOM document. + * The parser does not validate the DTD nor any kind of schema. + * It is namespace aware. + * + * @param xml The XML string to parse. Must not be null. + * @param log An {@link ILogger} for reporting errors. Must not be null. + * @return A new DOM {@link Document}, or null. + */ + @Nullable + static Document parseDocument(@NonNull String xml, + @NonNull IMergerLog log, + @NonNull FileAndLine errorContext) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + InputSource is = new InputSource(new StringReader(xml)); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(is); + findLineNumbers(doc, 1); + return doc; + } catch (Exception e) { + log.error(Severity.ERROR, errorContext, "Failed to parse XML string"); + } + + return null; + } + + /** + * Decorates the document with the specified file name, which can be + * retrieved later by calling {@link #extractLineNumber(Node)}. + *

+ * It also tries to add line number information, with the caveat that the + * current implementation is a gross approximation. + *

+ * There is no need to call this after calling one of the {@code parseDocument()} + * methods since they already decorated their own document. + * + * @param doc The document to decorate. + * @param fileName The name to retrieve later for that document. + */ + static void decorateDocument(@NonNull Document doc, @NonNull String fileName) { + doc.setUserData(DATA_FILE_NAME, fileName, null /*handler*/); + findLineNumbers(doc, 1); + } + + /** + * Returns a new {@link FileAndLine} structure that identifies + * the base filename & line number from which the XML node was parsed. + *

+ * When the line number is unknown (e.g. if a {@link Document} instance is given) + * then line number 0 will be used. + * + * @param node The node or document where the error occurs. Must not be null. + * @return A new non-null {@link FileAndLine} combining the file name and line number. + */ + @NonNull + static FileAndLine xmlFileAndLine(@NonNull Node node) { + String name = extractXmlFilename(node); + int line = extractLineNumber(node); // 0 in case of error or unknown + return new FileAndLine(name, line); + } + + /** + * Extracts the origin {@link File} that {@link #parseDocument(File, IMergerLog)} + * added to the XML document or the string added by + * + * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog)}. + * @return The {@link File} object used to create the document or null. + */ + @Nullable + static String extractXmlFilename(@Nullable Node xmlNode) { + if (xmlNode != null && xmlNode.getNodeType() != Node.DOCUMENT_NODE) { + xmlNode = xmlNode.getOwnerDocument(); + } + if (xmlNode != null) { + Object data = xmlNode.getUserData(DATA_ORIGIN_FILE); + if (data instanceof File) { + return ((File) data).getName(); + } + data = xmlNode.getUserData(DATA_FILE_NAME); + if (data instanceof String) { + return (String) data; + } + } + + return null; + } + + /** + * This is a CRUDE INEXACT HACK to decorate the DOM with some kind of line number + * information for elements. It's inexact because by the time we get the DOM we + * already have lost all the information about whitespace between attributes. + *

+ * Also we don't even try to deal with \n vs \r vs \r\n insanity. This only counts + * the \n occurring in text nodes to determine line advances, which is clearly flawed. + *

+ * However it's good enough for testing, and we'll replace it by a PositionXmlParser + * once it's moved into com.android.util. + */ + private static int findLineNumbers(Node node, int line) { + for (; node != null; node = node.getNextSibling()) { + node.setUserData(DATA_LINE_NUMBER, Integer.valueOf(line), null /*handler*/); + + if (node.getNodeType() == Node.TEXT_NODE) { + String text = node.getNodeValue(); + if (text.length() > 0) { + for (int pos = 0; (pos = text.indexOf('\n', pos)) != -1; pos++) { + ++line; + } + } + } + + Node child = node.getFirstChild(); + if (child != null) { + line = findLineNumbers(child, line); + } + } + return line; + } + + /** + * Extracts the line number that {@link #findLineNumbers} added to the XML nodes. + * + * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog)}. + * @return The line number if found or 0. + */ + static int extractLineNumber(@Nullable Node xmlNode) { + if (xmlNode != null) { + Object data = xmlNode.getUserData(DATA_LINE_NUMBER); + if (data instanceof Integer) { + return ((Integer) data).intValue(); + } + } + + return 0; + } + + /** + * Outputs the given XML {@link Document} to the file {@code outFile}. + * + * TODO right now reformats the document. Needs to output as-is, respecting white-space. + * + * @param doc The document to output. Must not be null. + * @param outFile The {@link File} where to write the document. + * @param log A log in case of error. + * @return True if the file was written, false in case of error. + */ + static boolean printXmlFile( + @NonNull Document doc, + @NonNull File outFile, + @NonNull IMergerLog log) { + // Quick thing based on comments from http://stackoverflow.com/questions/139076 + try { + Transformer tf = TransformerFactory.newInstance().newTransformer(); + tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$ + tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ + tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ + tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$ + "4"); //$NON-NLS-1$ + tf.transform(new DOMSource(doc), new StreamResult(outFile)); + return true; + } catch (TransformerException e) { + log.error(Severity.ERROR, + new FileAndLine(outFile.getName(), 0), + "Failed to write XML file: %1$s", + e.toString()); + return false; + } + } + + /** + * Outputs the given XML {@link Document} as a string. + * + * TODO right now reformats the document. Needs to output as-is, respecting white-space. + * + * @param doc The document to output. Must not be null. + * @param log A log in case of error. + * @return A string representation of the XML. Null in case of error. + */ + static String printXmlString( + @NonNull Document doc, + @NonNull IMergerLog log) { + try { + Transformer tf = TransformerFactory.newInstance().newTransformer(); + tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$ + tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ + tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ + tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$ + "4"); //$NON-NLS-1$ + StringWriter sw = new StringWriter(); + tf.transform(new DOMSource(doc), new StreamResult(sw)); + return sw.toString(); + } catch (TransformerException e) { + log.error(Severity.ERROR, + new FileAndLine(extractXmlFilename(doc), 0), + "Failed to write XML file: %1$s", + e.toString()); + return null; + } + } + + /** + * Dumps the structure of the DOM to a simple text string. + * + * @param node The first node to dump (recursively). Can be null. + * @param nextSiblings If true, will also dump the following siblings. + * If false, it will just process the given node. + * @return A string representation of the Node structure, useful for debugging. + */ + @NonNull + static String dump(@Nullable Node node, boolean nextSiblings) { + return dump(node, 0 /*offset*/, nextSiblings, true /*deep*/, null /*keyAttr*/); + } + + + /** + * Dumps the structure of the DOM to a simple text string. + * Each line is terminated with a \n separator. + * + * @param node The first node to dump. Can be null. + * @param offsetIndex The offset to add at the begining of each line. Each offset is + * converted into 2 space characters. + * @param nextSiblings If true, will also dump the following siblings. + * If false, it will just process the given node. + * @param deep If true, this will recurse into children. + * @param keyAttr An optional attribute *local* name to insert when writing an element. + * For example when writing an Activity, it helps to always insert "name" attribute. + * @return A string representation of the Node structure, useful for debugging. + */ + @NonNull + static String dump( + @Nullable Node node, + int offsetIndex, + boolean nextSiblings, + boolean deep, + @Nullable String keyAttr) { + StringBuilder sb = new StringBuilder(); + + String offset = ""; //$NON-NLS-1$ + for (int i = 0; i < offsetIndex; i++) { + offset += " "; //$NON-NLS-1$ + } + + if (node == null) { + sb.append(offset).append("(end reached)\n"); + + } else { + for (; node != null; node = node.getNextSibling()) { + String type = null; + short t = node.getNodeType(); + switch(t) { + case Node.ELEMENT_NODE: + String attr = ""; + if (keyAttr != null) { + NamedNodeMap attrs = node.getAttributes(); + if (attrs != null) { + for (int i = 0; i < attrs.getLength(); i++) { + Node a = attrs.item(i); + if (a != null && keyAttr.equals(a.getLocalName())) { + attr = String.format(" %1$s=%2$s", + a.getNodeName(), a.getNodeValue()); + break; + } + } + } + } + sb.append(String.format("%1$s<%2$s%3$s>\n", + offset, node.getNodeName(), attr)); + break; + case Node.COMMENT_NODE: + sb.append(String.format("%1$s\n", + offset, node.getNodeValue())); + break; + case Node.TEXT_NODE: + String txt = node.getNodeValue().trim(); + if (txt.length() == 0) { + // Keep this for debugging. TODO make it a flag + // to dump whitespace on debugging. Otherwise ignore it. + // txt = "[whitespace]"; + break; + } + sb.append(String.format("%1$s%2$s\n", offset, txt)); + break; + case Node.ATTRIBUTE_NODE: + sb.append(String.format("%1$s @%2$s = %3$s\n", + offset, node.getNodeName(), node.getNodeValue())); + break; + case Node.CDATA_SECTION_NODE: + type = "cdata"; //$NON-NLS-1$ + break; + case Node.DOCUMENT_NODE: + type = "document"; //$NON-NLS-1$ + break; + case Node.PROCESSING_INSTRUCTION_NODE: + type = "PI"; //$NON-NLS-1$ + break; + default: + type = Integer.toString(t); + } + + if (type != null) { + sb.append(String.format("%1$s[%2$s] <%3$s> %4$s\n", + offset, type, node.getNodeName(), node.getNodeValue())); + } + + if (deep) { + List attrs = sortedAttributeList(node.getAttributes()); + for (Attr attr : attrs) { + sb.append(String.format("%1$s @%2$s = %3$s\n", + offset, attr.getNodeName(), attr.getNodeValue())); + } + + Node child = node.getFirstChild(); + if (child != null) { + sb.append(dump(child, offsetIndex+1, true, true, keyAttr)); + } + } + + if (!nextSiblings) { + break; + } + } + } + return sb.toString(); + } + + /** + * Returns a sorted list of attributes. + * The list is never null and does not contain null items. + * + * @param attrMap A Node map as returned by {@link Node#getAttributes()}. + * Can be null, in which case an empty list is returned. + * @return A non-null, possible empty, list of all nodes that are actual {@link Attr}, + * sorted by increasing attribute name. + */ + @NonNull + static List sortedAttributeList(@Nullable NamedNodeMap attrMap) { + List list = new ArrayList(); + + if (attrMap != null) { + for (int i = 0; i < attrMap.getLength(); i++) { + Node attr = attrMap.item(i); + if (attr instanceof Attr) { + list.add((Attr) attr); + } + } + } + + if (list.size() > 1) { + // Sort it by attribute name + Collections.sort(list, getAttrComparator()); + } + + return list; + } + + /** + * Returns a comparator for {@link Attr}, alphabetically sorted by name. + * The "name" attribute is special and always sorted to the front. + */ + @NonNull + static Comparator getAttrComparator() { + return new Comparator() { + @Override + public int compare(Attr a1, Attr a2) { + String s1 = a1 == null ? "" : a1.getNodeName(); //$NON-NLS-1$ + String s2 = a2 == null ? "" : a2.getNodeValue(); //$NON-NLS-1$ + + int prio1 = s1.equals("name") ? 0 : 1; //$NON-NLS-1$ + int prio2 = s2.equals("name") ? 0 : 1; //$NON-NLS-1$ + if (prio1 == 0 || prio2 == 0) { + return prio1 - prio2; + } + + return s1.compareTo(s2); + } + }; + } + + /** + * Inject attributes into an existing document. + *

+ * The map keys are "/manifest/elements...|attribute-ns-uri attribute-local-name", + * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion". + * (note the space separator between the attribute URI and its local name.) + * The elements will be created if they don't exists. Existing attributes will be modified. + * The replacement is done on the main document before merging. + * The value can be null to remove an existing attribute. + * + * @param doc The document to modify in-place. + * @param attributeMap A map of attributes to inject in the form [pseudo-xpath] => value. + * @param log A log in case of error. + */ + static void injectAttributes( + @Nullable Document doc, + @Nullable Map attributeMap, + @NonNull IMergerLog log) { + if (doc == null || attributeMap == null || attributeMap.isEmpty()) { + return; + } + + // 1=path 2=URI 3=local name + final Pattern keyRx = Pattern.compile("^/([^\\|]+)\\|([^ ]*) +(.+)$"); //$NON-NLS-1$ + final FileAndLine docInfo = xmlFileAndLine(doc); + + nextAttribute: for (Entry entry : attributeMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (key == null || key.isEmpty()) { + continue; + } + + Matcher m = keyRx.matcher(key); + if (!m.matches()) { + log.error(Severity.WARNING, docInfo, "Invalid injected attribute key: %s", key); + continue; + } + String path = m.group(1); + String attrNsUri = m.group(2); + String attrName = m.group(3); + + String[] segment = path.split(Pattern.quote("/")); //$NON-NLS-1$ + + // Get the path elements. Create them as needed if they don't exist. + Node element = doc; + nextSegment: for (int i = 0; i < segment.length; i++) { + // Find a child with the segment's name + String name = segment[i]; + for (Node child = element.getFirstChild(); + child != null; + child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE && + child.getNamespaceURI() == null && + child.getNodeName().equals(name)) { + // Found it. Continue to the next inner segment. + element = child; + continue nextSegment; + } + } + // No such element. Create it. + if (value == null) { + // If value is null, we want to remove, not create and if can't find the + // element, then we're done: there's no such attribute to remove. + break nextAttribute; + } + + Element child = doc.createElement(name); + element = element.insertBefore(child, element.getFirstChild()); + } + + if (element == null) { + log.error(Severity.WARNING, docInfo, "Invalid injected attribute path: %s", path); + return; + } + + NamedNodeMap attrs = element.getAttributes(); + if (attrs != null) { + + + if (attrNsUri != null && attrNsUri.isEmpty()) { + attrNsUri = null; + } + Node attr = attrs.getNamedItemNS(attrNsUri, attrName); + + if (value == null) { + // We want to remove the attribute from the attribute map. + if (attr != null) { + attrs.removeNamedItemNS(attrNsUri, attrName); + } + + } else { + // We want to add or replace the attribute. + if (attr == null) { + attr = doc.createAttributeNS(attrNsUri, attrName); + if (attrNsUri != null) { + attr.setPrefix(XmlUtils.lookupNamespacePrefix(element, attrNsUri)); + } + attrs.setNamedItemNS(attr); + } + attr.setNodeValue(value); + } + } + } + } + + // ------- + + /** + * Flatten the element to a string. This "pretty prints" the XML tree starting + * from the given node and all its children and attributes. + *

+ * The output is designed to be printed using {@link #printXmlDiff}. + * + * @param node The root node to print. + * @param nsPrefix A map that is filled with all the URI=>prefix found. + * The internal string only contains the expanded URIs but this is rather verbose + * so when printing the diff these will be replaced by the prefixes collected here. + * @param prefix A "space" prefix added at the beginning of each line for indentation + * purposes. The diff printer later relies on this to find out the structure. + */ + @NonNull + static String printElement( + @NonNull Node node, + @NonNull Map nsPrefix, + @NonNull String prefix) { + StringBuilder sb = new StringBuilder(); + sb.append(prefix).append('<'); + String uri = node.getNamespaceURI(); + if (uri != null) { + sb.append(uri).append(':'); + nsPrefix.put(uri, node.getPrefix()); + } + sb.append(node.getLocalName()); + printAttributes(sb, node, nsPrefix, prefix); + sb.append(">\n"); //$NON-NLS-1$ + printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$ + + sb.append(prefix).append("\n"); //$NON-NLS-1$ + + return sb.toString(); + } + + /** + * Flatten several children elements to a string. + * This is an implementation detail for {@link #printElement(Node, Map, String)}. + *

+ * If {@code nextSiblings} is false, the string conversion takes only the given + * child element and stops there. + *

+ * If {@code nextSiblings} is true, the string conversion also takes _all_ the siblings + * after the given element. The idea is the caller can call this with the first child + * of a parent and get a string showing all the children at the same time. They are + * sorted to avoid the ordering issue. + */ + @NonNull + private static StringBuilder printChildren( + @NonNull StringBuilder sb, + @NonNull Node child, + boolean nextSiblings, + @NonNull Map nsPrefix, + @NonNull String prefix) { + ArrayList children = new ArrayList(); + + boolean hasText = false; + for (; child != null; child = child.getNextSibling()) { + short t = child.getNodeType(); + if (nextSiblings && t == Node.TEXT_NODE) { + // We don't typically have meaningful text nodes in an Android manifest. + // If there are, just dump them as-is into the element representation. + // We do trim whitespace and ignore all-whitespace or empty text nodes. + String s = child.getNodeValue().trim(); + if (s.length() > 0) { + sb.append(s); + hasText = true; + } + } else if (t == Node.ELEMENT_NODE) { + children.add(printElement(child, nsPrefix, prefix)); + if (!nextSiblings) { + break; + } + } + } + + if (hasText) { + sb.append('\n'); + } + + if (!children.isEmpty()) { + Collections.sort(children); + for (String s : children) { + sb.append(s); + } + } + + return sb; + } + + /** + * Flatten several attributes to a string using their alphabethical order. + * This is an implementation detail for {@link #printElement(Node, Map, String)}. + */ + @NonNull + private static StringBuilder printAttributes( + @NonNull StringBuilder sb, + @NonNull Node node, + @NonNull Map nsPrefix, + @NonNull String prefix) { + ArrayList attrs = new ArrayList(); + + NamedNodeMap attrMap = node.getAttributes(); + if (attrMap != null) { + StringBuilder sb2 = new StringBuilder(); + for (int i = 0; i < attrMap.getLength(); i++) { + Node attr = attrMap.item(i); + if (attr instanceof Attr) { + sb2.setLength(0); + sb2.append('@'); + String uri = attr.getNamespaceURI(); + if (uri != null) { + sb2.append(uri).append(':'); + nsPrefix.put(uri, attr.getPrefix()); + } + sb2.append(attr.getLocalName()); + sb2.append("=\"").append(attr.getNodeValue()).append('\"'); //$NON-NLS-1$ + attrs.add(sb2.toString()); + } + } + } + + Collections.sort(attrs); + + for(String attr : attrs) { + sb.append('\n'); + sb.append(prefix).append(" ").append(attr); //$NON-NLS-1$ + } + return sb; + } + + //------------ + + /** + * Computes a quick diff between two strings generated by + * {@link #printElement(Node, Map, String)}. + *

+ * This is a not designed to be a full contextual diff. + * It just stops at the first difference found, printing up to 3 lines of diff + * and backtracking to add prior contextual information to understand the + * structure of the element where the first diff line occured (by printing + * each parent found till the root one as well as printing the attribute + * named by {@code keyAttr}). + * + * @param sb The string builder where to output is written. + * @param expected The expected XML tree (as generated by {@link #printElement}.) + * For best result this would be the "destination" XML we're merging into, + * e.g. the main manifest. + * @param actual The actual XML tree (as generated by {@link #printElement}.) + * For best result this would be the "source" XML we're merging from, + * e.g. a library manifest. + * @param nsPrefixE The map of URI=>prefix for the expected XML tree. + * @param nsPrefixA The map of URI=>prefix for the actual XML tree. + * @param keyAttr An optional attribute *full* name (uri:local name) to always + * insert when writing the contextual lines before a diff line. + * For example when writing an Activity, it helps to always insert + * the "name" attribute since that's the key element to help the user + * identify which node is being dumped. + */ + static void printXmlDiff( + StringBuilder sb, + String expected, + String actual, + Map nsPrefixE, + Map nsPrefixA, + String keyAttr) { + String[] aE = expected.split("\n"); + String[] aA = actual.split("\n"); + int lE = aE.length; + int lA = aA.length; + int lm = lE < lA ? lA : lE; + boolean eofE = false; + boolean eofA = false; + boolean contextE = true; + boolean contextA = true; + int numDiff = 0; + + StringBuilder sE = new StringBuilder(); + StringBuilder sA = new StringBuilder(); + + outerLoop: for (int i = 0, iE = 0, iA = 0; i < lm; i++) { + if (iE < lE && iA < lA && aE[iE].equals(aA[iA])) { + if (numDiff > 0) { + // If we found a difference, stop now. + break outerLoop; + } + iE++; + iA++; + continue; + } else { + // Try to print some context for each side based on previous lines's space prefix. + if (contextE) { + if (iE > 0) { + String p = diffGetPrefix(aE[iE]); + for (int kE = iE-1; kE >= 0; kE--) { + if (!aE[kE].startsWith(p)) { + sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " "); + if (p.length() == 0) { + break; + } + p = diffGetPrefix(aE[kE]); + } else if (aE[kE].contains(keyAttr) || kE == 0) { + sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " "); + } + } + } + contextE = false; + } + if (iE >= lE) { + if (!eofE) { + sE.append("--(end reached)\n"); + eofE = true; + } + } else { + sE.append("--").append(diffReplaceNs(aE[iE++], nsPrefixE)).append('\n'); + } + + if (contextA) { + if (iA > 0) { + String p = diffGetPrefix(aA[iA]); + for (int kA = iA-1; kA >= 0; kA--) { + if (!aA[kA].startsWith(p)) { + sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " "); + p = diffGetPrefix(aA[kA]); + if (p.length() == 0) { + break; + } + } else if (aA[kA].contains(keyAttr) || kA == 0) { + sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " "); + } + } + } + contextA = false; + } + if (iA >= lA) { + if (!eofA) { + sA.append("++(end reached)\n"); + eofA = true; + } + } else { + sA.append("++").append(diffReplaceNs(aA[iA++], nsPrefixA)).append('\n'); + } + + // Dump up to 3 lines of difference + numDiff++; + if (numDiff == 3) { + break outerLoop; + } + } + } + + sb.append(sE); + sb.append(sA); + } + + /** + * Returns all the whitespace at the beginning of a string. + * Implementation details for {@link #printXmlDiff} used to find the "parent" + * element and include it in the context of the diff. + */ + private static String diffGetPrefix(String str) { + int pos = 0; + int len = str.length(); + while (pos < len && str.charAt(pos) == ' ') { + pos++; + } + return str.substring(0, pos); + } + + /** + * Simplifies a diff line by replacing NS URIs by their prefix. + * Implementation details for {@link #printXmlDiff}. + */ + private static String diffReplaceNs(String str, Map nsPrefix) { + for (Entry entry : nsPrefix.entrySet()) { + String uri = entry.getKey(); + String prefix = entry.getValue(); + if (prefix != null && str.contains(uri)) { + str = str.replaceAll(Pattern.quote(uri), Matcher.quoteReplacement(prefix)); + } + } + return str; + } + +} diff --git a/manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTest.java b/manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTest.java new file mode 100755 index 0000000..6da0bb9 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.manifmerger; + + +/** + * Unit tests for {@link ManifestMerger}. + */ +public class ManifestMergerTest extends ManifestMergerTestCase { + + /* + * Wait, I hear you, where are the tests? + * + * processTestFiles() uses loadTestData(), which infers the data filename + * from the caller method name. + * E.g. the method "test00_noop" will use the data file named "data/00_noop.xml". + * + * We could simplify this even further by simply iterating on the data + * files and getting rid of the test methods; however there's some value in + * having tests break on a method name that easily points to the data file. + */ + + public void test00_noop() throws Exception { + processTestFiles(); + } + + public void test01_ignore_app_attr() throws Exception { + processTestFiles(); + } + + public void test02_ignore_instrumentation() throws Exception { + processTestFiles(); + } + + public void test03_inject_attributes() throws Exception { + processTestFiles(); + } + + public void test04_inject_attributes() throws Exception { + processTestFiles(); + } + + public void test10_activity_merge() throws Exception { + processTestFiles(); + } + + public void test11_activity_dup() throws Exception { + processTestFiles(); + } + + public void test12_alias_dup() throws Exception { + processTestFiles(); + } + + public void test13_service_dup() throws Exception { + processTestFiles(); + } + + public void test14_receiver_dup() throws Exception { + processTestFiles(); + } + + public void test15_provider_dup() throws Exception { + processTestFiles(); + } + + public void test16_fqcn_merge() throws Exception { + processTestFiles(); + } + + public void test17_fqcn_conflict() throws Exception { + processTestFiles(); + } + + public void test20_uses_lib_merge() throws Exception { + processTestFiles(); + } + + public void test21_uses_lib_errors() throws Exception { + processTestFiles(); + } + + public void test25_permission_merge() throws Exception { + processTestFiles(); + } + + public void test26_permission_dup() throws Exception { + processTestFiles(); + } + + public void test28_uses_perm_merge() throws Exception { + processTestFiles(); + } + + public void test30_uses_sdk_ok() throws Exception { + processTestFiles(); + } + + public void test32_uses_sdk_minsdk_ok() throws Exception { + processTestFiles(); + } + + public void test33_uses_sdk_minsdk_conflict() throws Exception { + processTestFiles(); + } + + public void test36_uses_sdk_targetsdk_warning() throws Exception { + processTestFiles(); + } + + public void test40_uses_feat_merge() throws Exception { + processTestFiles(); + } + + public void test41_uses_feat_errors() throws Exception { + processTestFiles(); + } + + public void test45_uses_feat_gles_once() throws Exception { + processTestFiles(); + } + + public void test47_uses_feat_gles_conflict() throws Exception { + processTestFiles(); + } + + public void test50_uses_conf_warning() throws Exception { + processTestFiles(); + } + + public void test52_support_screens_warning() throws Exception { + processTestFiles(); + } + + public void test54_compat_screens_warning() throws Exception { + processTestFiles(); + } + + public void test56_support_gltext_warning() throws Exception { + processTestFiles(); + } + + public void test60_merge_order() throws Exception { + processTestFiles(); + } + + public void test65_override_app() throws Exception { + processTestFiles(); + } + + public void test66_remove_app() throws Exception { + processTestFiles(); + } + + public void test67_override_activities() throws Exception { + processTestFiles(); + } + + public void test68_override_uses() throws Exception { + processTestFiles(); + } + + public void test69_remove_uses() throws Exception { + processTestFiles(); + } + + public void test70_expand_fqcns() throws Exception { + processTestFiles(); + } + + public void test71_prefixes_enable_extractprefix() throws Exception { + processTestFiles(); + } +} diff --git a/manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTestCase.java b/manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTestCase.java new file mode 100755 index 0000000..eb3a215 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/ManifestMergerTestCase.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.manifmerger; + +import com.android.annotations.NonNull; +import com.android.manifmerger.IMergerLog.FileAndLine; +import com.android.sdklib.mock.MockLog; + +import org.w3c.dom.Document; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +/** + * Some utilities to reduce repetitions in the {@link ManifestMergerTest}s. + *

+ * See {@link #loadTestData(String)} for an explanation of the data file format. + */ +abstract class ManifestMergerTestCase extends TestCase { + + /** + * Delimiter that indicates the test must fail. + * An XML output and errors are still generated and checked. + */ + private static final String DELIM_FAILS = "fails"; + /** + * Delimiter that starts a library XML content. + * The delimiter name must be in the form {@code @libSomeName} and it will be + * used as the base for the test file name. Using separate lib names is encouraged + * since it makes the error output easier to read. + */ + private static final String DELIM_LIB = "lib"; + /** + * Delimiter that starts the main manifest XML content. + */ + private static final String DELIM_MAIN = "main"; + /** + * Delimiter that starts the inject attribute section. + */ + private static final String DELIM_INJECT_ATTR = "inject"; + /** + * Delimiter that starts the resulting XML content, whatever is generated by the merge. + */ + private static final String DELIM_RESULT = "result"; + /** + * Delimiter that starts the SdkLog output. + * The logger prints each entry on its lines, prefixed with E for errors, + * W for warnings and P for regular printfs. + */ + private static final String DELIM_ERRORS = "errors"; + + static class TestFiles { + private final File mMain; + private final File[] mLibs; + private final Map mInjectAttributes; + private final File mActualResult; + private final String mExpectedResult; + private final String mExpectedErrors; + private final boolean mShouldFail; + + /** Files used by a given test case. */ + public TestFiles( + boolean shouldFail, + @NonNull File main, + @NonNull File[] libs, + @NonNull Map injectAttributes, + @NonNull File actualResult, + @NonNull String expectedResult, + @NonNull String expectedErrors) { + mShouldFail = shouldFail; + mMain = main; + mLibs = libs; + mInjectAttributes = injectAttributes; + mActualResult = actualResult; + mExpectedResult = expectedResult; + mExpectedErrors = expectedErrors; + } + + public boolean getShouldFail() { + return mShouldFail; + } + + @NonNull + public File getMain() { + return mMain; + } + + @NonNull + public File[] getLibs() { + return mLibs; + } + + public Map getInjectAttributes() { + return mInjectAttributes; + } + + @NonNull + public File getActualResult() { + return mActualResult; + } + + @NonNull + public String getExpectedResult() { + return mExpectedResult; + } + + public String getExpectedErrors() { + return mExpectedErrors; + } + + // Try to delete any temp file potentially created. + public void cleanup() { + if (mMain != null && mMain.isFile()) { + mMain.delete(); + } + + if (mActualResult != null && mActualResult.isFile()) { + mActualResult.delete(); + } + + for (File f : mLibs) { + if (f != null && f.isFile()) { + f.delete(); + } + } + } + } + + /** + * Calls {@link #loadTestData(String)} by + * inferring the data filename from the caller's method name. + *

+ * The caller method name must be composed of "test" + the leaf filename. + * Extensions ".xml" or ".txt" are implied. + *

+ * E.g. to use the data file "12_foo.xml", simply call this from a method + * named "test12_foo". + * + * @return A new {@link TestFiles} instance. Never null. + * @throws Exception when things go wrong. + * @see #loadTestData(String) + */ + @NonNull + TestFiles loadTestData() throws Exception { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + for (int i = 0, n = stack.length; i < n; i++) { + StackTraceElement caller = stack[i]; + String name = caller.getMethodName(); + if (name.startsWith("test")) { + return loadTestData(name.substring(4)); + } + } + + throw new IllegalArgumentException("No caller method found which name started with 'test'"); + } + + /** + * Loads test data for a given test case. + * The input (main + libs) are stored in temp files. + * A new destination temp file is created to store the actual result output. + * The expected result is actually kept in a string. + *

+ * Data File Syntax: + *

    + *
  • Lines starting with # are ignored (anywhere, as long as # is the first char). + *
  • Lines before the first {@code @delimiter} are ignored. + *
  • Empty lines just after the {@code @delimiter} + * and before the first < XML line are ignored. + *
  • Valid delimiters are {@code @main} for the XML of the main app manifest. + *
  • Following delimiters are {@code @libXYZ}, read in the order of definition. + * The name can be anything as long as it starts with "{@code @lib}". + *
+ * + * @param filename The test data filename. If no extension is provided, this will + * try with .xml or .txt. Must not be null. + * @return A new {@link TestFiles} instance. Must not be null. + * @throws Exception when things fail to load properly. + */ + @NonNull + TestFiles loadTestData(@NonNull String filename) throws Exception { + + String resName = "data" + File.separator + filename; + InputStream is = null; + BufferedReader reader = null; + BufferedWriter writer = null; + + try { + is = this.getClass().getResourceAsStream(resName); + if (is == null && !filename.endsWith(".xml")) { + String resName2 = resName + ".xml"; + is = this.getClass().getResourceAsStream(resName2); + if (is != null) { + filename = resName2; + } + } + if (is == null && !filename.endsWith(".txt")) { + String resName3 = resName + ".txt"; + is = this.getClass().getResourceAsStream(resName3); + if (is != null) { + filename = resName3; + } + } + assertNotNull("Test data file not found for " + filename, is); + + reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); + + // Get the temporary directory to use. Just create a temp file, extracts its + // directory and remove the file. + File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".tmp"); + File tempDir = tempFile.getParentFile(); + if (!tempFile.delete()) { + tempFile.deleteOnExit(); + } + + String line = null; + String delimiter = null; + boolean skipEmpty = true; + + boolean shouldFail = false; + Map injectAttributes = new HashMap(); + StringBuilder expectedResult = new StringBuilder(); + StringBuilder expectedErrors = new StringBuilder(); + File mainFile = null; + File actualResultFile = null; + List libFiles = new ArrayList(); + int tempIndex = 0; + + while ((line = reader.readLine()) != null) { + if (skipEmpty && line.trim().length() == 0) { + continue; + } + if (line.length() > 0 && line.charAt(0) == '#') { + continue; + } + if (line.length() > 0 && line.charAt(0) == '@') { + delimiter = line.substring(1); + assertTrue( + "Unknown delimiter @" + delimiter + " in " + filename, + delimiter.startsWith(DELIM_LIB) || + delimiter.equals(DELIM_MAIN) || + delimiter.equals(DELIM_RESULT) || + delimiter.equals(DELIM_ERRORS) || + delimiter.equals(DELIM_FAILS) || + delimiter.equals(DELIM_INJECT_ATTR)); + + skipEmpty = true; + + if (writer != null) { + try { + writer.close(); + } catch (IOException ignore) {} + writer = null; + } + + if (delimiter.equals(DELIM_FAILS)) { + shouldFail = true; + + } else if (!delimiter.equals(DELIM_ERRORS) && + !delimiter.equals(DELIM_INJECT_ATTR)) { + tempFile = new File(tempDir, String.format("%1$s%2$d_%3$s.xml", + this.getClass().getSimpleName(), + tempIndex++, + delimiter.replaceAll("[^a-zA-Z0-9_-]", "") + )); + tempFile.deleteOnExit(); + + if (delimiter.startsWith(DELIM_LIB)) { + libFiles.add(tempFile); + + } else if (delimiter.equals(DELIM_MAIN)) { + mainFile = tempFile; + + } else if (delimiter.equals(DELIM_RESULT)) { + actualResultFile = tempFile; + + } else { + fail("Unexpected data file delimiter @" + delimiter + + " in " + filename); + } + + if (!delimiter.equals(DELIM_RESULT)) { + writer = new BufferedWriter(new FileWriter(tempFile)); + } + } + + continue; + } + if (delimiter != null && + skipEmpty && + line.length() > 0 && + line.charAt(0) != '#' && + line.charAt(0) != '@') { + skipEmpty = false; + } + if (writer != null) { + writer.write(line); + writer.write('\n'); + } else if (DELIM_RESULT.equals(delimiter)) { + expectedResult.append(line).append('\n'); + } else if (DELIM_ERRORS.equals(delimiter)) { + expectedErrors.append(line).append('\n'); + } else if (DELIM_INJECT_ATTR.equals(delimiter)) { + String[] in = line.split("="); + if (in != null && in.length == 2) { + injectAttributes.put(in[0], "null".equals(in[1]) ? null : in[1]); + } + } + } + + assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile); + assertNotNull("Missing @" + DELIM_RESULT + " in " + filename, actualResultFile); + + assert mainFile != null; + assert actualResultFile != null; + + return new TestFiles( + shouldFail, + mainFile, + libFiles.toArray(new File[libFiles.size()]), + injectAttributes, + actualResultFile, + expectedResult.toString(), + expectedErrors.toString()); + + } catch (UnsupportedEncodingException e) { + // BufferedReader failed to decode UTF-8, O'RLY? + throw e; + + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException ignore) {} + } + if (reader != null) { + try { + reader.close(); + } catch (IOException ignore) {} + } + if (is != null) { + try { + is.close(); + } catch (IOException ignore) {} + } + } + } + + /** + * Loads the data test files using {@link #loadTestData()} and then + * invokes {@link #processTestFiles(TestFiles)} to test them. + * + * @see #loadTestData() + * @see #processTestFiles(TestFiles) + */ + void processTestFiles() throws Exception { + processTestFiles(loadTestData()); + } + + /** + * Processes the data from the given {@link TestFiles} by + * invoking {@link ManifestMerger#process(File, File, File[], Map)}: + * the given library files are applied consecutively to the main XML + * document and the output is generated. + *

+ * Then the expected and actual outputs are loaded into a DOM, + * dumped again to a String using an XML transform and compared. + * This makes sure only the structure is checked and that any + * formatting is ignored in the comparison. + * + * @param testFiles The test files to process. Must not be null. + * @throws Exception when this go wrong. + */ + void processTestFiles(TestFiles testFiles) throws Exception { + MockLog log = new MockLog(); + IMergerLog mergerLog = MergerLog.wrapSdkLog(log); + ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() { + @Override + public int queryCodenameApiLevel(@NonNull String codename) { + if ("ApiCodename1".equals(codename)) { + return 1; + } else if ("ApiCodename10".equals(codename)) { + return 10; + } + return ICallback.UNKNOWN_CODENAME; + } + }); + + // Test name contains "enable_extractprefix" to enable manifest extract prefix + if (getName().contains("enable_extractprefix")) { + merger.setExtractPackagePrefix(true); + } + boolean processOK = merger.process(testFiles.getActualResult(), + testFiles.getMain(), + testFiles.getLibs(), + testFiles.getInjectAttributes()); + + String expectedErrors = testFiles.getExpectedErrors().trim(); + StringBuilder actualErrors = new StringBuilder(); + for (String s : log.getMessages()) { + actualErrors.append(s); + if (!s.endsWith("\n")) { + actualErrors.append('\n'); + } + } + assertEquals("Error generated during merging", + expectedErrors, actualErrors.toString().trim()); + + if (testFiles.getShouldFail()) { + assertFalse("Merge process() returned true, expected false", processOK); + } else { + assertTrue("Merge process() returned false, expected true", processOK); + } + + // Test result XML. There should always be one created + // since the process action does not stop on errors. + log.clear(); + Document document = MergerXmlUtils.parseDocument(testFiles.getActualResult(), mergerLog); + assertNotNull(document); + assert document != null; // for Eclipse null analysis + String actual = MergerXmlUtils.printXmlString(document, mergerLog); + assertEquals("Error parsing actual result XML", "[]", log.toString()); + log.clear(); + document = MergerXmlUtils.parseDocument( + testFiles.getExpectedResult(), + mergerLog, + new FileAndLine("", 0)); + assertNotNull("Failed to parse result document: " + testFiles.getExpectedResult(),document); + assert document != null; + String expected = MergerXmlUtils.printXmlString(document, mergerLog); + assertEquals("Error parsing expected result XML", "[]", log.toString()); + assertEquals("Error comparing expected to actual result", expected, actual); + + testFiles.cleanup(); + } + +} diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/00_noop.xml b/manifmerger/src/test/java/com/android/manifmerger/data/00_noop.xml new file mode 100755 index 0000000..2160f69 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/00_noop.xml @@ -0,0 +1,229 @@ +# +# Syntax: +# - Lines starting with # are ignored (anywhere, as long as # is the first char). +# - Lines before the first @delimiter are ignored. +# - Empty lines just after the @delimiter and before the first < XML line are ignored. +# - Valid delimiters are @main for the XML of the main app manifest. +# - Following delimiters are @libXYZ, read in the order of definition. The name can be +# anything as long as it starts with "@lib". +# - Last delimiter should be @result. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + +@lib2 + +# An empty library is not supported. It must be a valid XML file. + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest2_lib2.xml:1] Missing 'package' attribute in manifest. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml b/manifmerger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml new file mode 100755 index 0000000..b939dd7 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml @@ -0,0 +1,69 @@ +# +# Test: +# - Attributes from the application element in a library are ignored (except name) +# - Comments from nodes ignored in libraries are not merged either. +# + +@main + + + + + + + + + +@lib1 + + + + + + + + + +@result + + + + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml b/manifmerger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml new file mode 100755 index 0000000..ed0dbbc --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml @@ -0,0 +1,62 @@ +# +# Test: +# - Instrumentation element from libraries are not merged in main manifest. +# + +@main + + + + + + + + +@lib1 + + + + + + + + +@result + + + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml b/manifmerger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml new file mode 100755 index 0000000..0a7057c --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml @@ -0,0 +1,53 @@ +# +# Test: +# - Inject attributes in a main manifest. +# + +@inject +/manifest|http://schemas.android.com/apk/res/android versionCode=101 +/manifest|http://schemas.android.com/apk/res/android versionName=1.0.1 +/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion=10 +/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion=14 +/manifest/application|http://schemas.android.com/apk/res/android label=null +/manifest/application|http://schemas.android.com/apk/res/android icon=null + +@main + + + + + + + + +@result + + + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml b/manifmerger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml new file mode 100755 index 0000000..57f4e84 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml @@ -0,0 +1,63 @@ +# +# Test: +# - Inject attributes in a main manifest. +# The attributes are injected and then the merge is done. In this case the app +# starts with a minSdkVersion of 20, which is higher than the lib1's 15 value. +# However the injection replaces it by 10, which is now lower than the lib's +# version and thus a warning will be generated. +# + +@fails + +@inject +/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion=10 +/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion=14 +/manifest/application|http://schemas.android.com/apk/res/android label=null +/manifest/application|http://schemas.android.com/apk/res/android icon=null + +@main + + + + + + + + + +@lib1 + + + + + + + + + +@result + + + + + + + + + +@errors + +E [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1.xml:3] Main manifest has but library uses minSdkVersion='15' +W [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1.xml:3] Main manifest has but library uses targetSdkVersion='16' diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml b/manifmerger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml new file mode 100755 index 0000000..59c5c42 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml @@ -0,0 +1,378 @@ +# +# Test: +# - Activities from libraries are merged in the main manifest. +# - Acts on activity / activity-alias / service / receiver / provider. +# - Elements are merged as-is with the first comment element preceding them. +# - Whitespace preceding the merged elements is transfered over too. +# +# Note: +# - New elements are always merged at the end of the application element. +# - It's an error if an element with the same @name attribute is defined +# or merged more than once unless the definition is *exactly* the same, +# the "same" being defined by the exact XML elements, whitespace excluded. +# +# This tests that a normal merge is done as expected. +# There's a warning because one of the activities from lib2 is already defined +# in the main but it's purely identical so it's not an error. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1_widget + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2_activity + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib3_alias + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# from @lib1_widget + + + + + + + + + + + + + + + + + +# from @lib2_activity + + + + + + + + + + + +# from @lib3_alias + + + + + + + + + + + + + + + +@errors + +P [ManifestMergerTest0_main.xml:31, ManifestMergerTest2_lib2_activity.xml:6] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. +P [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:19] Skipping identical /manifest/application/activity[@name=com.example.LibActivity2] element. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml b/manifmerger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml new file mode 100755 index 0000000..ef163b0 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml @@ -0,0 +1,406 @@ +# +# Test: +# - Activities from libraries are merged in the main manifest. +# - Acts on activity / activity-alias / service / receiver / provider. +# - Elements are merged as-is with the first comment element preceding them. +# - Whitespace preceding the merged elements is transfered over too. +# +# Note: +# - New elements are always merged at the end of the application element. +# - It's an error if an element with the same @name attribute is defined +# or merged more than once unless the definition is *exactly* the same, +# the "same" being defined by the exact XML elements, whitespace excluded. +# +# This tests that an error is generated because the libraries define +# activities which are already in the main, with slightly different XML content: +# number and *order* of elements must match, attributes must match. +# + +@fails + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1_widget + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2_activity + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib3_alias + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# from @lib1_widget + + + + + + + + + + +# from @lib2_activity + + + + + + + + + + + +# from @lib3_alias + + + + + + + + + + + + + + + + +@errors + +E [ManifestMergerTest0_main.xml:32, ManifestMergerTest1_lib1_widget.xml:16] Trying to merge incompatible /manifest/application/activity[@name=com.example.WidgetConfigurationUI] element: + +--(end reached) + +++ +E [ManifestMergerTest0_main.xml:38, ManifestMergerTest2_lib2_activity.xml:6] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity] element: + +-- +-- +++ +E [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:19] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity2] element: + + @android:name="android.intent.category.LAUNCHER"> +-- +-- +--(end reached) + + @android:name="android.intent.action.MAIN"> + @android:name="android.intent.category.LAUNCHER"> +++ +++ diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml b/manifmerger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml new file mode 100755 index 0000000..7b5aed3 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml @@ -0,0 +1,211 @@ +# +# Test: +# - Activities from libraries are merged in the main manifest. +# - Acts on activity / activity-alias / service / receiver / provider. +# - Elements are merged as-is with the first comment element preceding them. +# - Whitespace preceding the merged elements is transfered over too. +# +# Note: +# - New elements are always merged at the end of the application element. +# - It's an error if an element with the same @name attribute is defined +# or merged more than once unless the definition is *exactly* the same, +# the "same" being defined by the exact XML elements, whitespace excluded. +# +# This tests that an error is generated because the libraries define +# aliases which are already defined differently. +# + +@fails + +@main + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/activity-alias[@name=com.example.alias.MyActivity1] element. +E [ManifestMergerTest0_main.xml:13, ManifestMergerTest1_lib1.xml:14] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity2] element: + +++ +E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity3] element: + +-- + +++ +++ + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + +@errors + +P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/service[@name=com.example.AppService1] element. +E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService2] element: + +-- +--(end reached) + +++ +++ +E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService3] element: + +-- +-- + +++ +++(end reached) diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml b/manifmerger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml new file mode 100755 index 0000000..777ba22 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml @@ -0,0 +1,186 @@ +# +# Test: +# - Activities from libraries are merged in the main manifest. +# - Acts on activity / activity-alias / service / receiver / provider. +# - Elements are merged as-is with the first comment element preceding them. +# - Whitespace preceding the merged elements is transfered over too. +# +# Note: +# - New elements are always merged at the end of the application element. +# - It's an error if an element with the same @name attribute is defined +# or merged more than once unless the definition is *exactly* the same, +# the "same" being defined by the exact XML elements, whitespace excluded. +# +# This tests that an error is generated because the libraries define +# receivers which are already defined differently. +# + +@fails + +@main + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver1] element. +E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:13] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver2] element: + +-- + +++ +++(end reached) +E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver3] element: + + + + + + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml b/manifmerger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml new file mode 100755 index 0000000..bd0c8fe --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml @@ -0,0 +1,153 @@ +# +# Test: +# - Activities from libraries are merged in the main manifest. +# - Acts on activity / activity-alias / service / receiver / provider. +# - Elements are merged as-is with the first comment element preceding them. +# - Whitespace preceding the merged elements is transfered over too. +# +# Note: +# - New elements are always merged at the end of the application element. +# - It's an error if an element with the same @name attribute is defined +# or merged more than once unless the definition is *exactly* the same, +# the "same" being defined by the exact XML elements, whitespace excluded. +# +# This tests that an error is generated because the libraries define +# providers which are already defined differently. +# + +@fails + +@main + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + +@errors + +P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/provider[@name=com.example.Provider1] element. +E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider2] element: + +-- +--(end reached) + +E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider3] element: + +-- + +++ +++(end reached) diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml b/manifmerger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml new file mode 100755 index 0000000..8414a3c --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml @@ -0,0 +1,129 @@ +# +# Test how FQCN class names are expanded and handled: +# - A library application can be merged doesn't have an app class name. +# - A library application can be merged if it has the same class name as the app. +# - A partial class name is expanded using the package name in a library or app. +# + +@main + + + + + + + + + + + +@lib1_widget + + + + + + + + + + + + +@lib2_activity + + + + + + + + + + + + + + + + + +@lib3_alias + + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + +# from @lib1_widget + + + + + +# from @lib2_activity + + + + + + +# from @lib3_alias + + + + + + + + + +@errors + +P [ManifestMergerTest0_main.xml:6, ManifestMergerTest2_lib2_activity.xml:5] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity] element. +P [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:8] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity2] element. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml b/manifmerger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml new file mode 100755 index 0000000..b4f5ea0 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml @@ -0,0 +1,120 @@ +# +# Test how FQCN class names are expanded and handled: +# - A library application can be merged doesn't have an app class name. +# - A library application can be merged if it has the same class name as the app. +# - A partial class name is expanded using the package name in a library or app. +# +# All tests fail with just warnings, no solid errors. +# + +@main + + + + + + + + + + + +@lib1_widget + + + + + + + + + + +@lib2_widget + + + + + + + + + + + +@lib3_widget + + + + + + + + + + +@lib4_not_package + + + + + + + + + + + + + + + + +@result + + + + + + + + + + +# from @lib4_alias + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1_widget.xml:4] Main manifest has but library uses name='com.example.lib1.TheApp'. +W [ManifestMergerTest0_main.xml:3, ManifestMergerTest2_lib2_widget.xml:4] Main manifest has but library uses backupAgent='com.example.lib2.MyBackupAgent'. +P [ManifestMergerTest0_main.xml:6, ManifestMergerTest2_lib2_widget.xml:6] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity] element. +W [ManifestMergerTest4_lib4_not_package.xml:1] Missing 'package' attribute in manifest. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml b/manifmerger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml new file mode 100755 index 0000000..a5eecce --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml @@ -0,0 +1,176 @@ +# +# Test merge of uses-library: +# - Merge is OK if destination already has one with the same @name. +# - required defaults to "true" +# - when merging, a required=true (explicit or implicit) overwrites a required=false. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + +# required=false from lib1 is ignored, it stays at the default + + + + + + +# lib1 keeps it required=false but lib2 makes it switch to required=true + + + + + +# new from lib1 + + + +# new from lib1, but lib2 makes it switch to required=true + + + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml b/manifmerger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml new file mode 100755 index 0000000..9e8f5a0 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml @@ -0,0 +1,202 @@ +# +# Test merge of uses-library: +# - Merge is OK if destination already has one with the same @name. +# - required defaults to "true" +# - when merging, a required=true (explicit or implicit) overwrites a required=false. +# + +@fails + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + +# lib1 keeps it required=false but lib2 makes it switch to required=true + + + + + + + + +# new from lib1 + + + +# new from lib1, but lib2 makes it switch to required=true + + + + + + + + +@errors + +E [ManifestMergerTest1_lib1.xml:6] Undefined 'name' attribute in /manifest/application/uses-library. +E [ManifestMergerTest1_lib1.xml:7] Undefined 'name' attribute in /manifest/application/uses-library. +E [ManifestMergerTest1_lib1.xml:8] Undefined 'name' attribute in /manifest/application/uses-library. +W [ManifestMergerTest0_main.xml:12] Invalid attribute 'required' in /manifest/application/uses-library[@name=com.example.SomeLibrary2_RequiredTrue] element: +Expected 'true' or 'false' but found 'booh!'. +W [ManifestMergerTest0_main.xml:15] Manifest has more than one /manifest/application/uses-library[@name=com.example.SomeLibrary3_RequiredFalse] element. +W [ManifestMergerTest1_lib1.xml:17] Invalid attribute 'required' in /manifest/application/uses-library[@name=com.example.SomeLibrary4_RequiredFalse] element: +Expected 'true' or 'false' but found 'foo'. +W [ManifestMergerTest0_main.xml:15] Manifest has more than one /manifest/application/uses-library[@name=com.example.SomeLibrary3_RequiredFalse] element. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml b/manifmerger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml new file mode 100755 index 0000000..07208ad --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml @@ -0,0 +1,259 @@ +# +# Text permission, permission-group and permission-tree: +# - Libraries can add any of these elements as long as they don't conflict +# with the destination: either the element must not be at all in the destination +# (as identified by the name) or it must match exactly. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Added by lib1 + + + + + + + +# Added by lib2 + + + + + + + + + + +@errors diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml b/manifmerger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml new file mode 100755 index 0000000..bd9a4f1 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml @@ -0,0 +1,314 @@ +# +# Text permission, permission-group and permission-tree: +# - Libraries can add any of these elements as long as they don't conflict +# with the destination: either the element must not be at all in the destination +# (as identified by the name) or it must match exactly. +# +# This one tests that duplicate definitions that are strictly equal generate errors +# with some (hopefully useful) diff. +# + +@fails + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Added by lib1 + + + + + + + +# Added by lib2 + + + + + + + + + + +@errors + +E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:4] Trying to merge incompatible /manifest/permission[@name=com.example.DangerWillRobinson] element: + + +E [ManifestMergerTest0_main.xml:16, ManifestMergerTest1_lib1.xml:5] Trying to merge incompatible /manifest/permission-group[@name=com.example.MasterControlPermission] element: + + +++ +E [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:6] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree] element: + + +E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:7] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree1] element: + +-- +--(end reached) + +++ diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml b/manifmerger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml new file mode 100755 index 0000000..ecc644a --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml @@ -0,0 +1,156 @@ +# +# Text uses-permission: +# - Libraries can add any of these elements as long as they don't conflict +# with the destination: either the element must not be at all in the destination +# (as identified by the name) or it must match exactly. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + +# Added by lib1 + + + + + + +# Added by lib2 + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml b/manifmerger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml new file mode 100755 index 0000000..bcee4ce --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml @@ -0,0 +1,86 @@ +# +# Test uses-sdk: add a uses-sdk from an app that doesn't define one. +# + +@main + + + + + + + + + + +@lib1 + + + + + + + + + +@lib2 + + + + + + + + + +@lib3 + + + + + + + + + +@result + + + + + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml b/manifmerger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml new file mode 100755 index 0000000..b94efe8 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml @@ -0,0 +1,70 @@ +# +# Test uses-sdk: it's ok for a library to have a smaller minSdkVersion than the main manifest. +# + +@main + + + + + + + + + + +@lib1 + + + + + + + + + +@lib2 + + + + + + + + +@lib3 + + + + + + + + +@result + + + + + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml b/manifmerger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml new file mode 100755 index 0000000..8edbedb --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml @@ -0,0 +1,148 @@ +# +# Test uses-sdk: it's an error for a library to require a minSdkVersion higher than the +# one defined in the main manifest. +# +# Also a uses-sdk with a lack of minSdkVersion is equivalent to using version=1. +# + +@fails + +@main + + + + + + + + + + + +@lib1 + + + + + + + + + +@lib2 + + + + + + + + +@lib3 + + + + + + + + +@lib4_parsingError + + + + + + + + + +@lib5_parsingError + + + + + + + + + +@lib6_parsingError + + + + + + + + + +@lib7_parsingError + + + + + + + + + +@lib8_parsingCodename + + + + + + + + + +@result + + + + + + + + + + + +@errors + +E [ManifestMergerTest0_main.xml:4, ManifestMergerTest1_lib1.xml:4] Main manifest has but library uses minSdkVersion='4' +Note: main manifest lacks a declaration, which defaults to value 1. +E [ManifestMergerTest0_main.xml:4, ManifestMergerTest2_lib2.xml:3] Main manifest has but library uses minSdkVersion='10' +Note: main manifest lacks a declaration, which defaults to value 1. +E [ManifestMergerTest0_main.xml:4, ManifestMergerTest3_lib3.xml:3] Main manifest has but library uses minSdkVersion='11' +Note: main manifest lacks a declaration, which defaults to value 1. +E [ManifestMergerTest4_lib4_parsingError.xml:4] Failed to parse : must be an integer number or codename. +E [ManifestMergerTest5_lib5_parsingError.xml:4] Failed to parse : must be an integer number or codename. +E [ManifestMergerTest6_lib6_parsingError.xml:4] Failed to parse : must be an integer number or codename. +E [ManifestMergerTest7_lib7_parsingError.xml:4] Failed to parse : must be an integer number or codename. +E [ManifestMergerTest7_lib7_parsingError.xml:4] Failed to parse : must be an integer number or codename. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml b/manifmerger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml new file mode 100755 index 0000000..df8b717 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml @@ -0,0 +1,77 @@ +# +# Test uses-sdk: there's a warning if the main manifest defines a targetSdkVersion that +# is smaller than what the libraries target. +# + +@fails + +@main + + + + + + + + + + + +@lib1 + + + + + + + + + +@lib2 + + + + + + + + +@result + + + + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:4, ManifestMergerTest1_lib1.xml:4] Main manifest has but library uses targetSdkVersion='11' diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml b/manifmerger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml new file mode 100755 index 0000000..d14dcaa --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml @@ -0,0 +1,178 @@ +# +# Test merge of uses-feature: +# - Merge is OK if destination already has one with the same @name. +# - required defaults to "true" +# - when merging, a required=true (explicit or implicit) overwrites a required=false. +# +# Note: uses-feature with android:glEsVersion is dealt with in another test case. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + +@result + + + + + + + +# required=false from lib1 is ignored, it stays at the default + + + + + + +# lib1 keeps it required=false but lib2 makes it switch to required=true + + + + + + + + + +# new from lib1 + + + +# new from lib1, but lib2 makes it switch to required=true + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml b/manifmerger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml new file mode 100755 index 0000000..b86f74a --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml @@ -0,0 +1,205 @@ +# +# Test merge of uses-feature: +# - Merge is OK if destination already has one with the same @name. +# - required defaults to "true" +# - when merging, a required=true (explicit or implicit) overwrites a required=false. +# +# Note: uses-feature with android:glEsVersion is dealt with in another test case. +# + +@fails + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + +# lib1 keeps it required=false but lib2 makes it switch to required=true + + + +# in case of duplicated name, they are all modified. + + + + + + + + + +# new from lib1 + + + +# new from lib1, but lib2 makes it switch to required=true + + + + + + +@errors + +E [ManifestMergerTest1_lib1.xml:4] Undefined 'name' attribute in /manifest/uses-feature. +E [ManifestMergerTest1_lib1.xml:5] Undefined 'name' attribute in /manifest/uses-feature. +E [ManifestMergerTest1_lib1.xml:6] Undefined 'name' attribute in /manifest/uses-feature. +W [ManifestMergerTest0_main.xml:10] Invalid attribute 'required' in /manifest/uses-feature[@name=com.example.SomeFeature2_RequiredTrue] element: +Expected 'true' or 'false' but found 'booh!'. +W [ManifestMergerTest0_main.xml:13] Manifest has more than one /manifest/uses-feature[@name=com.example.SomeFeature3_RequiredFalse] element. +W [ManifestMergerTest1_lib1.xml:15] Invalid attribute 'required' in /manifest/uses-feature[@name=com.example.SomeFeature4_RequiredFalse] element: +Expected 'true' or 'false' but found 'foo'. +W [ManifestMergerTest0_main.xml:13] Manifest has more than one /manifest/uses-feature[@name=com.example.SomeFeature3_RequiredFalse] element. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml b/manifmerger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml new file mode 100755 index 0000000..57937a6 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml @@ -0,0 +1,126 @@ +# +# Test merge of uses-feature with android:glEsVersion: +# - Error if defined in lib+dest with dest < lib. +# - Never automatically change dest. +# - Default implied value is 1.0 (0x00010000). +# +# This tests a case that works. Also checks that glEsVersion attributes are stripped +# when merging uses-feature with the name attribute. +# + +@main + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + +@result + + + + + + + + + + + + +# lib1 adds this new node. Note how the glEsVersion=2.1 is stripped out. + + + +# lib2 adds this new node. Note how the glEsVersion=2.0 is stripped out. + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml b/manifmerger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml new file mode 100755 index 0000000..936d009 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml @@ -0,0 +1,162 @@ +# +# Test merge of uses-feature with android:glEsVersion: +# - Error if defined in lib+dest with dest < lib. +# - Never automatically change dest. +# - Default implied value is 1.0 (0x00010000). +# +# This tests a case that doesn't works because the main manifest doesn't declare +# the value and thus defaults to 1.0, so libraries with higher requirements will +# conflict. +# + +@fails + +@main + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + +# lib1 adds this new node. Note how the glEsVersion=2.1 is stripped out. + + + +# lib2 adds this new node. Note how the glEsVersion=2.0 is stripped out. + + + + + +@errors + +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:4] Main manifest has but library uses glEsVersion='0x00020001' +Note: main manifest lacks a declaration, and thus defaults to glEsVersion=0x00010000. +W [ManifestMergerTest2_lib2.xml:12] Ignoring because it's smaller than 1.0. +W [ManifestMergerTest2_lib2.xml:15] Ignoring because it's smaller than 1.0. +E [ManifestMergerTest2_lib2.xml:21] Failed to parse : must be an integer in the form 0x00020001. +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:18] Main manifest has but library uses glEsVersion='0xffffffff' +Note: main manifest lacks a declaration, and thus defaults to glEsVersion=0x00010000. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml b/manifmerger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml new file mode 100755 index 0000000..b1cb3f9 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml @@ -0,0 +1,162 @@ +# +# Test uses-configuration: +# - it's OK if a library defines one or multiple times an element already in the application. +# - it's a warning if the library defines an element not in the application. +# - this does not actually merge anything. The XML is not changed at all. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + +@lib2 + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/uses-configuration defined in library, missing from main manifest: + + @android:reqFiveWayNav = false + @android:reqNavigation = trackball + @android:reqTouchScreen = finger diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml b/manifmerger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml new file mode 100755 index 0000000..363fb2b --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml @@ -0,0 +1,162 @@ +# +# Test supports-screens: +# - it's OK if a library defines one or multiple times an element already in the application. +# - it's a warning if the library defines an element not in the application. +# - this does not actually merge anything. The XML is not changed at all. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + +@lib2 + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/supports-screens defined in library, missing from main manifest: + + @android:resizeable = false + @android:smallScreens = false diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml b/manifmerger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml new file mode 100755 index 0000000..1e1c2d2 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml @@ -0,0 +1,204 @@ +# +# Test compatible-screens: +# - it's OK if a library defines one or multiple times an element already in the application. +# - it's a warning if the library defines an element not in the application. +# - this does not actually merge anything. The XML is not changed at all. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/compatible-screens defined in library, missing from main manifest: + + + @android:screenDensity = ldpi + @android:screenSize = small + + @android:screenDensity = mdpi + @android:screenSize = normal +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:9] /manifest/compatible-screens defined in library, missing from main manifest: + + + @android:screenDensity = ldpi + @android:screenSize = small +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:13] /manifest/compatible-screens defined in library, missing from main manifest: + + + @android:screenDensity = ldpi + @android:screenSize = normal + + @android:screenDensity = mdpi + @android:screenSize = normal + + @android:screenDensity = hdpi + @android:screenSize = normal + + @android:screenDensity = xhdpi + @android:screenSize = normal diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml b/manifmerger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml new file mode 100755 index 0000000..114e9f4 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml @@ -0,0 +1,151 @@ +# +# Test supports-gl-texture: +# - it's a warning if the library defines a supports-gl-texture not in the application. +# - this does not actually merge anything. The XML is not changed at all. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + +@lib2 + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/supports-gl-texture defined in library, missing from main manifest: + + @android:name = some.gl.texture3 diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/60_merge_order.xml b/manifmerger/src/test/java/com/android/manifmerger/data/60_merge_order.xml new file mode 100755 index 0000000..d6767f7 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/60_merge_order.xml @@ -0,0 +1,318 @@ +# +# Test merge order: +# - When activity / activity-alias / service / receiver / provider are merged, +# we do a comparison to check whether the elements are already present in the +# main manifest. The order of the elements must NOT matter in the comparison, +# nor does the whitespace between them. +# - What this checks is that the order of the elements or attributes within +# the elements should not matter. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +P [ManifestMergerTest0_main.xml:37, ManifestMergerTest1_lib1.xml:26] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. +P [ManifestMergerTest0_main.xml:5, ManifestMergerTest1_lib1.xml:41] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element. +P [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:7] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element. +P [ManifestMergerTest0_main.xml:5, ManifestMergerTest2_lib2.xml:6] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element. +P [ManifestMergerTest0_main.xml:37, ManifestMergerTest2_lib2.xml:11] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. +P [ManifestMergerTest0_main.xml:18, ManifestMergerTest2_lib2.xml:20] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/65_override_app.xml b/manifmerger/src/test/java/com/android/manifmerger/data/65_override_app.xml new file mode 100755 index 0000000..464b52f --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/65_override_app.xml @@ -0,0 +1,197 @@ +# +# Test the tools:merge="override" tag on an application. +# It essentially ignores _any_ application tag from libraries. +# All other non-application elements are merged as usual. +# + +@main + + + + + + + + + + + + + + + + + + +@lib1_widget + + + + + + + + + + + + + + + +@lib2_widget + + + + + + + + + + + + + + + + +@lib3_widget + + + + + + + + + + + + + +@lib4_not_package + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest4_lib4_not_package.xml:1] Missing 'package' attribute in manifest. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/66_remove_app.xml b/manifmerger/src/test/java/com/android/manifmerger/data/66_remove_app.xml new file mode 100755 index 0000000..a1b04b5 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/66_remove_app.xml @@ -0,0 +1,53 @@ +# +# Test how elements are removed by tools:merge="remove". +# + +@main + + + + + + + + + + + + +@lib1_widget + + + + + + + + + + + +@result + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/67_override_activities.xml b/manifmerger/src/test/java/com/android/manifmerger/data/67_override_activities.xml new file mode 100755 index 0000000..8bcdb63 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/67_override_activities.xml @@ -0,0 +1,159 @@ +# +# Test how elements are overriden by tools:merge="override". +# The override only blocks elements that would be merged. +# + +@main + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/68_override_uses.xml b/manifmerger/src/test/java/com/android/manifmerger/data/68_override_uses.xml new file mode 100755 index 0000000..a43d13e --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/68_override_uses.xml @@ -0,0 +1,205 @@ +# +# Test how elements are overriden by tools:merge="override". +# The override only blocks elements that would be merged. +# That means items which are just checked (not merged) still produce warnings. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:19] /manifest/supports-screens defined in library, missing from main manifest: + + @android:resizeable = false + @android:smallScreens = false +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:23] /manifest/supports-gl-texture defined in library, missing from main manifest: + + @android:name = some.gl.texture3 +W [ManifestMergerTest1_lib1.xml:14] Ignoring because it's smaller than 1.0. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml b/manifmerger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml new file mode 100755 index 0000000..c3e46c9 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml @@ -0,0 +1,177 @@ +# +# Test how elements are removed by tools:merge="remove". +# The removal happens is done at the end and block merges. +# That means items which are just checked (not merged) still produce warnings. +# + +@main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@lib2 + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + + +@errors + +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:19] /manifest/supports-screens defined in library, missing from main manifest: + + @android:resizeable = false + @android:smallScreens = false +W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:23] /manifest/supports-gl-texture defined in library, missing from main manifest: + + @android:name = some.gl.texture3 +W [ManifestMergerTest1_lib1.xml:14] Ignoring because it's smaller than 1.0. diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml b/manifmerger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml new file mode 100755 index 0000000..5864345 --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml @@ -0,0 +1,84 @@ +# +# Test the option to extract prefixes +# + +@main + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + +@errors + + diff --git a/manifmerger/src/test/java/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml b/manifmerger/src/test/java/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml new file mode 100755 index 0000000..dd11dcc --- /dev/null +++ b/manifmerger/src/test/java/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml @@ -0,0 +1,84 @@ +# +# Test the option to extract prefixes +# + +@main + + + + + + + + + + + + + + + + + + +@lib1 + + + + + + + + + + + +@result + + + + + + + + + + + + + + + + + + + + +@errors + + diff --git a/manifmerger/tests/Android.mk b/manifmerger/tests/Android.mk deleted file mode 100755 index 9314106..0000000 --- a/manifmerger/tests/Android.mk +++ /dev/null @@ -1,28 +0,0 @@ -# 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. - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -# Only compile source java files in this lib. -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_JAVA_RESOURCE_DIRS := src - -LOCAL_MODULE := manifmerger-tests -LOCAL_MODULE_TAGS := optional - -LOCAL_JAVA_LIBRARIES := manifmerger sdklib-tests junit - -include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java deleted file mode 100755 index 6da0bb9..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php - * - * 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.manifmerger; - - -/** - * Unit tests for {@link ManifestMerger}. - */ -public class ManifestMergerTest extends ManifestMergerTestCase { - - /* - * Wait, I hear you, where are the tests? - * - * processTestFiles() uses loadTestData(), which infers the data filename - * from the caller method name. - * E.g. the method "test00_noop" will use the data file named "data/00_noop.xml". - * - * We could simplify this even further by simply iterating on the data - * files and getting rid of the test methods; however there's some value in - * having tests break on a method name that easily points to the data file. - */ - - public void test00_noop() throws Exception { - processTestFiles(); - } - - public void test01_ignore_app_attr() throws Exception { - processTestFiles(); - } - - public void test02_ignore_instrumentation() throws Exception { - processTestFiles(); - } - - public void test03_inject_attributes() throws Exception { - processTestFiles(); - } - - public void test04_inject_attributes() throws Exception { - processTestFiles(); - } - - public void test10_activity_merge() throws Exception { - processTestFiles(); - } - - public void test11_activity_dup() throws Exception { - processTestFiles(); - } - - public void test12_alias_dup() throws Exception { - processTestFiles(); - } - - public void test13_service_dup() throws Exception { - processTestFiles(); - } - - public void test14_receiver_dup() throws Exception { - processTestFiles(); - } - - public void test15_provider_dup() throws Exception { - processTestFiles(); - } - - public void test16_fqcn_merge() throws Exception { - processTestFiles(); - } - - public void test17_fqcn_conflict() throws Exception { - processTestFiles(); - } - - public void test20_uses_lib_merge() throws Exception { - processTestFiles(); - } - - public void test21_uses_lib_errors() throws Exception { - processTestFiles(); - } - - public void test25_permission_merge() throws Exception { - processTestFiles(); - } - - public void test26_permission_dup() throws Exception { - processTestFiles(); - } - - public void test28_uses_perm_merge() throws Exception { - processTestFiles(); - } - - public void test30_uses_sdk_ok() throws Exception { - processTestFiles(); - } - - public void test32_uses_sdk_minsdk_ok() throws Exception { - processTestFiles(); - } - - public void test33_uses_sdk_minsdk_conflict() throws Exception { - processTestFiles(); - } - - public void test36_uses_sdk_targetsdk_warning() throws Exception { - processTestFiles(); - } - - public void test40_uses_feat_merge() throws Exception { - processTestFiles(); - } - - public void test41_uses_feat_errors() throws Exception { - processTestFiles(); - } - - public void test45_uses_feat_gles_once() throws Exception { - processTestFiles(); - } - - public void test47_uses_feat_gles_conflict() throws Exception { - processTestFiles(); - } - - public void test50_uses_conf_warning() throws Exception { - processTestFiles(); - } - - public void test52_support_screens_warning() throws Exception { - processTestFiles(); - } - - public void test54_compat_screens_warning() throws Exception { - processTestFiles(); - } - - public void test56_support_gltext_warning() throws Exception { - processTestFiles(); - } - - public void test60_merge_order() throws Exception { - processTestFiles(); - } - - public void test65_override_app() throws Exception { - processTestFiles(); - } - - public void test66_remove_app() throws Exception { - processTestFiles(); - } - - public void test67_override_activities() throws Exception { - processTestFiles(); - } - - public void test68_override_uses() throws Exception { - processTestFiles(); - } - - public void test69_remove_uses() throws Exception { - processTestFiles(); - } - - public void test70_expand_fqcns() throws Exception { - processTestFiles(); - } - - public void test71_prefixes_enable_extractprefix() throws Exception { - processTestFiles(); - } -} diff --git a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java deleted file mode 100755 index eb3a215..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php - * - * 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.manifmerger; - -import com.android.annotations.NonNull; -import com.android.manifmerger.IMergerLog.FileAndLine; -import com.android.sdklib.mock.MockLog; - -import org.w3c.dom.Document; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import junit.framework.TestCase; - -/** - * Some utilities to reduce repetitions in the {@link ManifestMergerTest}s. - *

- * See {@link #loadTestData(String)} for an explanation of the data file format. - */ -abstract class ManifestMergerTestCase extends TestCase { - - /** - * Delimiter that indicates the test must fail. - * An XML output and errors are still generated and checked. - */ - private static final String DELIM_FAILS = "fails"; - /** - * Delimiter that starts a library XML content. - * The delimiter name must be in the form {@code @libSomeName} and it will be - * used as the base for the test file name. Using separate lib names is encouraged - * since it makes the error output easier to read. - */ - private static final String DELIM_LIB = "lib"; - /** - * Delimiter that starts the main manifest XML content. - */ - private static final String DELIM_MAIN = "main"; - /** - * Delimiter that starts the inject attribute section. - */ - private static final String DELIM_INJECT_ATTR = "inject"; - /** - * Delimiter that starts the resulting XML content, whatever is generated by the merge. - */ - private static final String DELIM_RESULT = "result"; - /** - * Delimiter that starts the SdkLog output. - * The logger prints each entry on its lines, prefixed with E for errors, - * W for warnings and P for regular printfs. - */ - private static final String DELIM_ERRORS = "errors"; - - static class TestFiles { - private final File mMain; - private final File[] mLibs; - private final Map mInjectAttributes; - private final File mActualResult; - private final String mExpectedResult; - private final String mExpectedErrors; - private final boolean mShouldFail; - - /** Files used by a given test case. */ - public TestFiles( - boolean shouldFail, - @NonNull File main, - @NonNull File[] libs, - @NonNull Map injectAttributes, - @NonNull File actualResult, - @NonNull String expectedResult, - @NonNull String expectedErrors) { - mShouldFail = shouldFail; - mMain = main; - mLibs = libs; - mInjectAttributes = injectAttributes; - mActualResult = actualResult; - mExpectedResult = expectedResult; - mExpectedErrors = expectedErrors; - } - - public boolean getShouldFail() { - return mShouldFail; - } - - @NonNull - public File getMain() { - return mMain; - } - - @NonNull - public File[] getLibs() { - return mLibs; - } - - public Map getInjectAttributes() { - return mInjectAttributes; - } - - @NonNull - public File getActualResult() { - return mActualResult; - } - - @NonNull - public String getExpectedResult() { - return mExpectedResult; - } - - public String getExpectedErrors() { - return mExpectedErrors; - } - - // Try to delete any temp file potentially created. - public void cleanup() { - if (mMain != null && mMain.isFile()) { - mMain.delete(); - } - - if (mActualResult != null && mActualResult.isFile()) { - mActualResult.delete(); - } - - for (File f : mLibs) { - if (f != null && f.isFile()) { - f.delete(); - } - } - } - } - - /** - * Calls {@link #loadTestData(String)} by - * inferring the data filename from the caller's method name. - *

- * The caller method name must be composed of "test" + the leaf filename. - * Extensions ".xml" or ".txt" are implied. - *

- * E.g. to use the data file "12_foo.xml", simply call this from a method - * named "test12_foo". - * - * @return A new {@link TestFiles} instance. Never null. - * @throws Exception when things go wrong. - * @see #loadTestData(String) - */ - @NonNull - TestFiles loadTestData() throws Exception { - StackTraceElement[] stack = Thread.currentThread().getStackTrace(); - for (int i = 0, n = stack.length; i < n; i++) { - StackTraceElement caller = stack[i]; - String name = caller.getMethodName(); - if (name.startsWith("test")) { - return loadTestData(name.substring(4)); - } - } - - throw new IllegalArgumentException("No caller method found which name started with 'test'"); - } - - /** - * Loads test data for a given test case. - * The input (main + libs) are stored in temp files. - * A new destination temp file is created to store the actual result output. - * The expected result is actually kept in a string. - *

- * Data File Syntax: - *

    - *
  • Lines starting with # are ignored (anywhere, as long as # is the first char). - *
  • Lines before the first {@code @delimiter} are ignored. - *
  • Empty lines just after the {@code @delimiter} - * and before the first < XML line are ignored. - *
  • Valid delimiters are {@code @main} for the XML of the main app manifest. - *
  • Following delimiters are {@code @libXYZ}, read in the order of definition. - * The name can be anything as long as it starts with "{@code @lib}". - *
- * - * @param filename The test data filename. If no extension is provided, this will - * try with .xml or .txt. Must not be null. - * @return A new {@link TestFiles} instance. Must not be null. - * @throws Exception when things fail to load properly. - */ - @NonNull - TestFiles loadTestData(@NonNull String filename) throws Exception { - - String resName = "data" + File.separator + filename; - InputStream is = null; - BufferedReader reader = null; - BufferedWriter writer = null; - - try { - is = this.getClass().getResourceAsStream(resName); - if (is == null && !filename.endsWith(".xml")) { - String resName2 = resName + ".xml"; - is = this.getClass().getResourceAsStream(resName2); - if (is != null) { - filename = resName2; - } - } - if (is == null && !filename.endsWith(".txt")) { - String resName3 = resName + ".txt"; - is = this.getClass().getResourceAsStream(resName3); - if (is != null) { - filename = resName3; - } - } - assertNotNull("Test data file not found for " + filename, is); - - reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); - - // Get the temporary directory to use. Just create a temp file, extracts its - // directory and remove the file. - File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".tmp"); - File tempDir = tempFile.getParentFile(); - if (!tempFile.delete()) { - tempFile.deleteOnExit(); - } - - String line = null; - String delimiter = null; - boolean skipEmpty = true; - - boolean shouldFail = false; - Map injectAttributes = new HashMap(); - StringBuilder expectedResult = new StringBuilder(); - StringBuilder expectedErrors = new StringBuilder(); - File mainFile = null; - File actualResultFile = null; - List libFiles = new ArrayList(); - int tempIndex = 0; - - while ((line = reader.readLine()) != null) { - if (skipEmpty && line.trim().length() == 0) { - continue; - } - if (line.length() > 0 && line.charAt(0) == '#') { - continue; - } - if (line.length() > 0 && line.charAt(0) == '@') { - delimiter = line.substring(1); - assertTrue( - "Unknown delimiter @" + delimiter + " in " + filename, - delimiter.startsWith(DELIM_LIB) || - delimiter.equals(DELIM_MAIN) || - delimiter.equals(DELIM_RESULT) || - delimiter.equals(DELIM_ERRORS) || - delimiter.equals(DELIM_FAILS) || - delimiter.equals(DELIM_INJECT_ATTR)); - - skipEmpty = true; - - if (writer != null) { - try { - writer.close(); - } catch (IOException ignore) {} - writer = null; - } - - if (delimiter.equals(DELIM_FAILS)) { - shouldFail = true; - - } else if (!delimiter.equals(DELIM_ERRORS) && - !delimiter.equals(DELIM_INJECT_ATTR)) { - tempFile = new File(tempDir, String.format("%1$s%2$d_%3$s.xml", - this.getClass().getSimpleName(), - tempIndex++, - delimiter.replaceAll("[^a-zA-Z0-9_-]", "") - )); - tempFile.deleteOnExit(); - - if (delimiter.startsWith(DELIM_LIB)) { - libFiles.add(tempFile); - - } else if (delimiter.equals(DELIM_MAIN)) { - mainFile = tempFile; - - } else if (delimiter.equals(DELIM_RESULT)) { - actualResultFile = tempFile; - - } else { - fail("Unexpected data file delimiter @" + delimiter + - " in " + filename); - } - - if (!delimiter.equals(DELIM_RESULT)) { - writer = new BufferedWriter(new FileWriter(tempFile)); - } - } - - continue; - } - if (delimiter != null && - skipEmpty && - line.length() > 0 && - line.charAt(0) != '#' && - line.charAt(0) != '@') { - skipEmpty = false; - } - if (writer != null) { - writer.write(line); - writer.write('\n'); - } else if (DELIM_RESULT.equals(delimiter)) { - expectedResult.append(line).append('\n'); - } else if (DELIM_ERRORS.equals(delimiter)) { - expectedErrors.append(line).append('\n'); - } else if (DELIM_INJECT_ATTR.equals(delimiter)) { - String[] in = line.split("="); - if (in != null && in.length == 2) { - injectAttributes.put(in[0], "null".equals(in[1]) ? null : in[1]); - } - } - } - - assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile); - assertNotNull("Missing @" + DELIM_RESULT + " in " + filename, actualResultFile); - - assert mainFile != null; - assert actualResultFile != null; - - return new TestFiles( - shouldFail, - mainFile, - libFiles.toArray(new File[libFiles.size()]), - injectAttributes, - actualResultFile, - expectedResult.toString(), - expectedErrors.toString()); - - } catch (UnsupportedEncodingException e) { - // BufferedReader failed to decode UTF-8, O'RLY? - throw e; - - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException ignore) {} - } - if (reader != null) { - try { - reader.close(); - } catch (IOException ignore) {} - } - if (is != null) { - try { - is.close(); - } catch (IOException ignore) {} - } - } - } - - /** - * Loads the data test files using {@link #loadTestData()} and then - * invokes {@link #processTestFiles(TestFiles)} to test them. - * - * @see #loadTestData() - * @see #processTestFiles(TestFiles) - */ - void processTestFiles() throws Exception { - processTestFiles(loadTestData()); - } - - /** - * Processes the data from the given {@link TestFiles} by - * invoking {@link ManifestMerger#process(File, File, File[], Map)}: - * the given library files are applied consecutively to the main XML - * document and the output is generated. - *

- * Then the expected and actual outputs are loaded into a DOM, - * dumped again to a String using an XML transform and compared. - * This makes sure only the structure is checked and that any - * formatting is ignored in the comparison. - * - * @param testFiles The test files to process. Must not be null. - * @throws Exception when this go wrong. - */ - void processTestFiles(TestFiles testFiles) throws Exception { - MockLog log = new MockLog(); - IMergerLog mergerLog = MergerLog.wrapSdkLog(log); - ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() { - @Override - public int queryCodenameApiLevel(@NonNull String codename) { - if ("ApiCodename1".equals(codename)) { - return 1; - } else if ("ApiCodename10".equals(codename)) { - return 10; - } - return ICallback.UNKNOWN_CODENAME; - } - }); - - // Test name contains "enable_extractprefix" to enable manifest extract prefix - if (getName().contains("enable_extractprefix")) { - merger.setExtractPackagePrefix(true); - } - boolean processOK = merger.process(testFiles.getActualResult(), - testFiles.getMain(), - testFiles.getLibs(), - testFiles.getInjectAttributes()); - - String expectedErrors = testFiles.getExpectedErrors().trim(); - StringBuilder actualErrors = new StringBuilder(); - for (String s : log.getMessages()) { - actualErrors.append(s); - if (!s.endsWith("\n")) { - actualErrors.append('\n'); - } - } - assertEquals("Error generated during merging", - expectedErrors, actualErrors.toString().trim()); - - if (testFiles.getShouldFail()) { - assertFalse("Merge process() returned true, expected false", processOK); - } else { - assertTrue("Merge process() returned false, expected true", processOK); - } - - // Test result XML. There should always be one created - // since the process action does not stop on errors. - log.clear(); - Document document = MergerXmlUtils.parseDocument(testFiles.getActualResult(), mergerLog); - assertNotNull(document); - assert document != null; // for Eclipse null analysis - String actual = MergerXmlUtils.printXmlString(document, mergerLog); - assertEquals("Error parsing actual result XML", "[]", log.toString()); - log.clear(); - document = MergerXmlUtils.parseDocument( - testFiles.getExpectedResult(), - mergerLog, - new FileAndLine("", 0)); - assertNotNull("Failed to parse result document: " + testFiles.getExpectedResult(),document); - assert document != null; - String expected = MergerXmlUtils.printXmlString(document, mergerLog); - assertEquals("Error parsing expected result XML", "[]", log.toString()); - assertEquals("Error comparing expected to actual result", expected, actual); - - testFiles.cleanup(); - } - -} diff --git a/manifmerger/tests/src/com/android/manifmerger/data/00_noop.xml b/manifmerger/tests/src/com/android/manifmerger/data/00_noop.xml deleted file mode 100755 index 2160f69..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/00_noop.xml +++ /dev/null @@ -1,229 +0,0 @@ -# -# Syntax: -# - Lines starting with # are ignored (anywhere, as long as # is the first char). -# - Lines before the first @delimiter are ignored. -# - Empty lines just after the @delimiter and before the first < XML line are ignored. -# - Valid delimiters are @main for the XML of the main app manifest. -# - Following delimiters are @libXYZ, read in the order of definition. The name can be -# anything as long as it starts with "@lib". -# - Last delimiter should be @result. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - -@lib2 - -# An empty library is not supported. It must be a valid XML file. - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest2_lib2.xml:1] Missing 'package' attribute in manifest. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml b/manifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml deleted file mode 100755 index b939dd7..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/01_ignore_app_attr.xml +++ /dev/null @@ -1,69 +0,0 @@ -# -# Test: -# - Attributes from the application element in a library are ignored (except name) -# - Comments from nodes ignored in libraries are not merged either. -# - -@main - - - - - - - - - -@lib1 - - - - - - - - - -@result - - - - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml b/manifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml deleted file mode 100755 index ed0dbbc..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/02_ignore_instrumentation.xml +++ /dev/null @@ -1,62 +0,0 @@ -# -# Test: -# - Instrumentation element from libraries are not merged in main manifest. -# - -@main - - - - - - - - -@lib1 - - - - - - - - -@result - - - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/03_inject_attributes.xml b/manifmerger/tests/src/com/android/manifmerger/data/03_inject_attributes.xml deleted file mode 100755 index 0a7057c..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/03_inject_attributes.xml +++ /dev/null @@ -1,53 +0,0 @@ -# -# Test: -# - Inject attributes in a main manifest. -# - -@inject -/manifest|http://schemas.android.com/apk/res/android versionCode=101 -/manifest|http://schemas.android.com/apk/res/android versionName=1.0.1 -/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion=10 -/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion=14 -/manifest/application|http://schemas.android.com/apk/res/android label=null -/manifest/application|http://schemas.android.com/apk/res/android icon=null - -@main - - - - - - - - -@result - - - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/04_inject_attributes.xml b/manifmerger/tests/src/com/android/manifmerger/data/04_inject_attributes.xml deleted file mode 100755 index 57f4e84..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/04_inject_attributes.xml +++ /dev/null @@ -1,63 +0,0 @@ -# -# Test: -# - Inject attributes in a main manifest. -# The attributes are injected and then the merge is done. In this case the app -# starts with a minSdkVersion of 20, which is higher than the lib1's 15 value. -# However the injection replaces it by 10, which is now lower than the lib's -# version and thus a warning will be generated. -# - -@fails - -@inject -/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion=10 -/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion=14 -/manifest/application|http://schemas.android.com/apk/res/android label=null -/manifest/application|http://schemas.android.com/apk/res/android icon=null - -@main - - - - - - - - - -@lib1 - - - - - - - - - -@result - - - - - - - - - -@errors - -E [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1.xml:3] Main manifest has but library uses minSdkVersion='15' -W [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1.xml:3] Main manifest has but library uses targetSdkVersion='16' diff --git a/manifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml deleted file mode 100755 index 59c5c42..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/10_activity_merge.xml +++ /dev/null @@ -1,378 +0,0 @@ -# -# Test: -# - Activities from libraries are merged in the main manifest. -# - Acts on activity / activity-alias / service / receiver / provider. -# - Elements are merged as-is with the first comment element preceding them. -# - Whitespace preceding the merged elements is transfered over too. -# -# Note: -# - New elements are always merged at the end of the application element. -# - It's an error if an element with the same @name attribute is defined -# or merged more than once unless the definition is *exactly* the same, -# the "same" being defined by the exact XML elements, whitespace excluded. -# -# This tests that a normal merge is done as expected. -# There's a warning because one of the activities from lib2 is already defined -# in the main but it's purely identical so it's not an error. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1_widget - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2_activity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib3_alias - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# from @lib1_widget - - - - - - - - - - - - - - - - - -# from @lib2_activity - - - - - - - - - - - -# from @lib3_alias - - - - - - - - - - - - - - - -@errors - -P [ManifestMergerTest0_main.xml:31, ManifestMergerTest2_lib2_activity.xml:6] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. -P [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:19] Skipping identical /manifest/application/activity[@name=com.example.LibActivity2] element. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml deleted file mode 100755 index ef163b0..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml +++ /dev/null @@ -1,406 +0,0 @@ -# -# Test: -# - Activities from libraries are merged in the main manifest. -# - Acts on activity / activity-alias / service / receiver / provider. -# - Elements are merged as-is with the first comment element preceding them. -# - Whitespace preceding the merged elements is transfered over too. -# -# Note: -# - New elements are always merged at the end of the application element. -# - It's an error if an element with the same @name attribute is defined -# or merged more than once unless the definition is *exactly* the same, -# the "same" being defined by the exact XML elements, whitespace excluded. -# -# This tests that an error is generated because the libraries define -# activities which are already in the main, with slightly different XML content: -# number and *order* of elements must match, attributes must match. -# - -@fails - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1_widget - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2_activity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib3_alias - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# from @lib1_widget - - - - - - - - - - -# from @lib2_activity - - - - - - - - - - - -# from @lib3_alias - - - - - - - - - - - - - - - - -@errors - -E [ManifestMergerTest0_main.xml:32, ManifestMergerTest1_lib1_widget.xml:16] Trying to merge incompatible /manifest/application/activity[@name=com.example.WidgetConfigurationUI] element: - ---(end reached) - -++ -E [ManifestMergerTest0_main.xml:38, ManifestMergerTest2_lib2_activity.xml:6] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity] element: - --- --- -++ -E [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:19] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity2] element: - - @android:name="android.intent.category.LAUNCHER"> --- --- ---(end reached) - - @android:name="android.intent.action.MAIN"> - @android:name="android.intent.category.LAUNCHER"> -++ -++ diff --git a/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml deleted file mode 100755 index 7b5aed3..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml +++ /dev/null @@ -1,211 +0,0 @@ -# -# Test: -# - Activities from libraries are merged in the main manifest. -# - Acts on activity / activity-alias / service / receiver / provider. -# - Elements are merged as-is with the first comment element preceding them. -# - Whitespace preceding the merged elements is transfered over too. -# -# Note: -# - New elements are always merged at the end of the application element. -# - It's an error if an element with the same @name attribute is defined -# or merged more than once unless the definition is *exactly* the same, -# the "same" being defined by the exact XML elements, whitespace excluded. -# -# This tests that an error is generated because the libraries define -# aliases which are already defined differently. -# - -@fails - -@main - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/activity-alias[@name=com.example.alias.MyActivity1] element. -E [ManifestMergerTest0_main.xml:13, ManifestMergerTest1_lib1.xml:14] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity2] element: - -++ -E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity3] element: - --- - -++ -++ - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - -@errors - -P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/service[@name=com.example.AppService1] element. -E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService2] element: - --- ---(end reached) - -++ -++ -E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService3] element: - --- --- - -++ -++(end reached) diff --git a/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml deleted file mode 100755 index 777ba22..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml +++ /dev/null @@ -1,186 +0,0 @@ -# -# Test: -# - Activities from libraries are merged in the main manifest. -# - Acts on activity / activity-alias / service / receiver / provider. -# - Elements are merged as-is with the first comment element preceding them. -# - Whitespace preceding the merged elements is transfered over too. -# -# Note: -# - New elements are always merged at the end of the application element. -# - It's an error if an element with the same @name attribute is defined -# or merged more than once unless the definition is *exactly* the same, -# the "same" being defined by the exact XML elements, whitespace excluded. -# -# This tests that an error is generated because the libraries define -# receivers which are already defined differently. -# - -@fails - -@main - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver1] element. -E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:13] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver2] element: - --- - -++ -++(end reached) -E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver3] element: - - - - - - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml deleted file mode 100755 index bd0c8fe..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml +++ /dev/null @@ -1,153 +0,0 @@ -# -# Test: -# - Activities from libraries are merged in the main manifest. -# - Acts on activity / activity-alias / service / receiver / provider. -# - Elements are merged as-is with the first comment element preceding them. -# - Whitespace preceding the merged elements is transfered over too. -# -# Note: -# - New elements are always merged at the end of the application element. -# - It's an error if an element with the same @name attribute is defined -# or merged more than once unless the definition is *exactly* the same, -# the "same" being defined by the exact XML elements, whitespace excluded. -# -# This tests that an error is generated because the libraries define -# providers which are already defined differently. -# - -@fails - -@main - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - -@errors - -P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/provider[@name=com.example.Provider1] element. -E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider2] element: - --- ---(end reached) - -E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider3] element: - --- - -++ -++(end reached) diff --git a/manifmerger/tests/src/com/android/manifmerger/data/16_fqcn_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/16_fqcn_merge.xml deleted file mode 100755 index 8414a3c..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/16_fqcn_merge.xml +++ /dev/null @@ -1,129 +0,0 @@ -# -# Test how FQCN class names are expanded and handled: -# - A library application can be merged doesn't have an app class name. -# - A library application can be merged if it has the same class name as the app. -# - A partial class name is expanded using the package name in a library or app. -# - -@main - - - - - - - - - - - -@lib1_widget - - - - - - - - - - - - -@lib2_activity - - - - - - - - - - - - - - - - - -@lib3_alias - - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - -# from @lib1_widget - - - - - -# from @lib2_activity - - - - - - -# from @lib3_alias - - - - - - - - - -@errors - -P [ManifestMergerTest0_main.xml:6, ManifestMergerTest2_lib2_activity.xml:5] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity] element. -P [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:8] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity2] element. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/17_fqcn_conflict.xml b/manifmerger/tests/src/com/android/manifmerger/data/17_fqcn_conflict.xml deleted file mode 100755 index b4f5ea0..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/17_fqcn_conflict.xml +++ /dev/null @@ -1,120 +0,0 @@ -# -# Test how FQCN class names are expanded and handled: -# - A library application can be merged doesn't have an app class name. -# - A library application can be merged if it has the same class name as the app. -# - A partial class name is expanded using the package name in a library or app. -# -# All tests fail with just warnings, no solid errors. -# - -@main - - - - - - - - - - - -@lib1_widget - - - - - - - - - - -@lib2_widget - - - - - - - - - - - -@lib3_widget - - - - - - - - - - -@lib4_not_package - - - - - - - - - - - - - - - - -@result - - - - - - - - - - -# from @lib4_alias - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:3, ManifestMergerTest1_lib1_widget.xml:4] Main manifest has but library uses name='com.example.lib1.TheApp'. -W [ManifestMergerTest0_main.xml:3, ManifestMergerTest2_lib2_widget.xml:4] Main manifest has but library uses backupAgent='com.example.lib2.MyBackupAgent'. -P [ManifestMergerTest0_main.xml:6, ManifestMergerTest2_lib2_widget.xml:6] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity] element. -W [ManifestMergerTest4_lib4_not_package.xml:1] Missing 'package' attribute in manifest. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml deleted file mode 100755 index a5eecce..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/20_uses_lib_merge.xml +++ /dev/null @@ -1,176 +0,0 @@ -# -# Test merge of uses-library: -# - Merge is OK if destination already has one with the same @name. -# - required defaults to "true" -# - when merging, a required=true (explicit or implicit) overwrites a required=false. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - -# required=false from lib1 is ignored, it stays at the default - - - - - - -# lib1 keeps it required=false but lib2 makes it switch to required=true - - - - - -# new from lib1 - - - -# new from lib1, but lib2 makes it switch to required=true - - - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml b/manifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml deleted file mode 100755 index 9e8f5a0..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/21_uses_lib_errors.xml +++ /dev/null @@ -1,202 +0,0 @@ -# -# Test merge of uses-library: -# - Merge is OK if destination already has one with the same @name. -# - required defaults to "true" -# - when merging, a required=true (explicit or implicit) overwrites a required=false. -# - -@fails - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - -# lib1 keeps it required=false but lib2 makes it switch to required=true - - - - - - - - -# new from lib1 - - - -# new from lib1, but lib2 makes it switch to required=true - - - - - - - - -@errors - -E [ManifestMergerTest1_lib1.xml:6] Undefined 'name' attribute in /manifest/application/uses-library. -E [ManifestMergerTest1_lib1.xml:7] Undefined 'name' attribute in /manifest/application/uses-library. -E [ManifestMergerTest1_lib1.xml:8] Undefined 'name' attribute in /manifest/application/uses-library. -W [ManifestMergerTest0_main.xml:12] Invalid attribute 'required' in /manifest/application/uses-library[@name=com.example.SomeLibrary2_RequiredTrue] element: -Expected 'true' or 'false' but found 'booh!'. -W [ManifestMergerTest0_main.xml:15] Manifest has more than one /manifest/application/uses-library[@name=com.example.SomeLibrary3_RequiredFalse] element. -W [ManifestMergerTest1_lib1.xml:17] Invalid attribute 'required' in /manifest/application/uses-library[@name=com.example.SomeLibrary4_RequiredFalse] element: -Expected 'true' or 'false' but found 'foo'. -W [ManifestMergerTest0_main.xml:15] Manifest has more than one /manifest/application/uses-library[@name=com.example.SomeLibrary3_RequiredFalse] element. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml deleted file mode 100755 index 07208ad..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/25_permission_merge.xml +++ /dev/null @@ -1,259 +0,0 @@ -# -# Text permission, permission-group and permission-tree: -# - Libraries can add any of these elements as long as they don't conflict -# with the destination: either the element must not be at all in the destination -# (as identified by the name) or it must match exactly. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Added by lib1 - - - - - - - -# Added by lib2 - - - - - - - - - - -@errors diff --git a/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml deleted file mode 100755 index bd9a4f1..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml +++ /dev/null @@ -1,314 +0,0 @@ -# -# Text permission, permission-group and permission-tree: -# - Libraries can add any of these elements as long as they don't conflict -# with the destination: either the element must not be at all in the destination -# (as identified by the name) or it must match exactly. -# -# This one tests that duplicate definitions that are strictly equal generate errors -# with some (hopefully useful) diff. -# - -@fails - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Added by lib1 - - - - - - - -# Added by lib2 - - - - - - - - - - -@errors - -E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:4] Trying to merge incompatible /manifest/permission[@name=com.example.DangerWillRobinson] element: - - -E [ManifestMergerTest0_main.xml:16, ManifestMergerTest1_lib1.xml:5] Trying to merge incompatible /manifest/permission-group[@name=com.example.MasterControlPermission] element: - - -++ -E [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:6] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree] element: - - -E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:7] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree1] element: - --- ---(end reached) - -++ diff --git a/manifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml deleted file mode 100755 index ecc644a..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/28_uses_perm_merge.xml +++ /dev/null @@ -1,156 +0,0 @@ -# -# Text uses-permission: -# - Libraries can add any of these elements as long as they don't conflict -# with the destination: either the element must not be at all in the destination -# (as identified by the name) or it must match exactly. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - -# Added by lib1 - - - - - - -# Added by lib2 - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml b/manifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml deleted file mode 100755 index bcee4ce..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/30_uses_sdk_ok.xml +++ /dev/null @@ -1,86 +0,0 @@ -# -# Test uses-sdk: add a uses-sdk from an app that doesn't define one. -# - -@main - - - - - - - - - - -@lib1 - - - - - - - - - -@lib2 - - - - - - - - - -@lib3 - - - - - - - - - -@result - - - - - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml b/manifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml deleted file mode 100755 index b94efe8..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml +++ /dev/null @@ -1,70 +0,0 @@ -# -# Test uses-sdk: it's ok for a library to have a smaller minSdkVersion than the main manifest. -# - -@main - - - - - - - - - - -@lib1 - - - - - - - - - -@lib2 - - - - - - - - -@lib3 - - - - - - - - -@result - - - - - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml b/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml deleted file mode 100755 index 8edbedb..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml +++ /dev/null @@ -1,148 +0,0 @@ -# -# Test uses-sdk: it's an error for a library to require a minSdkVersion higher than the -# one defined in the main manifest. -# -# Also a uses-sdk with a lack of minSdkVersion is equivalent to using version=1. -# - -@fails - -@main - - - - - - - - - - - -@lib1 - - - - - - - - - -@lib2 - - - - - - - - -@lib3 - - - - - - - - -@lib4_parsingError - - - - - - - - - -@lib5_parsingError - - - - - - - - - -@lib6_parsingError - - - - - - - - - -@lib7_parsingError - - - - - - - - - -@lib8_parsingCodename - - - - - - - - - -@result - - - - - - - - - - - -@errors - -E [ManifestMergerTest0_main.xml:4, ManifestMergerTest1_lib1.xml:4] Main manifest has but library uses minSdkVersion='4' -Note: main manifest lacks a declaration, which defaults to value 1. -E [ManifestMergerTest0_main.xml:4, ManifestMergerTest2_lib2.xml:3] Main manifest has but library uses minSdkVersion='10' -Note: main manifest lacks a declaration, which defaults to value 1. -E [ManifestMergerTest0_main.xml:4, ManifestMergerTest3_lib3.xml:3] Main manifest has but library uses minSdkVersion='11' -Note: main manifest lacks a declaration, which defaults to value 1. -E [ManifestMergerTest4_lib4_parsingError.xml:4] Failed to parse : must be an integer number or codename. -E [ManifestMergerTest5_lib5_parsingError.xml:4] Failed to parse : must be an integer number or codename. -E [ManifestMergerTest6_lib6_parsingError.xml:4] Failed to parse : must be an integer number or codename. -E [ManifestMergerTest7_lib7_parsingError.xml:4] Failed to parse : must be an integer number or codename. -E [ManifestMergerTest7_lib7_parsingError.xml:4] Failed to parse : must be an integer number or codename. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml deleted file mode 100755 index df8b717..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml +++ /dev/null @@ -1,77 +0,0 @@ -# -# Test uses-sdk: there's a warning if the main manifest defines a targetSdkVersion that -# is smaller than what the libraries target. -# - -@fails - -@main - - - - - - - - - - - -@lib1 - - - - - - - - - -@lib2 - - - - - - - - -@result - - - - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:4, ManifestMergerTest1_lib1.xml:4] Main manifest has but library uses targetSdkVersion='11' diff --git a/manifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml b/manifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml deleted file mode 100755 index d14dcaa..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/40_uses_feat_merge.xml +++ /dev/null @@ -1,178 +0,0 @@ -# -# Test merge of uses-feature: -# - Merge is OK if destination already has one with the same @name. -# - required defaults to "true" -# - when merging, a required=true (explicit or implicit) overwrites a required=false. -# -# Note: uses-feature with android:glEsVersion is dealt with in another test case. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - -@result - - - - - - - -# required=false from lib1 is ignored, it stays at the default - - - - - - -# lib1 keeps it required=false but lib2 makes it switch to required=true - - - - - - - - - -# new from lib1 - - - -# new from lib1, but lib2 makes it switch to required=true - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml b/manifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml deleted file mode 100755 index b86f74a..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/41_uses_feat_errors.xml +++ /dev/null @@ -1,205 +0,0 @@ -# -# Test merge of uses-feature: -# - Merge is OK if destination already has one with the same @name. -# - required defaults to "true" -# - when merging, a required=true (explicit or implicit) overwrites a required=false. -# -# Note: uses-feature with android:glEsVersion is dealt with in another test case. -# - -@fails - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - -# lib1 keeps it required=false but lib2 makes it switch to required=true - - - -# in case of duplicated name, they are all modified. - - - - - - - - - -# new from lib1 - - - -# new from lib1, but lib2 makes it switch to required=true - - - - - - -@errors - -E [ManifestMergerTest1_lib1.xml:4] Undefined 'name' attribute in /manifest/uses-feature. -E [ManifestMergerTest1_lib1.xml:5] Undefined 'name' attribute in /manifest/uses-feature. -E [ManifestMergerTest1_lib1.xml:6] Undefined 'name' attribute in /manifest/uses-feature. -W [ManifestMergerTest0_main.xml:10] Invalid attribute 'required' in /manifest/uses-feature[@name=com.example.SomeFeature2_RequiredTrue] element: -Expected 'true' or 'false' but found 'booh!'. -W [ManifestMergerTest0_main.xml:13] Manifest has more than one /manifest/uses-feature[@name=com.example.SomeFeature3_RequiredFalse] element. -W [ManifestMergerTest1_lib1.xml:15] Invalid attribute 'required' in /manifest/uses-feature[@name=com.example.SomeFeature4_RequiredFalse] element: -Expected 'true' or 'false' but found 'foo'. -W [ManifestMergerTest0_main.xml:13] Manifest has more than one /manifest/uses-feature[@name=com.example.SomeFeature3_RequiredFalse] element. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml b/manifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml deleted file mode 100755 index 57937a6..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/45_uses_feat_gles_once.xml +++ /dev/null @@ -1,126 +0,0 @@ -# -# Test merge of uses-feature with android:glEsVersion: -# - Error if defined in lib+dest with dest < lib. -# - Never automatically change dest. -# - Default implied value is 1.0 (0x00010000). -# -# This tests a case that works. Also checks that glEsVersion attributes are stripped -# when merging uses-feature with the name attribute. -# - -@main - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - -@result - - - - - - - - - - - - -# lib1 adds this new node. Note how the glEsVersion=2.1 is stripped out. - - - -# lib2 adds this new node. Note how the glEsVersion=2.0 is stripped out. - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml b/manifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml deleted file mode 100755 index 936d009..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml +++ /dev/null @@ -1,162 +0,0 @@ -# -# Test merge of uses-feature with android:glEsVersion: -# - Error if defined in lib+dest with dest < lib. -# - Never automatically change dest. -# - Default implied value is 1.0 (0x00010000). -# -# This tests a case that doesn't works because the main manifest doesn't declare -# the value and thus defaults to 1.0, so libraries with higher requirements will -# conflict. -# - -@fails - -@main - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - -# lib1 adds this new node. Note how the glEsVersion=2.1 is stripped out. - - - -# lib2 adds this new node. Note how the glEsVersion=2.0 is stripped out. - - - - - -@errors - -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:4] Main manifest has but library uses glEsVersion='0x00020001' -Note: main manifest lacks a declaration, and thus defaults to glEsVersion=0x00010000. -W [ManifestMergerTest2_lib2.xml:12] Ignoring because it's smaller than 1.0. -W [ManifestMergerTest2_lib2.xml:15] Ignoring because it's smaller than 1.0. -E [ManifestMergerTest2_lib2.xml:21] Failed to parse : must be an integer in the form 0x00020001. -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:18] Main manifest has but library uses glEsVersion='0xffffffff' -Note: main manifest lacks a declaration, and thus defaults to glEsVersion=0x00010000. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml deleted file mode 100755 index b1cb3f9..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/50_uses_conf_warning.xml +++ /dev/null @@ -1,162 +0,0 @@ -# -# Test uses-configuration: -# - it's OK if a library defines one or multiple times an element already in the application. -# - it's a warning if the library defines an element not in the application. -# - this does not actually merge anything. The XML is not changed at all. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - -@lib2 - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/uses-configuration defined in library, missing from main manifest: - - @android:reqFiveWayNav = false - @android:reqNavigation = trackball - @android:reqTouchScreen = finger diff --git a/manifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml deleted file mode 100755 index 363fb2b..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/52_support_screens_warning.xml +++ /dev/null @@ -1,162 +0,0 @@ -# -# Test supports-screens: -# - it's OK if a library defines one or multiple times an element already in the application. -# - it's a warning if the library defines an element not in the application. -# - this does not actually merge anything. The XML is not changed at all. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - -@lib2 - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/supports-screens defined in library, missing from main manifest: - - @android:resizeable = false - @android:smallScreens = false diff --git a/manifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml deleted file mode 100755 index 1e1c2d2..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/54_compat_screens_warning.xml +++ /dev/null @@ -1,204 +0,0 @@ -# -# Test compatible-screens: -# - it's OK if a library defines one or multiple times an element already in the application. -# - it's a warning if the library defines an element not in the application. -# - this does not actually merge anything. The XML is not changed at all. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/compatible-screens defined in library, missing from main manifest: - - - @android:screenDensity = ldpi - @android:screenSize = small - - @android:screenDensity = mdpi - @android:screenSize = normal -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:9] /manifest/compatible-screens defined in library, missing from main manifest: - - - @android:screenDensity = ldpi - @android:screenSize = small -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:13] /manifest/compatible-screens defined in library, missing from main manifest: - - - @android:screenDensity = ldpi - @android:screenSize = normal - - @android:screenDensity = mdpi - @android:screenSize = normal - - @android:screenDensity = hdpi - @android:screenSize = normal - - @android:screenDensity = xhdpi - @android:screenSize = normal diff --git a/manifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml deleted file mode 100755 index 114e9f4..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/56_support_gltext_warning.xml +++ /dev/null @@ -1,151 +0,0 @@ -# -# Test supports-gl-texture: -# - it's a warning if the library defines a supports-gl-texture not in the application. -# - this does not actually merge anything. The XML is not changed at all. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - -@lib2 - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest2_lib2.xml:4] /manifest/supports-gl-texture defined in library, missing from main manifest: - - @android:name = some.gl.texture3 diff --git a/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml b/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml deleted file mode 100755 index d6767f7..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml +++ /dev/null @@ -1,318 +0,0 @@ -# -# Test merge order: -# - When activity / activity-alias / service / receiver / provider are merged, -# we do a comparison to check whether the elements are already present in the -# main manifest. The order of the elements must NOT matter in the comparison, -# nor does the whitespace between them. -# - What this checks is that the order of the elements or attributes within -# the elements should not matter. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -P [ManifestMergerTest0_main.xml:37, ManifestMergerTest1_lib1.xml:26] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. -P [ManifestMergerTest0_main.xml:5, ManifestMergerTest1_lib1.xml:41] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element. -P [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:7] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element. -P [ManifestMergerTest0_main.xml:5, ManifestMergerTest2_lib2.xml:6] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element. -P [ManifestMergerTest0_main.xml:37, ManifestMergerTest2_lib2.xml:11] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. -P [ManifestMergerTest0_main.xml:18, ManifestMergerTest2_lib2.xml:20] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/65_override_app.xml b/manifmerger/tests/src/com/android/manifmerger/data/65_override_app.xml deleted file mode 100755 index 464b52f..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/65_override_app.xml +++ /dev/null @@ -1,197 +0,0 @@ -# -# Test the tools:merge="override" tag on an application. -# It essentially ignores _any_ application tag from libraries. -# All other non-application elements are merged as usual. -# - -@main - - - - - - - - - - - - - - - - - - -@lib1_widget - - - - - - - - - - - - - - - -@lib2_widget - - - - - - - - - - - - - - - - -@lib3_widget - - - - - - - - - - - - - -@lib4_not_package - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest4_lib4_not_package.xml:1] Missing 'package' attribute in manifest. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/66_remove_app.xml b/manifmerger/tests/src/com/android/manifmerger/data/66_remove_app.xml deleted file mode 100755 index a1b04b5..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/66_remove_app.xml +++ /dev/null @@ -1,53 +0,0 @@ -# -# Test how elements are removed by tools:merge="remove". -# - -@main - - - - - - - - - - - - -@lib1_widget - - - - - - - - - - - -@result - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/67_override_activities.xml b/manifmerger/tests/src/com/android/manifmerger/data/67_override_activities.xml deleted file mode 100755 index 8bcdb63..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/67_override_activities.xml +++ /dev/null @@ -1,159 +0,0 @@ -# -# Test how elements are overriden by tools:merge="override". -# The override only blocks elements that would be merged. -# - -@main - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/68_override_uses.xml b/manifmerger/tests/src/com/android/manifmerger/data/68_override_uses.xml deleted file mode 100755 index a43d13e..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/68_override_uses.xml +++ /dev/null @@ -1,205 +0,0 @@ -# -# Test how elements are overriden by tools:merge="override". -# The override only blocks elements that would be merged. -# That means items which are just checked (not merged) still produce warnings. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:19] /manifest/supports-screens defined in library, missing from main manifest: - - @android:resizeable = false - @android:smallScreens = false -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:23] /manifest/supports-gl-texture defined in library, missing from main manifest: - - @android:name = some.gl.texture3 -W [ManifestMergerTest1_lib1.xml:14] Ignoring because it's smaller than 1.0. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/69_remove_uses.xml b/manifmerger/tests/src/com/android/manifmerger/data/69_remove_uses.xml deleted file mode 100755 index c3e46c9..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/69_remove_uses.xml +++ /dev/null @@ -1,177 +0,0 @@ -# -# Test how elements are removed by tools:merge="remove". -# The removal happens is done at the end and block merges. -# That means items which are just checked (not merged) still produce warnings. -# - -@main - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@lib2 - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - - -@errors - -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:19] /manifest/supports-screens defined in library, missing from main manifest: - - @android:resizeable = false - @android:smallScreens = false -W [ManifestMergerTest0_main.xml:1, ManifestMergerTest1_lib1.xml:23] /manifest/supports-gl-texture defined in library, missing from main manifest: - - @android:name = some.gl.texture3 -W [ManifestMergerTest1_lib1.xml:14] Ignoring because it's smaller than 1.0. diff --git a/manifmerger/tests/src/com/android/manifmerger/data/70_expand_fqcns.xml b/manifmerger/tests/src/com/android/manifmerger/data/70_expand_fqcns.xml deleted file mode 100755 index 5864345..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/70_expand_fqcns.xml +++ /dev/null @@ -1,84 +0,0 @@ -# -# Test the option to extract prefixes -# - -@main - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - -@errors - - diff --git a/manifmerger/tests/src/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml b/manifmerger/tests/src/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml deleted file mode 100755 index dd11dcc..0000000 --- a/manifmerger/tests/src/com/android/manifmerger/data/71_prefixes_enable_extractprefix.xml +++ /dev/null @@ -1,84 +0,0 @@ -# -# Test the option to extract prefixes -# - -@main - - - - - - - - - - - - - - - - - - -@lib1 - - - - - - - - - - - -@result - - - - - - - - - - - - - - - - - - - - -@errors - - -- cgit v1.1