Skip to content

Most visited

Recently visited

navigation

Fusionar múltiples archivos de manifiesto

Tu archivo APK puede contener solo un archivo AndroidManifest.xml, pero tu proyecto de Android Studio puede contener varios, ya que pueden ser proporcionados por el conjunto principal de orígenes, las variantes de las compilaciones y las bibliotecas importadas. De esta manera, cuando creas tu app, la compilación de Gradle fusiona todos los archivos de manifiesto en un único archivo de manifiesto empaquetado en tu APK.

La herramienta de fusión de manifiesto combina todos los elementos XML de cada archivo a través de cierta heurística de fusión y de acuerdo con las preferencias de fusión definidas con atributos XML especiales. Esta página describe cómo funciona la fusión de manifiesto y cómo aplicar las preferencias de fusión para resolver conflictos.

Sugerencia: Usa la vista Merged Manifest para obtener una vista previa de los resultados de tu manifiesto fusionado y detectar errores de conflicto.

Prioridades de fusión

La herramienta de fusión combina todos los archivos de manifiesto en un solo archivo. Para ello, fusiona los archivos de manifiesto secuencialmente según la prioridad de cada uno. Por ejemplo, si tienes tres archivos de manifiesto, el manifiesto de menor prioridad se fusiona con el de la siguiente prioridad, y a su vez estos archivos fusionados se vuelven a fusionar con el manifiesto de mayor prioridad, como se ilustra en la figura 1.

Figura 1. Proceso de fusión de tres archivos de manifiesto, menor prioridad (izquierda) en mayor prioridad (derecha)

Existen tres tipos básicos de archivos de manifiesto que se pueden fusionar . A continuación se indican sus prioridades de fusión (la mayor prioridad primero):

  1. Archivo de manifiesto para tu variante de compilación

    Si tienes múltiples conjuntos de orígenes para tu variante, sus prioridades de manifiesto son las siguientes:

    1. Manifiesto de variante de compilación (como src/demoDebug/)
    2. Manifiesto de tipo de compilación (como src/debug/)
    3. Manifiesto de clase de producto (como src/demo/)

      Si estás usando dimensiones de clase, las prioridades de manifiesto corresponden a la orden en que se enumera cada dimensión en la propiedad flavorDimensions (primero la mayor prioridad).

  2. Archivo de manifiesto principal para el módulo de app
  3. Archivo de manifiesto de una biblioteca incluida

    Si tienes múltiples bibliotecas, sus prioridades de manifiesto coinciden con el orden de dependencia (el orden en que aparecen en tu bloque Gradle dependencies).

Por ejemplo, un manifiesto de biblioteca se fusiona en el manifiesto principal, y luego el manifiesto principal se fusiona en el manifiesto de la variante de compilación.

Nota: Las prioridades de fusión son las mismas para todos los conjuntos de orígenes, según se describe en Compilación con conjuntos de orígenes.

Importante: Las configuraciones de compilación del archivo build.gradle anulan cualquier atributo coincidente en los archivos de manifiesto fusionados. Por ejemplo, el minSdkVersion del archivo build.gradle anula el atributo coincidente del elemento de manifiesto <uses-sdk>. Para evitar confusiones, simplemente debes dejar afuera el elemento <uses-sdk> y definir estas propiedades solamente en el archivo build.gradle. Para obtener información detallada, consulta Configura tu compilación.

Heurística de conflictos de fusión

La herramienta de fusión puede unir de manera lógica cada elemento XML de un manifiesto con un elemento coincidente del otro manifiesto. (Para obtener detalles sobre cómo funciona el mecanismo de coincidencia, consulta el apéndice sobre fusionar políticas).

Si un elemento del manifiesto de menor prioridad no coincide con ningún elemento del manifiesto de mayor prioridad, el elemento se agregará al manifiesto fusionado. Sin embargo, si hay un elemento coincidente, la herramienta de fusión intenta combinar todos los atributos de cada uno en el mismo elemento. Si la herramienta detecta que ambos manifiestos contienen el mismo atributo con diferentes valores, se produce un conflicto de fusión.

La Tabla 1 ilustra los posibles resultados que pueden generarse cuando una herramienta de fusión intenta combinar todos los atributos en el mismo elemento.

Tabla 1. Comportamiento de fusión predeterminado para los valores de los atributos

Atributo de alta prioridad Atributo de baja prioridad Resultado fusionado del atributo
Sin valor Sin valor Sin valor (usar valor predeterminado)
Valor B Valor B
Valor A Sin valor Valor A
Valor A Valor A
Valor B Error de conflicto; debes agregar un marcador de regla de fusión

Sin embargo, existen algunas situaciones en que la herramienta de fusión se comporta de manera diferente para evitar conflictos de fusión:

Para todos los demás conflictos entre atributos, recibirás un error y deberás indicarle a la herramienta de fusión como resolverlo. Para ello, debes agregar un atributo especial en el archivo de manifiesto de mayor prioridad (consulta la siguiente sección sobre marcadores de reglas de fusión).

No depende de los valores de atributos predeterminados. Debido a que todos los atributos únicos se combinan en el mismo elemento, es posible que se produzcan resultados inesperados si el manifiesto de mayor prioridad realmente depende del valor predeterminado de un atributo sin declararlo. Por ejemplo, si el manifiesto de mayor prioridad no declara el atributo android:launchMode , usará el valor predeterminado de "standard"; pero si el manifiesto de menor prioridad declara este atributo con un valor diferente, se aplicará ese valor al manifiesto fusionado (y se anulará el valor predeterminado). Debes definir explícitamente cada atributo como quieres que sea. (Los valores predeterminados de cada atributo se documentan en la Referencia del manifiesto.)

Marcadores de reglas de fusión

Un marcador de reglas de fusión es un atributo XML que puedes usar para expresar tu preferencia para resolver conflictos de fusión o quitar elementos y atributos no deseados. Puedes aplicar un marcador a un elemento entero o solo a atributos específicos de un elemento.

Al fusionar dos archivos de manifiesto, la herramienta de fusión busca estos marcadores en el archivo de manifiesto de mayor prioridad.

Todos los marcadores pertenecen al espacio de nombres tools de Android; por ello, primero debes declarar este espacio de nombres en el elemento <manifest>, como se observa a continuación:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp"
    xmlns:tools="http://schemas.android.com/tools">

Marcadores de nodos

Para aplicar una regla de fusión a un elemento XML completo (a todos los atributos de un elemento de manifiesto específico y a todas sus etiquetas secundarias), usa los siguientes atributos:

tools:node="merge"
Fusionar todos los atributos de esta etiqueta y todos los elementos anidados cuando no hay conflictos utilizando la heurística de conflictos de fusión. Este es el comportamiento predeterminado para los elementos.

Manifiesto de baja prioridad:

<activity android:name=”com.example.ActivityOne”
    android:windowSoftInputMode=”stateUnchanged”>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Manifiesto de alta prioridad:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”
    tools:node="merge”>
</activity>

Resultado del manifiesto fusionado:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”
    android:windowSoftInputMode=”stateUnchanged”>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
tools:node="merge-only-attributes"
Fusionar solo los atributos de esta etiqueta, no fusionar los elementos anidados.

Manifiesto de baja prioridad:

<activity android:name=”com.example.ActivityOne”
    android:windowSoftInputMode=”stateUnchanged”>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <data android:type="image/*" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Manifiesto de alta prioridad:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”
    tools:node="merge-only-attributes”>
</activity>

Resultado del manifiesto fusionado:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”
    android:windowSoftInputMode=”stateUnchanged”>
</activity>
tools:node="remove"
Quitar este elemento del manifiesto fusionado. Aunque parezca que debes borrar este elemento, cuando descubres un elemento en tu manifiesto fusionado que no necesitas y que fue suministrado por un archivo de manifiesto de menor prioridad que está fuera de tu control (como una biblioteca importada), debes usar esta opción.

Manifiesto de baja prioridad:

<activity-alias android:name=”com.example.alias”>
  <meta-data android:name=”cow”
      android:value=”@string/moo”/>
  <meta-data android:name=”duck”
      android:value=”@string/quack”/>
</activity-alias>

Manifiesto de alta prioridad:

<activity-alias android:name=”com.example.alias”>
  <meta-data android:name=”cow”
      tools:node=”remove”/>
</activity-alias>

Resultado del manifiesto fusionado:

<activity-alias android:name=”com.example.alias”>
  <meta-data android:name=”duck”
      android:value=”@string/quack”/>
</activity-alias>
tools:node="removeAll"
Igual que tools:node="remove", pero quita todos los elementos que coinciden con este tipo de elemento (dentro del mismo elemento principal).

Manifiesto de baja prioridad:

<activity-alias android:name=”com.example.alias”>
  <meta-data android:name=”cow”
      android:value=”@string/moo”/>
  <meta-data android:name=”duck”
      android:value=”@string/quack”/>
</activity-alias>

Manifiesto de alta prioridad:

<activity-alias android:name=”com.example.alias”>
  <meta-data tools:node=”removeAll”/>
</activity-alias>

Resultado del manifiesto fusionado:

<activity-alias android:name=”com.example.alias”>
</activity-alias>
tools:node="replace"
Reemplazar el elemento de menor prioridad completamente. Es decir, si hay un elemento coincidente en el manifiesto de menor prioridad, ignorarlo y usar este elemento exactamente como aparece en este manifiesto.

Manifiesto de baja prioridad:

<activity-alias android:name=”com.example.alias”>
  <meta-data android:name=”cow”
      android:value=”@string/moo”/>
  <meta-data android:name=”duck”
      android:value=”@string/quack”/>
</activity-alias>

Manifiesto de alta prioridad:

<activity-alias android:name=”com.example.alias”
    tools:node=”replace”>
  <meta-data android:name=”fox”
      android:value=”@string/dingeringeding”/>
</activity-alias>

Resultado del manifiesto fusionado:

<activity-alias android:name=”com.example.alias”>
  <meta-data android:name=”fox”
      android:value=”@string/dingeringeding”/>
</activity-alias>
tools:node="strict"
Generar una falla de compilación cada vez que este elemento del manifiesto de menor prioridad no coincida exactamente con el elemento del manifiesto de mayor prioridad (excepto que se resuelva a través de otros marcadores de reglas de fusión). De esta manera, se anula la heurística de los conflictos de fusión. Por ejemplo, si el manifiesto de menor prioridad simplemente incluye un atributo adicional, la compilación falla (mientras que el comportamiento predeterminado agrega el atributo adicional al manifiesto fusionado).

Manifiesto de baja prioridad:

<activity android:name=”com.example.ActivityOne”
    android:windowSoftInputMode=”stateUnchanged”>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Manifiesto de alta prioridad:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”
    tools:node="strict”>
</activity>

De esta manera, se crea un error de fusión de manfiestos. Los dos elementos de manifiestos no pueden diferir de ninguna manera en sentido estricto. Por ello, debes aplicar otros marcadores de reglas de fusión para resolver estas diferencias. (Generalmente, estos dos se fusionarán correctamente, como se observa en el ejemplo anterior para tools:node="merge".)

Marcadores de atributos

Para aplicar una regla de fusión solo a atributos específicos en una etiqueta de manifiesto, usa los siguientes atributos. Cada atributo acepta uno o más nombres de atributos (incluido el espacio de nombres de los atributos), separados por coma.

tools:remove="attr, ..."
Quitar los atributos especificados del manifiesto fusionado. Aunque parezca que simplemente podrías borrar estos atributos, cuando el archivo de menor prioridad incluye estos atributos y quieres asegurarte de que no se incluyan en el manifiesto fusionado, debes usar esta opción.

Manifiesto de baja prioridad:

<activity android:name=”com.example.ActivityOne”
    android:windowSoftInputMode=”stateUnchanged”>

Manifiesto de alta prioridad:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”
    tools:remove=”android:windowSoftInputMode”>

Resultado del manifiesto fusionado:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”>
tools:replace="attr, ..."
Reemplazar los atributos especificados en el manifiesto de menor prioridad por los de este manifiesto. Es decir, mantener siempre los valores del manifiesto de alta prioridad.

Manifiesto de baja prioridad:

<activity android:name=”com.example.ActivityOne”
    android:theme=”@oldtheme”
    android:exported=”false”
    android:windowSoftInputMode=”stateUnchanged”>

Manifiesto de alta prioridad:

<activity android:name=”com.example.ActivityOne”
    android:theme=”@newtheme”
    android:exported=”true”
    android:screenOrientation=”portrait”
    tools:replace=”android:theme,android:exported”>

Resultado del manifiesto fusionado:

<activity android:name=”com.example.ActivityOne”
    android:theme=”@newtheme”
    android:exported=”true”
    android:screenOrientation=”portrait”
    android:windowSoftInputMode=”stateUnchanged”>
tools:strict="attr, ..."
Generar una falla de compilación cada vez que estos atributos del manifiesto de menor prioridad no coincidan exactamente con los del manifiesto de mayor prioridad. Este es el comportamiento predeterminado para todos los atributos, excepto por los que tienen comportamientos especiales, como se describe en la heurística de los conflictos de fusión.

Manifiesto de baja prioridad:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”landscape”>
</activity>

Manifiesto de alta prioridad:

<activity android:name=”com.example.ActivityOne”
    android:screenOrientation=”portrait”
    tools:strict="android:screenOrientation”>
</activity>

De esta manera, se crea un error de fusión de manifiestos. Para resolver este conflicto, debes aplicar otros marcadores de reglas de fusión. (Recuerda: Este es el comportamiento predeterminado; por ello , el ejemplo anterior tiene el mismo resultado si quitas tools:strict="screenOrientation”).

También puedes aplicar múltiples marcadores a un elemento, como se describe a continuación.

Manifiesto de baja prioridad:

<activity android:name=”com.example.ActivityOne”
    android:theme=”@oldtheme”
    android:exported=”false”
    android:allowTaskReparenting="true"
    android:windowSoftInputMode=”stateUnchanged”>

Manifiesto de alta prioridad:

<activity android:name=”com.example.ActivityOne”
    android:theme=”@newtheme”
    android:exported=”true”
    android:screenOrientation=”portrait”
    tools:replace=”android:theme,android:exported”
    tools:remove=”android:windowSoftInputMode”>

Resultado del manifiesto fusionado:

<activity android:name=”com.example.ActivityOne”
    android:theme=”@newtheme”
    android:exported=”true”
    android:allowTaskReparenting="true"
    android:screenOrientation=”portrait”>

Selector de marcadores

Si quieres aplicar los marcadores de reglas de fusión solo a una biblioteca importada específica, agrega el atributo tools:selector con el nombre del paquete de la biblioteca.

Por ejemplo, con el siguiente manifiesto, la regla de fusión remove solo se aplica cuando el archivo de manifiesto de menor prioridad pertenece a la biblioteca com.example.lib1.

<permission android:name="permissionOne"
    tools:node="remove"
    tools:selector="com.example.lib1">

Si el manifiesto de menor prioridad pertenece a otro origen, la regla de fusión remove se ignora.

Nota: Si usas este procedimiento con uno de los marcadores de atributos, este se aplicará a todos los atributos especificados en el marcador.

Override<uses-sdk> para las bibliotecas importadas

En forma predeterminada, si importas una biblioteca con un valor minSdkVersion mayor que el archivo de manifiesto principal, se produce un error y no se puede importar la biblioteca. Para que la herramienta de fusión ignore este conflicto, importe la biblioteca y mantenga el valor minSdkVersion menor de tu app, agrega el atributo overrideLibrary a la etiqueta <uses-sdk>. El valor del atributo puede ser uno o más nombres de paquetes de bibliotecas (separados por coma), que indiquen las bibliotecas que pueden anular el valor minSdkVersion del manifiesto principal.

Por ejemplo, si el manifiesto principal de tu app aplica overrideLibrary como se indica a continuación:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.app"
          xmlns:tools="http://schemas.android.com/tools">
  <uses-sdk android:targetSdkVersion="22" android:minSdkVersion="2"
            tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
...

El siguiente manifiesto se puede fusionar sin ningún error relacionado con la etiqueta &lt;uses-sdk>, y el manifiesto fusionado mantiene el valor minSdkVersion="2" del manifiesto de la app.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lib1">
   <uses-sdk android:minSdkVersion="4" />
...

Permisos del sistema implícitos

Algunas API de Android que alguna vez fueron de libre acceso a través de las app, en las últimas versiones de Android son restringidas por los permisos del sistema . Para evitar romper las app que esperan acceso a estas API, las últimas versiones de Android permiten que las app continúen accediendo a estas API sin permiso si se configuró targetSdkVersion a un valor menor que la versión en la que se agregó la restricción. Este comportamiento efectivamente otorga a la app un permiso implícito para permitir el acceso a las API. De esta manera, los manifiestos fusionados que tienen diferentes valores para targetSdkVersion pueden resultar afectados como se indica a continuación.

Si el archivo de manifiesto de menor prioridad tiene un valor menor para targetSdkVersion que le otorga un permiso implícito, y el manifiesto de mayor prioridad no tiene el mismo permiso implícito (porque su targetSdkVersion es igual o mayor que la versión en la que se agregó la restricción), la herramienta de fusión explícitamente agrega el permiso del sistema al manifiesto fusionado.

Por ejemplo, si tu app configura la targetSdkVersion a 4 o una versión superior e importa una biblioteca con la targetSdkVersion configurada como 3 o anterior, la herramienta de fusión agrega el permiso WRITE_EXTERNAL_STORAGE al manifiesto fusionado. La Tabla 2 enumera todos los posibles permisos que se pueden agregar a tu manifiesto fusionado.

Nota: Si configuraste la targetSdkVersion de tu app en 23 o superior, debes realizar solicitudes de permiso de tiempo de ejecución para cualquier permiso peligroso cuando tu app intenta acceder a las API protegidas por estos permisos. Para obtener más detalles, consulta Trabajo con permisos del sistema.

Tabla 2. Lista de permisos que la herramienta de fusión puede agregar al manifiesto fusionado

El manifiesto de menor prioridad declara Permisos agregados al manifiesto fusionado
targetSdkVersion es 3 o menor WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE
targetSdkVersion es 15 o menor y utiliza READ_CONTACTS READ_CALL_LOG
targetSdkVersion es 15 o menor y utiliza WRITE_CONTACTS WRITE_CALL_LOG

Inspeccionar el manifiesto fusionado y buscar conflictos

Aun antes de compilar tu APK, puedes obtener una vista previa de tu manifiesto fusionado. Para ello, debes abrir tu archivo AndroidManifest.xml en Android Studio, y luego hacer clic en la pestaña Merged Manifest que se encuentra en la parte inferior del editor.

La vista Merged Manifest muestra los resultados del manifiesto fusionado en la parte izquierda e incluye información sobre cada manifiesto fusionado a la derecha, como se observa en la figura 2. Los elementos que fueron fusionados a partir de archivos de baja prioridad se destacan en diferentes colores, a la izquierda. El significado de cada color se especifica en Manifest Sources, a la derecha.

Figura 2. Vista del manifiesto fusionado

Los archivos de manifiesto que fueron parte de la compilación pero no aportaron elementos o atributos se enumeran en Other Manifest Files, a la derecha.

Para ver información sobre el origen de un elemento, si haces clic sobre un elemento en la parte izquierda, podrás ver los detalles en Merging Log, a la derecha.

Si se produce algún conflicto, este aparecerá en Merging Errors, en la parte derecha con una recomendación sobre cómo resolver el conflicto utilizando los marcadores de reglas de fusión. También se incluyen errores impresos en la ventana Event Log (selecciona View > Tool Windows > Event Log).

Si quieres ver un registro completo del árbol de decisiones de fusión, puedes buscar el archivo de registro en el directorio build/outputs/logs/ de tu módulo, denominado manifest-merger-buildVariant-report.txt.

Apéndice: Políticas de fusión

La herramienta de fusión de manifiestos puede unir de manera lógica cada elemento XML de un archivo de manifiesto con un elemento coincidente de otro archivo. La herramienta une los elementos a través de una "clave de coincidencia", que puede ser un valor de atributo único (como android:name) o la exclusividad natural de la etiqueta propiamente dicha (por ejemplo, puede haber un solo elemento <supports-screen>). Si los dos manifiestos tienen el mismo elemento XML, la herramienta fusiona ambos mediante una de las tres políticas de fusión:

Fusionar
Combina todos los atributos sin conflictos en la misma etiqueta y fusiona los elementos secundarios según sus respectivas políticas de fusión. Si alguno de los atributos tiene un conflicto con otro atributo, fusionarlos con los marcadores de reglas de fusión.
Fusionar solo elementos secundarios
No combina ni fusiona los atributos (solo mantiene los atributos provistos por el archivo de manifiesto de mayor prioridad), y fusiona los elementos secundarios según sus políticas de fusión.
Mantener
Deja el elemento "como se encuentra" y lo agrega al elemento principal común en el archivo fusionado. Esta opción solo se usa cuando es aceptable que haya diferentes declaraciones del mismo elemento.

La Tabla 1 enumera cada tipo de elemento, el tipo de política de fusión utilizado y la clave utilizada para determinar la coincidencia de elementos entre dos manifiestos.

Tabla<manifest> 3. Políticas de fusión de elementos de manifiesto y claves de coincidencia

atributo atributo atributo atributo atributo atributo atributo atributo atributo atributo atributo atributo atributo atributo atributo
Elemento Política de fusión Clave de coincidencia
<action> Fusionar android:name
<activity> Fusionar android:name
<application> Fusionar Solo hay uno por
<category> Fusionar android:name
<data> Fusionar Solo hay uno por <intent-filter>
<grant-uri-permission> Fusionar Solo hay uno por <provider>
<instrumentation> Fusionar android:name
<intent-filter> Mantener Sin coincidencia, se permiten varias declaraciones dentro del elemento principal
<manifest> Fusionar solo elementos secundarios Solo hay uno por archivo
<meta-data> Fusionar android:name
<path-permission> Fusionar Solo hay uno por <provider>
<permission-group> Fusionar android:name
<permission> Fusionar android:name
<permission-tree> Fusionar android:name
<provider> Fusionar android:name
<receiver> Fusionar android:name
<screen> Fusionar android:screenSize
<service> Fusionar android:name
<supports-gl-texture> Fusionar android:name
<supports-screen> Fusionar Solo hay uno por <manifest>
<uses-configuration> Fusionar Solo hay uno por <manifest>
<uses-feature> Fusionar android:name atributo (si no está presente, el atributo android:glEsVersion)
<uses-library> Fusionar android:name
<uses-permission> Fusionar android:name
<uses-sdk> Fusionar Solo hay uno por <manifest>
Elementos personalizados Fusionar Sin coincidencia, son desconocidos para la herramienta de fusion, por lo que siempre se incluyen en el manifiesto fusionado
This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a one-minute survey?
Help us improve Android tools and documentation.