Gerentes de dependencia



En este artículo, le diré cuáles son los administradores de paquetes similares en estructura interna, algoritmo de operación y cuáles son sus diferencias fundamentales. Miré a los administradores de paquetes diseñados para el desarrollo en iOS / OS X, pero el contenido del artículo con algunas suposiciones se aplica a otros.

Variedades de gestores de dependencias.


  • Administradores de dependencias del sistema: instale las utilidades que faltan en el sistema operativo. Por ejemplo, Homebrew .
  • Administradores de dependencia del lenguaje: recopile fuentes escritas en uno de los lenguajes de programación en programas ejecutables finales. Por ejemplo, ve a construir .
  • Gerentes de dependencias del proyecto: administre las dependencias en el contexto de un proyecto específico. Es decir, sus tareas incluyen describir dependencias, descargar y actualizar su código fuente. Estos son, por ejemplo, Cocoapods .

La principal diferencia entre ellos es a quién "sirven". Sistema MH para usuarios, MH del proyecto para desarrolladores y MH del lenguaje para ambos.

A continuación, consideraré los gerentes de dependencia de proyectos: los usamos con mayor frecuencia y son más fáciles de entender.

El esquema del proyecto cuando se usa el administrador de dependencias


Considere Cocoapods, un popular administrador de paquetes.
Por lo general, ejecutamos el comando de instalación de pod condicional, y luego el administrador de dependencias hace todo por nosotros. Considere en qué debe consistir el proyecto para que este equipo se complete con éxito.



  1. Existe nuestro código en el que usamos esta o aquella dependencia, por ejemplo, la biblioteca Alamofire .
  2. Desde el archivo de manifiesto, el administrador de dependencias sabe qué dependencias usamos en el código fuente. Si olvidamos indicar alguna biblioteca allí, la dependencia no se establecerá y el proyecto eventualmente no se ensamblará.
  3. Archivo de bloqueo: un archivo de cierto formato generado por el administrador de dependencias, que enumera todas las dependencias instaladas con éxito en el proyecto.
  4. El código de dependencia es el código fuente externo que el administrador de dependencias "extrae" y que se llamará desde nuestro código.

Esto no hubiera sido posible sin un algoritmo específico que se ejecuta cada vez después del comando de instalación de dependencia.

Los 4 componentes se enumeran uno tras otro, como El siguiente componente se forma en base al anterior.



No todos los administradores de dependencias tienen los 4 componentes, pero teniendo en cuenta las funciones del administrador de dependencias, la presencia de todos es la mejor opción.

Después de instalar las dependencias, los 4 componentes van a la entrada del compilador o intérprete, según el idioma.



También llamo la atención sobre el hecho de que los desarrolladores son responsables de los dos primeros componentes (escribimos este código y el administrador de dependencias para los dos componentes restantes) genera los archivos y descarga el código fuente de las dependencias.



Flujo de trabajo del administrador de dependencias


Con los componentes más o menos ordenados, pasemos ahora a la parte algorítmica del MOH.

Un algoritmo de trabajo típico se ve así:

  1. Validación del proyecto y el medio ambiente. El objeto llamado Analizador es responsable de esto.
  2. Construyendo un gráfico. De las dependencias, el Ministerio de Salud debe construir un gráfico. El objeto Resolver hace esto.
  3. Descargando dependencias. Obviamente, el código fuente de las dependencias debe descargarse para que podamos usarlo en nuestras fuentes.
  4. Integración de dependencias. El hecho de que el código fuente de las dependencias se encuentre en un directorio vecino del disco puede no ser suficiente, por lo que aún deben adjuntarse a nuestro proyecto.
  5. Actualización de dependencia. Este paso no se realiza inmediatamente después del paso 4, pero si es necesario, actualice a la nueva versión de las bibliotecas. Aquí hay algunas peculiaridades, así que las destaqué en un paso separado, más sobre ellas más adelante.

Validación del proyecto y del entorno.


La validación incluye la verificación de las versiones del sistema operativo, las utilidades auxiliares que requiere el administrador de dependencias, así como la vinculación de la configuración del proyecto y los archivos de manifiesto: desde la verificación de sintaxis hasta la configuración incompatible.

Muestra de podfile

source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/RedMadRobot/cocoapods-specs' platform :ios, '10.0' use_frameworks! project 'Project.xcodeproj' workspace 'Project.xcworkspace' target 'Project' do project 'Project.xcodeproj' pod 'Alamofire' pod 'Fabric' pod 'GoogleMaps' end 

Posibles advertencias y errores al verificar el podfile:

  • No se encontró dependencia en ninguno de los repositorios de especificaciones ;
  • El sistema operativo y la versión no se especifican explícitamente;
  • Espacio de trabajo o nombre de proyecto no válido.

Construyendo un gráfico de dependencia


Dado que las dependencias requeridas por nuestro proyecto pueden tener sus propias dependencias, y esas a su vez pueden tener sus propias dependencias o subdependencias anidadas, el administrador utilizó las versiones correctas. Esquemáticamente, todas las dependencias como resultado deberían alinearse en un gráfico acíclico dirigido .



La construcción de un gráfico acíclico dirigido reduce el problema de la clasificación topológica. Ella tiene varios algoritmos de decisión.

  1. Algoritmo de Kahn : enumeración de vértices, complejidad O (n).
  2. Algoritmo de Tarjan : basado en una búsqueda profunda, complejidad O (n).
  3. El algoritmo de Demucron es una partición gráfica en capas.
  4. Algoritmos paralelos que utilizan un número polinómico de procesadores. En este caso, la complejidad "caerá" a O (log (n) ^ 2)

La tarea en sí es NP-complete; el mismo algoritmo se utiliza en compiladores y aprendizaje automático.

El resultado de la solución es el archivo de bloqueo creado, que describe completamente la relación entre las dependencias.



¿Qué problemas pueden surgir cuando funciona este algoritmo? Considere un ejemplo: hay un proyecto con dependencias A, B, E con dependencias anidadas C, F, D.



Las dependencias A y B tienen una dependencia común C. Y aquí C debe satisfacer los requisitos de las dependencias A y B. Algunos administradores de dependencias permiten la instalación de versiones separadas si es necesario, pero los cocoapods, por ejemplo, no. Por lo tanto, en caso de incompatibilidad de requisitos: A requiere una versión igual a 2.0 de la dependencia de C, y B requiere la versión 1.0, la instalación fallará. Y si las dependencias A necesitan la versión 1.0 y superior a la versión 2.0, y las dependencias B versión 1.2 o menos a 1.0, se instalará la versión más compatible para A y B versión 1.2. No olvide que puede ocurrir una situación de dependencia cíclica, incluso si no es directamente, en este caso, la instalación también fallará.



Veamos cómo se ve en el código de los administradores de dependencias más populares para iOS.

Cartago


 typealias DependencyGraph = [Dependency: Set<Dependency>] public enum Dependency { /// A repository hosted on GitHub.com or GitHub Enterprise. case gitHub(Server, Repository) /// An arbitrary Git repository. case git(GitURL) /// A binary-only framework case binary(URL) } /// Protocol for resolving acyclic dependency graphs. public protocol ResolverProtocol { init( versionsForDependency: @escaping (Dependency) -> SignalProducer<PinnedVersion, CarthageError>, dependenciesForDependency: @escaping (Dependency, PinnedVersion) -> SignalProducer<(Dependency, VersionSpecifier), CarthageError>, resolvedGitReference: @escaping (Dependency, String) -> SignalProducer<PinnedVersion, CarthageError> ) func resolve( dependencies: [Dependency: VersionSpecifier], lastResolved: [Dependency: PinnedVersion]?, dependenciesToUpdate: [String]? ) -> SignalProducer<[Dependency: PinnedVersion], CarthageError> } 

La implementación de Resolver está aquí , y NewResolver está aquí , Analyzer como tal no.

Cacao


La implementación del algoritmo de construcción de gráficos se asigna a un repositorio separado. Aquí está la implementación del gráfico y el Resolver . En Analyzer, puede encontrar que verifica que las versiones de cocoapods del sistema coincidan con el archivo de bloqueo.

 def validate_lockfile_version! if lockfile && lockfile.cocoapods_version > Version.new(VERSION) STDERR.puts '[!] The version of CocoaPods used to generate ' \ "the lockfile (#{lockfile.cocoapods_version}) is "\ "higher than the version of the current executable (#{VERSION}). " \ 'Incompatibility issues may arise.'.yellow end end 

Desde la fuente también puede ver que Analyzer genera objetivos para dependencias.

Un archivo de bloqueo típico de cocoapods se parece a esto:

 PODS: - Alamofire (4.7.0) - Fabric (1.7.5) - GoogleMaps (2.6.0): - GoogleMaps/Maps (= 2.6.0) - GoogleMaps/Base (2.6.0) - GoogleMaps/Maps (2.6.0): - GoogleMaps/Base SPEC CHECKSUMS: Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25 Fabric: ae7146a5f505ea370a1e44820b4b1dc8890e2890 GoogleMaps: 42f91c68b7fa2f84d5c86597b18ceb99f5414c7f PODFILE CHECKSUM: 5294972c5dd60a892bfcc35329cae74e46aac47b COCOAPODS: 1.4.0 

La sección PODS enumera las dependencias directas y anidadas que indican las versiones, luego sus sumas de verificación se calculan por separado y juntas, y se indica la versión de cocoapods que se utilizó para la instalación.

Descargar dependencias


Después de construir con éxito el gráfico y crear el archivo de bloqueo, el administrador de dependencias procede a descargarlos. No necesariamente será el código fuente, también puede ser archivos ejecutables o marcos compilados. Además, todos los administradores de dependencias generalmente admiten la capacidad de instalar en una ruta local.



No hay nada complicado para descargarlos desde el enlace (que, por supuesto, debe obtener desde algún lugar), por lo que no diré cómo se realiza la descarga en sí, sino que me centraré en los problemas de centralización y seguridad.

Centralizacion


En términos simples, el administrador de dependencias tiene dos formas de descargar dependencias:

  1. Vaya a alguna lista de dependencias disponibles y obtenga un enlace de descarga por nombre.
  2. Debemos especificar explícitamente el origen de cada dependencia en el archivo de manifiesto.

Los gerentes de dependencia centralizados van por el primer camino, descentralizados por el segundo.



Seguridad


Si descarga dependencias a través de https o ssh, puede dormir tranquilo. Sin embargo, los desarrolladores a menudo proporcionan enlaces http a sus bibliotecas oficiales. Y aquí podemos encontrar un ataque man-in-the-middle cuando un atacante falsifica el código fuente, el archivo ejecutable o el marco. Algunos administradores de dependencias no están protegidos de esto, y algunos lo hacen de la siguiente manera.

Cerveza casera

Comprobación de curl en versiones anteriores de OS X.

 def check_for_bad_curl return unless MacOS.version <= "10.8" return if Formula["curl"].installed? <<~EOS The system curl on 10.8 and below is often incapable of supporting modern secure connections & will fail on fetching formulae. We recommend you: brew install curl EOS end 

También hay una comprobación de hash SHA256 al descargar a través de http.

 def curl_http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default) max_time = hash_needed ? "600" : "25" output, = curl_output( "--connect-timeout", "15", "--include", "--max-time", max_time, "--location", url, user_agent: user_agent ) status_code = :unknown while status_code == :unknown || status_code.to_s.start_with?("3") headers, _, output = output.partition("\r\n\r\n") status_code = headers[%r{HTTP\/.* (\d+)}, 1] end output_hash = Digest::SHA256.digest(output) if hash_needed { status: status_code, etag: headers[%r{ETag: ([wW]\/)?"(([^"]|\\")*)"}, 2], content_length: headers[/Content-Length: (\d+)/, 1], file_hash: output_hash, file: output, } end 

Y también puede deshabilitar redireccionamientos inseguros a http (variable HOMEBREW_NO_INSECURE_REDIRECT ).

Cartago y cacaopods

Aquí todo es más simple: no puede usar http en archivos ejecutables.

 guard binaryURL.scheme == "file" || binaryURL.scheme == "https" else { return .failure(BinaryJSONError.nonHTTPSURL(binaryURL)) } 


 def validate_source_url(spec) return if spec.source.nil? || spec.source[:http].nil? url = URI(spec.source[:http]) return if url.scheme == 'https' || url.scheme == 'file' warning('http', "The URL (`#{url}`) doesn't use the encrypted HTTPs protocol. " \ 'It is crucial for Pods to be transferred over a secure protocol to protect your users from man-in-the-middle attacks. '\ 'This will be an error in future releases. Please update the URL to use https.') end 

Código completo aquí .

Administrador de paquetes Swift

Por el momento, no se pudo encontrar nada relacionado con la seguridad, pero en las propuestas de desarrollo se menciona brevemente un mecanismo para firmar paquetes mediante certificados.

Integración de dependencia


Por integración, me refiero a conectar las dependencias al proyecto de tal manera que podamos usarlas libremente, y se compilan con el código de la aplicación principal.
La integración puede ser manual (Cartago) o automática (Cocoapods). Las ventajas de una automática son un mínimo de gestos por parte del desarrollador, pero se puede agregar mucha magia al proyecto.

Diferencia después de instalar dependencias en un proyecto usando Cocoapods
 --- a/PODInspect/PODInspect.xcodeproj/project.pbxproj +++ b/PODInspect/PODInspect.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 5132347E1FE94F0900031F77 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5132347C1FE94F0900031F77 /* Main.storyboard */; }; 513234801FE94F0900031F77 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5132347F1FE94F0900031F77 /* Assets.xcassets */; }; 513234831FE94F0900031F77 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 513234811FE94F0900031F77 /* LaunchScreen.storyboard */; }; + 80BFE252F8CC89026D002347 /* Pods_PODInspect.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C797D84680452FD95785F /* Pods_PODInspect.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -22,6 +23,9 @@ 5132347F1FE94F0900031F77 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 513234821FE94F0900031F77 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 513234841FE94F0900031F77 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 700D806167013759DC590135 /* Pods-PODInspect.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PODInspect.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PODInspect/Pods-PODInspect.debug.xcconfig"; sourceTree = "<group>"; }; + E03230E2AEDEF09BD80A4BCB /* Pods-PODInspect.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PODInspect.release.xcconfig"; path = "Pods/Target Support Files/Pods-PODInspect/Pods-PODInspect.release.xcconfig"; sourceTree = "<group>"; }; + F92C797D84680452FD95785F /* Pods_PODInspect.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PODInspect.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -29,6 +33,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 80BFE252F8CC89026D002347 /* Pods_PODInspect.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -40,6 +45,8 @@ children = ( 513234771FE94F0900031F77 /* PODInspect */, 513234761FE94F0900031F77 /* Products */, + 78E8125D6DC3597E7EBE4521 /* Pods */, + 7DB1871A5E08D43F92A5D931 /* Frameworks */, ); sourceTree = "<group>"; }; @@ -64,6 +71,23 @@ path = PODInspect; sourceTree = "<group>"; }; + 78E8125D6DC3597E7EBE4521 /* Pods */ = { + isa = PBXGroup; + children = ( + 700D806167013759DC590135 /* Pods-PODInspect.debug.xcconfig */, + E03230E2AEDEF09BD80A4BCB /* Pods-PODInspect.release.xcconfig */, + ); + name = Pods; + sourceTree = "<group>"; + }; + 7DB1871A5E08D43F92A5D931 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F92C797D84680452FD95785F /* Pods_PODInspect.framework */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -71,9 +95,12 @@ isa = PBXNativeTarget; buildConfigurationList = 513234871FE94F0900031F77 /* Build configuration list for PBXNativeTarget "PODInspect" */; buildPhases = ( + 5A5E7D86F964C22F5DF60143 /* [CP] Check Pods Manifest.lock */, 513234711FE94F0900031F77 /* Sources */, 513234721FE94F0900031F77 /* Frameworks */, 513234731FE94F0900031F77 /* Resources */, + 5FD616368597C8B1F8138B2B /* [CP] Embed Pods Frameworks */, + F5ECBE5F431B568B7F8C9B0B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -131,6 +158,62 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 5A5E7D86F964C22F5DF60143 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PODInspect-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 5FD616368597C8B1F8138B2B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-PODInspect/Pods-PODInspect-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", + "${BUILT_PRODUCTS_DIR}/HTTPTransport/HTTPTransport.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HTTPTransport.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PODInspect/Pods-PODInspect-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F5ECBE5F431B568B7F8C9B0B /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PODInspect/Pods-PODInspect-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 513234711FE94F0900031F77 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -272,6 +355,7 @@ }; 513234881FE94F0900031F77 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 700D806167013759DC590135 /* Pods-PODInspect.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; @@ -287,6 +371,7 @@ }; 513234891FE94F0900031F77 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E03230E2AEDEF09BD80A4BCB /* Pods-PODInspect.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; 


En el caso del manual, usted, por ejemplo, siguiendo esta instrucción de Cartago, controla completamente el proceso de agregar dependencias al proyecto. Fiable, pero más largo.

Actualización de dependencia


Puede controlar el código fuente de las dependencias en el proyecto utilizando sus versiones.
Hay 3 métodos utilizados en los administradores de dependencias:
  1. Versiones de la biblioteca. La forma más conveniente y común. Puede especificar una versión específica y un intervalo. Es una forma completamente predecible de soportar compatibilidad de dependencia, siempre que los autores versionen correctamente las bibliotecas.
  2. Rama Al actualizar una rama y actualizar aún más una dependencia, no podemos predecir qué cambios ocurrirán.
  3. Comprometerse o etiquetar. Cuando se ejecuta el comando de actualización, las dependencias con enlaces a una confirmación o etiqueta específica (si no se cambia) nunca se actualizarán.

Conclusión


En el artículo, di una comprensión superficial de la estructura interna de los administradores de dependencias. Si desea saber más, debe profundizar en el código fuente del administrador de paquetes. La forma más fácil de encontrar uno que esté escrito en un idioma familiar. El esquema descrito es típico, pero en un administrador de dependencias particular puede faltar algo o, por el contrario, puede aparecer uno nuevo.
Comentarios y discusión en los comentarios son bienvenidos.

Source: https://habr.com/ru/post/es412945/


All Articles