Cómo resuelve NuGet las dependencias de paquetes

Siempre que se instala o reinstala un paquete, lo que incluye la instalación como parte de un proceso de restauración, NuGet también instala los paquetes adicionales de los que depende ese primer paquete.

Es posible que esas dependencias inmediatas también tengan sus propias dependencias, lo que puede continuar hasta una profundidad arbitraria. Esto produce lo que se llama gráfico de dependencias, que describe las relaciones entre paquetes en todos los niveles.

Cuando varios paquetes tienen la misma dependencia, entonces el mismo Id. de paquete puede aparecer varias veces en el gráfico, posiblemente con restricciones de versión diferentes. Pero, en un proyecto, solo se puede usar una versión de un paquete determinado, por lo que NuGet debe elegir la versión que se usa. El proceso exacto depende del formato de administración de paquetes que se usa.

Resolución de dependencias con PackageReference

Al instalar paquetes en proyectos con el formato PackageReference, NuGet agrega referencias a un gráfico de paquete sin formato en el archivo adecuado y resuelve conflictos con antelación. Este proceso se conoce como restauración transitiva. La reinstalación o restauración de paquetes es un proceso de descarga de los paquetes enumerados en el gráfico, lo que produce compilaciones más rápidas y predecibles.

También se pueden aprovechar las versiones flotantes, como la 2.8.*, con el fin de evitar que se modifique el proyecto que va a usar la versión más reciente de un paquete. Al usar versiones flotantes, se recomienda habilitar la funcionalidad de archivo de bloqueo para garantizar la repetibilidad.

Cuando se ejecuta el proceso de restauración de NuGet antes de una compilación, primero se resuelven las dependencias en memoria y después se escribe el gráfico resultante en un archivo denominado project.assets.json.

El archivo de recursos se encuentra en MSBuildProjectExtensionsPath, cuyo valor predeterminado es la carpeta "obj" del proyecto. Después, MSBuild lee este archivo y lo convierte en un conjunto de carpetas donde se pueden encontrar posibles referencias y luego se agregan al árbol del proyecto en memoria.

El archivo project.assets.json es temporal y no se debe agregar al control de código fuente. Se muestra de forma predeterminada en .gitignore y .tfignore. Vea Paquetes y control de código fuente.

Reglas de resolución de dependencias

La restauración transitiva aplica cuatro reglas principales para resolver las dependencias: la versión aplicable más baja, versiones flotantes, coincidencias de dependencias directas y dependencias relacionadas.

Versión aplicable más baja

La regla de la versión aplicable más baja restaura la versión más baja posible de un paquete de acuerdo con sus dependencias. También se aplica a las dependencias de la aplicación o la biblioteca de clases, a menos que se declaren como flotantes.

En la ilustración siguiente, por ejemplo, 1.0-beta se considera menor que 1.0, por lo que NuGet elige la versión 1.0:

Choosing the lowest applicable version

En la ilustración siguiente, la versión 2.1 no está disponible en la fuente, pero como la restricción de versión es >= 2.1, NuGet elige la siguiente versión más baja que puede encontrar, en este caso 2.2:

Choosing the next lowest version available on the feed

Cuando una aplicación especifica un número de versión exacto, como 1.2, que no está disponible en la fuente, NuGet produce un error al intentar instalar o restaurar el paquete:

NuGet generates an error when an exact package version is not available

Versiones flotantes

Se especifica una versión de dependencia flotante con el carácter *. Por ejemplo, 6.0.*. Esta especificación de versión indica "use la versión 6.0.x más reciente"; 4.* significa "use la versión 4.x más reciente". El uso de una versión flotante reduce los cambios en el archivo de proyecto, a la vez que se mantiene al día con la versión más reciente de una dependencia. Las versiones flotantes solo se pueden especificar en el nivel de proyecto.

Cuando se usa una versión flotante, NuGet resuelve la versión más alta de un paquete que coincide con el patrón de versión. Por ejemplo, 6.0.* obtiene la versión más alta de un paquete que empieza por 6.0:

Choosing version 6.0.1 when a floating version 6.0.* is requested

Versión Versiones presentes en el servidor Solución Motivo Notas
* 1.1.0
1.1.1
1.2.0
1.3.0-alpha
1.2.0 Versión estable más alta.
1.1.* 1.1.0
1.1.1
1.1.2-alfa
1.2.0-alpha
1.1.1 Versión estable más alta que respeta el patrón especificado.
*-* 1.1.0
1.1.1
1.1.2-alfa
1.3.0-beta
1.3.0-beta Versión más alta, incluidas las versiones no estables. Disponible en la versión 16.6 de Visual Studio, versión 5.6 de NuGet, SDK de .NET Core de la versión 3.1.300
1.1.*-* 1.1.0
1.1.1
1.1.2-alfa
1.1.2-beta
1.3.0-beta
1.1.2-beta Versión más alta que respeta el patrón e incluye las versiones no estables. Disponible en la versión 16.6 de Visual Studio, versión 5.6 de NuGet, SDK de .NET Core de la versión 3.1.300

Nota:

La resolución de versiones flotantes no tiene en cuenta la enumeración de paquetes. La resolución de versiones flotantes se resolverá localmente si las condiciones se pueden cumplir con los paquetes de la carpeta global de paquetes.

Coincidencias de dependencias directas

Cuando el gráfico de paquetes de una aplicación contiene versiones diferentes de un paquete en el mismo subgráfico, y una de esas versiones es una dependencia directa en ese subgráfico, esa versión se elegirá para ese subgráfico y el resto se ignorará. Este comportamiento permite que una aplicación invalide cualquier versión de paquete en particular en el gráfico de dependencias.

En el ejemplo siguiente, la aplicación depende directamente del Paquete B con una restricción de versión de >=2.0.0. La aplicación también depende del Paquete A que, a su vez, depende también del Paquete B, pero con una restricción >=1.0.0. Dado que la dependencia del Paquete B 2.0.0 es una dependencia directa a la aplicación en el gráfico, se usa esa versión:

Application using the Direct dependency wins rule

Advertencia

La regla de coincidencias de dependencias directas puede provocar el cambio a una versión anterior del paquete, lo que podría afectar a otras dependencias del gráfico. Cuando se cambia un paquete a una versión anterior, NuGet agrega una advertencia para alertar al usuario.

Esta regla también da como resultado una mayor eficacia con un gráfico de dependencias grande. Cuando una dependencia más cercana en el mismo subgráfico tiene una versión superior a otra, NuGet ignora esa dependencia, así como las demás dependencias de esa rama del gráfico.

En el diagrama siguiente, por ejemplo, como se usa el Paquete C 2.0.0, NuGet ignora las ramas en el subgráfico que hacen referencia a una versión anterior del Paquete C:

When NuGet ignores a package in the graph, it ignores that entire branch

A través de esta regla, NuGet intenta respetar la intención del autor del paquete. En el diagrama siguiente, el autor del Paquete A ha cambiado explícitamente el Paquete C a una versión anterior (de la versión 2.0.0 a la 1.0.0).

When a package author explicitly downgrades, NuGet honors that.

El propietario de la aplicación puede optar por actualizar el Paquete C a una versión superior a la 2.0.0, de este modo ya no se cambia más a la versión anterior. En este caso, no se genera ninguna advertencia.

When an application honor adds a direct dependency for a downgraded package, NuGet honors that.

Dependencias relacionadas

Cuando se hace referencia a versiones diferentes del paquete en diferentes subgráficos del gráfico de la aplicación, NuGet usa la versión más baja que cumpla todos los requisitos de versión (como sucede con las reglas de versión aplicable más baja y versiones flotantes). En la imagen siguiente, por ejemplo, la versión 2.0.0 del Paquete B satisface la otra restricción >=1.0.0 y, por tanto, es la que se usa:

Resolving cousin dependencies using the lower version that satisfies all constraints

Tenga en cuenta que los paquetes no necesitan estar en la misma distancia para que se aplique la regla de dependencias relacionadas. En el diagrama siguiente, se elige el Paquete D 2.0.0 en el subgráfico del Paquete C y el Paquete D 3.0.0 en el subgráfico del Paquete A. En el subgráfico Aplicación, no hay ninguna dependencia directa para el Paquete D, por lo que se aplica la regla de versión más baja aplicable y se elige la versión 3.0.0.

Resolving cousin dependencies using the lower version that satisfies all constraints at different distances

En algunos casos, no es posible cumplir todos los requisitos de versión. Como se muestra a continuación, si el Paquete A requiere exactamente el Paquete B 1.0.0 y el Paquete C requiere el Paquete B >=2.0.0, NuGet no puede resolver las dependencias y se produce un error.

Unresolvable dependencies due to an exact version requirement

En estas situaciones, el consumidor de nivel superior (la aplicación o el paquete) debe agregar su propia dependencia directa en el Paquete B para que se aplique la regla de coincidencias de dependencias directas.

Resolución de dependencias con packages.config

Con packages.config, las dependencias de un proyecto se escriben en packages.config como una lista plana. Todas las dependencias de esos paquetes también se escriben en la misma lista. Cuando se instalan paquetes, es posible que NuGet también modifique el archivo .csproj, app.config, web.config y otros archivos individuales.

Con packages.config, NuGet intenta resolver los conflictos de dependencias durante la instalación de cada paquete individual. Es decir, si se está instalando el Paquete A y depende del Paquete B, y el Paquete B ya aparece en packages.config como una dependencia de algo más, NuGet compara las versiones del Paquete B que se solicitan e intenta buscar una versión que satisfaga todas las restricciones de versión. En concreto, NuGet selecciona la versión principal.secundaria más baja que cumpla las dependencias.

De forma predeterminada, NuGet 2.8 busca la versión de revisión más antigua (vea Notas de la versión de NuGet 2.8). Puede controlar esta configuración mediante el atributo DependencyVersion de NuGet.Config y el modificador -DependencyVersion en la línea de comandos.

El proceso de packages.config para resolver dependencias se complica en gráficos de dependencias más grandes. En cada instalación de un paquete nuevo es necesario recorrer el gráfico completo y aumenta la posibilidad de conflictos de versiones. Cuando se produce un conflicto, la instalación se detiene, lo que deja el proyecto en un estado indeterminado, especialmente con posibles modificaciones del propio archivo de proyecto. Esto no supone un problema al usar otros formatos de administración de paquetes.

Administración de recursos de dependencia

Cuando se usa el formato PackageReference, se puede controlar qué activos de dependencias fluyen al proyecto de nivel superior. Para obtener más información, vea PackageReference.

Cuando el proyecto de nivel superior es un paquete, también tiene control sobre este flujo mediante los atributos include y exclude con las dependencias enumeradas en el archivo .nuspec. Vea Referencia de .nuspec: dependencias.

Exclusión de referencias

Hay escenarios en los que es posible que se haga referencia a ensamblados con el mismo nombre más de una vez en un proyecto, lo que produce errores de tiempo de diseño y tiempo de compilación. Considere la posibilidad de un proyecto que contiene una versión personalizada de C.dll y hace referencia al Paquete C que también contiene C.dll. Al mismo tiempo, el proyecto también depende del Paquete B que depende también del Paquete C y C.dll. Como resultado, NuGet no puede determinar qué C.dll usar, pero no se puede quitar la dependencia del proyecto del Paquete C porque el Paquete B también depende de ella.

Para resolver este problema, debe hacer referencia directamente al archivo C.dll que quiere (o bien usar otro paquete que haga referencia al archivo correcto) y, después, agregar una dependencia al Paquete C que excluya todos sus activos. Esto se realiza como se indica a continuación según el formato de administración de paquetes en uso:

  • PackageReference: agregue ExcludeAssets="All" en la dependencia:

    <PackageReference Include="PackageC" Version="1.0.0" ExcludeAssets="All" />
    
  • packages.config: quite la referencia a PackageC desde el archivo .csproj para que solo haga referencia a la versión de C.dll que quiera.

Actualizaciones de dependencias durante la instalación de paquetes

Si ya se cumple una versión de dependencia, la dependencia no se actualiza durante las instalaciones de otros paquetes. Por ejemplo, considere la posibilidad del Paquete A que depende del Paquete B y especifica 1.0 para el número de versión. El repositorio de origen contiene las versiones 1.0, 1.1 y 1.2 del Paquete B. Si se instala A en un proyecto que ya contiene la versión 1.0 de B, B 1.0 sigue en uso porque cumple la restricción de versión. Pero si el paquete A ha solicitado la versión 1.1 o posterior de B, debería instalarse B 1.2.

Resolución de errores de paquetes incompatibles

Durante la operación de restauración de un paquete, puede aparecer el error "Uno o más paquetes no son compatibles..." o que un paquete "no es compatible" con la plataforma de destino del proyecto.

Este error se produce cuando uno o varios de los paquetes a los que se hace referencia en el proyecto no indican que admiten la plataforma de destino del proyecto; es decir, el paquete no contiene un archivo DLL adecuado en su carpeta lib para una plataforma de destino compatible con el proyecto. (Vea Plataformas de destino para obtener una lista).

Por ejemplo, si un proyecto tiene como destino netstandard1.6 y se intenta instalar un paquete que solo contiene los archivos DLL en las carpetas lib\net20 y \lib\net45, se verán mensajes similares a los siguientes para el paquete y, posiblemente, sus elementos dependientes:

Restoring packages for myproject.csproj...
Package ContosoUtilities 2.1.2.3 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6). Package ContosoUtilities 2.1.2.3 supports:
  - net20 (.NETFramework,Version=v2.0)
  - net45 (.NETFramework,Version=v4.5)
Package ContosoCore 0.86.0 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6). Package ContosoCore 0.86.0 supports:
  - 11 (11,Version=v0.0)
  - net20 (.NETFramework,Version=v2.0)
  - sl3 (Silverlight,Version=v3.0)
  - sl4 (Silverlight,Version=v4.0)
One or more packages are incompatible with .NETStandard,Version=v1.6.
Package restore failed. Rolling back package changes for 'MyProject'.

Para resolver las incompatibilidades, siga estos pasos:

  • Redestine el proyecto a una plataforma que sea compatible con los paquetes que quiere usar.
  • Póngase en contacto con los autores de los paquetes y trabaje con ellos para agregar compatibilidad para la plataforma seleccionada. En las páginas de lista de paquetes de nuget.org se incluye un vínculo Póngase en contacto con el propietario para este propósito.

Sugerencia

Solución alternativa: NuGetSolver es una extensión de Visual Studio que ha desarrollado Microsoft DevLabs y está diseñada para ayudar a resolver conflictos de dependencias. Esta extensión automatiza el proceso de identificación y resolución de esos problemas. Para obtener más información, visite la página de NuGetSolver en Visual Studio Marketplace. Además, nos encantaría que nos contase su experiencia.