
In diesem Artikel werde ich Ihnen sagen, was die Paketmanager in ihrer internen Struktur, ihrem Betriebsalgorithmus und ihren grundlegenden Unterschieden ähnlich sind. Ich habe mir Paketmanager angesehen, die für die Entwicklung unter iOS / OS X entwickelt wurden, aber der Inhalt des Artikels mit einigen Annahmen gilt für andere.
Verschiedene Abhängigkeitsmanager
- Systemabhängigkeitsmanager - Installieren Sie die fehlenden Dienstprogramme im Betriebssystem. Zum Beispiel Homebrew .
- Sprachabhängigkeitsmanager - Sammeln Sie in einer der Programmiersprachen geschriebene Quellen in endgültigen ausführbaren Programmen. Gehen Sie zum Beispiel bauen .
- Projektabhängigkeitsmanager - Verwalten von Abhängigkeiten im Kontext eines bestimmten Projekts. Das heißt, ihre Aufgaben umfassen das Beschreiben von Abhängigkeiten, das Herunterladen und das Aktualisieren ihres Quellcodes. Dies sind zum Beispiel Cocoapods .
Der Hauptunterschied zwischen ihnen besteht darin, wem sie „dienen“. System MH für Benutzer, MH des Projekts für Entwickler und MH der Sprache für beide.
Als nächstes werde ich Projektabhängigkeitsmanager betrachten - wir verwenden sie am häufigsten und sie sind leichter zu verstehen.
Das Schema des Projekts bei Verwendung des Abhängigkeitsmanagers
Betrachten Sie Cocoapods, einen beliebten Paketmanager.
Normalerweise führen wir den bedingten
Pod-Installationsbefehl aus , und dann erledigt der Abhängigkeitsmanager alles für uns. Überlegen Sie, woraus das Projekt bestehen sollte, damit dieses Team erfolgreich abgeschlossen werden kann.

- Es gibt unseren Code, in dem wir diese oder jene Abhängigkeit verwenden, beispielsweise die Alamofire- Bibliothek.
- Aus der Manifestdatei weiß der Abhängigkeitsmanager, welche Abhängigkeiten wir im Quellcode verwenden. Wenn wir vergessen, dort eine Bibliothek anzugeben, wird die Abhängigkeit nicht hergestellt und das Projekt wird schließlich nicht zusammengestellt.
- Sperrdatei - Eine Datei eines bestimmten Formats, die vom Abhängigkeitsmanager generiert wurde und alle im Projekt erfolgreich installierten Abhängigkeiten auflistet.
- Abhängigkeitscode ist der externe Quellcode, den der Abhängigkeitsmanager "aufruft" und der von unserem Code aufgerufen wird.
Dies wäre ohne einen bestimmten Algorithmus nicht möglich gewesen, der jedes Mal nach dem Befehl dependency install ausgeführt wird.
Alle 4 Komponenten werden nacheinander als aufgelistet Die nächste Komponente wird basierend auf der vorherigen gebildet.

Nicht alle Abhängigkeitsmanager haben alle 4 Komponenten, aber unter Berücksichtigung der Funktionen des Abhängigkeitsmanagers ist die Anwesenheit aller die beste Option.
Nach der Installation der Abhängigkeiten werden alle 4 Komponenten je nach Sprache an die Eingabe des Compilers oder Interpreters gesendet.

Ich mache auch darauf aufmerksam, dass die Entwickler für die ersten beiden Komponenten verantwortlich sind - wir schreiben diesen Code und den Abhängigkeitsmanager für die verbleibenden zwei Komponenten - er generiert die Datei (en) und lädt den Quellcode der Abhängigkeiten herunter.

Abhängigkeitsmanager-Workflow
Nachdem die Komponenten mehr oder weniger aussortiert sind, gehen wir nun zum algorithmischen Teil des MOH über.
Ein typischer Arbeitsalgorithmus sieht folgendermaßen aus:
- Validierung des Projekts und der Umgebung. Dafür ist das Objekt Analyzer verantwortlich.
- Erstellen eines Diagramms. Von den Abhängigkeiten sollte das Gesundheitsministerium ein Diagramm erstellen. Das Resolver- Objekt führt dies aus.
- Abhängigkeiten herunterladen. Natürlich muss der Quellcode der Abhängigkeiten heruntergeladen werden, damit wir ihn in unseren Quellen verwenden können.
- Abhängigkeitsintegration. Die Tatsache, dass der Quellcode der Abhängigkeiten in einem benachbarten Verzeichnis auf der Festplatte liegt, reicht möglicherweise nicht aus, sodass sie noch an unser Projekt angehängt werden müssen.
- Abhängigkeitsaktualisierung. Dieser Schritt wird nicht unmittelbar nach Schritt 4 ausgeführt, sondern bei Bedarf auf die neue Version der Bibliotheken aktualisiert. Da es hier einige Besonderheiten gibt, habe ich sie in einem separaten Schritt herausgegriffen - dazu später mehr.
Validierung des Projekts und der Umgebung
Die Validierung umfasst die Überprüfung von Betriebssystemversionen, Hilfsprogrammen, die vom Abhängigkeitsmanager benötigt werden, sowie die Verknüpfung von Projekteinstellungen und Manifestdateien: von der Syntaxprüfung bis zu inkompatiblen Einstellungen.
Beispiel-
Podfilesource '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
Mögliche Warnungen und Fehler beim Überprüfen der Poddatei:
- In keinem der Spezifikationsrepositorys wurde eine Abhängigkeit gefunden.
- Das Betriebssystem und die Version sind nicht explizit angegeben.
- Ungültiger Arbeitsbereich oder Projektname.
Erstellen eines Abhängigkeitsdiagramms
Da die für unser Projekt erforderlichen Abhängigkeiten möglicherweise eigene Abhängigkeiten haben und diese wiederum ihre eigenen verschachtelten Abhängigkeiten oder Unterabhängigkeiten haben können, hat der Manager die richtigen Versionen verwendet. Schematisch sollten alle Abhängigkeiten als Ergebnis in einem
gerichteten azyklischen Graphen ausgerichtet sein .

Die Konstruktion eines gerichteten azyklischen Graphen reduziert sich auf das Problem der topologischen Sortierung. Sie hat mehrere Entscheidungsalgorithmen.
- Kahns Algorithmus - Aufzählung von Eckpunkten, Komplexität O (n).
- Tarjans Algorithmus - basierend auf einer tiefen Suche, Komplexität O (n).
- Demucrons Algorithmus ist eine geschichtete Graphpartition.
- Parallele Algorithmen unter Verwendung einer Polynomzahl von Prozessoren. In diesem Fall "fällt" die Komplexität auf O (log (n) ^ 2)
Die Aufgabe selbst ist NP-vollständig, der gleiche Algorithmus wird in Compilern und beim maschinellen Lernen verwendet.
Das Ergebnis der Lösung ist die erstellte Sperrdatei, die die Beziehung zwischen Abhängigkeiten vollständig beschreibt.

Welche Probleme können auftreten, wenn dieser Algorithmus funktioniert? Betrachten Sie ein Beispiel: Es gibt ein Projekt mit den Abhängigkeiten A, B, E mit den verschachtelten Abhängigkeiten C, F, D.

Die Abhängigkeiten A und B haben eine gemeinsame Abhängigkeit C. Und hier sollte C die Anforderungen der Abhängigkeiten A und B erfüllen. Einige Abhängigkeitsmanager ermöglichen die Installation separater Versionen, falls erforderlich, Cocoapods beispielsweise nicht. Im Falle einer Inkompatibilität der Anforderungen: A erfordert eine Version, die 2.0 der C-Abhängigkeit entspricht, und B erfordert Version 1.0. Die Installation schlägt fehl. Und wenn Abhängigkeiten A Version 1.0 und höher bis Version 2.0 und Abhängigkeiten B Version 1.2 oder weniger bis 1.0 benötigen, wird die am besten kompatible Version für A und B Version 1.2 installiert. Vergessen Sie nicht, dass eine zyklische Abhängigkeit auftreten kann, auch wenn dies nicht direkt geschieht. In diesem Fall schlägt auch die Installation fehl.

Mal sehen, wie es im Code der beliebtesten Abhängigkeitsmanager für iOS aussieht.
Karthago
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> }
Die Implementierung von Resolver ist
hier und NewResolver ist
hier , Analyzer als solches nicht.
Cocoapods
Die Implementierung des Graphkonstruktionsalgorithmus wird einem separaten
Repository zugeordnet . Hier ist die Implementierung des
Graphen und des
Resolvers . In
Analyzer können Sie feststellen, dass die Konsistenz der Cocoapods-Versionen des Systems und der Sperrdatei überprüft wird.
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
Aus der Quelle können Sie auch ersehen, dass Analyzer Ziele für Abhängigkeiten generiert.
Eine typische Cocoapods-Sperrdatei sieht ungefähr so aus:
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
Im Abschnitt PODS werden direkte und verschachtelte Abhängigkeiten mit Versionen aufgelistet, ihre Prüfsummen werden separat und zusammen berechnet und die Version der für die Installation verwendeten Cocoapods wird angegeben.
Abhängigkeiten herunterladen
Nachdem das Diagramm erfolgreich erstellt und die Sperrdatei erstellt wurde, lädt der Abhängigkeitsmanager diese herunter. Es muss nicht unbedingt der Quellcode sein, sondern es können auch ausführbare Dateien oder kompilierte Frameworks sein. Außerdem unterstützen alle Abhängigkeitsmanager im Allgemeinen die Möglichkeit, auf einem lokalen Pfad zu installieren.

Es ist nicht kompliziert, sie über den Link herunterzuladen (den Sie natürlich von irgendwoher erhalten müssen). Ich werde also nicht sagen, wie der Download selbst abläuft, sondern mich auf die Themen Zentralisierung und Sicherheit konzentrieren.
Zentralisierung
In einfachen Worten, der Abhängigkeitsmanager hat beim Herunterladen von Abhängigkeiten zwei Möglichkeiten:
- Gehen Sie zu einer Liste der verfügbaren Abhängigkeiten und erhalten Sie einen Download-Link mit Namen.
- Wir müssen die Quelle für jede Abhängigkeit in der Manifestdatei explizit angeben.
Zentralisierte Abhängigkeitsmanager gehen den ersten Weg, dezentral den zweiten.

Sicherheit
Wenn Sie Abhängigkeiten über https oder ssh herunterladen, können Sie ruhig schlafen. Entwickler stellen jedoch häufig http-Links zu ihren offiziellen Bibliotheken bereit. Und hier kann es zu einem
Man-in-the-Middle- Angriff kommen, wenn ein Angreifer den Quellcode, die ausführbare Datei oder das Framework fälscht. Einige Abhängigkeitsmanager sind nicht davor geschützt, andere tun dies wie folgt.
Homebrew
Überprüfen der Locke bei älteren Versionen von 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
Es gibt auch eine
SHA256-Hash-Prüfung beim Herunterladen über 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
Sie können auch unsichere Weiterleitungen zu http (Variable
HOMEBREW_NO_INSECURE_REDIRECT )
deaktivieren .
Karthago und Cocoapods
Hier ist alles einfacher -
Sie können http nicht für ausführbare Dateien verwenden.
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
Vollständiger Code
hier .
Schneller Paketmanager
Im Moment konnte nichts in Bezug auf Sicherheit gefunden werden, aber in den Entwicklungsvorschlägen wird kurz
ein Mechanismus zum Signieren von Paketen unter Verwendung von Zertifikaten erwähnt.
Abhängigkeitsintegration
Mit Integration meine ich, Abhängigkeiten mit dem Projekt so zu verbinden, dass wir sie frei verwenden können und sie mit dem Hauptanwendungscode kompiliert werden.
Die Integration kann entweder manuell (Karthago) oder automatisch (Cocoapods) erfolgen. Die Vorteile einer automatischen Version sind ein Minimum an Gesten seitens des Entwicklers, aber dem Projekt kann viel Magie hinzugefügt werden.
Diff nach der Installation von Abhängigkeiten in einem Projekt mit Cocoapods Im Fall eines Handbuchs steuern Sie beispielsweise gemäß
dieser Anweisung in Karthago den Prozess des Hinzufügens von Abhängigkeiten zum Projekt vollständig. Zuverlässig, aber länger.
Abhängigkeitsaktualisierung
Sie können den Quellcode von Abhängigkeiten im Projekt mithilfe ihrer Versionen steuern.
In Abhängigkeitsmanagern werden drei Methoden verwendet:
- Bibliotheksversionen. Der bequemste und gebräuchlichste Weg. Sie können sowohl eine bestimmte Version als auch ein Intervall angeben. Es ist eine vollständig vorhersehbare Möglichkeit, die Abhängigkeitskompatibilität zu unterstützen, vorausgesetzt, die Autoren haben die Bibliotheken korrekt versioniert.
- Zweig. Wenn Sie einen Zweig aktualisieren und eine Abhängigkeit weiter aktualisieren, können wir nicht vorhersagen, welche Änderungen auftreten werden.
- Commit oder Tag. Wenn der Aktualisierungsbefehl ausgeführt wird, werden Abhängigkeiten mit Links zu einem bestimmten Commit oder Tag (sofern dieser nicht geändert wird) niemals aktualisiert.
Fazit
In dem Artikel gab ich ein oberflächliches Verständnis der internen Struktur von Abhängigkeitsmanagern. Wenn Sie mehr wissen möchten, sollten Sie sich mit dem Quellcode des Paketmanagers befassen. Der einfachste Weg, eine zu finden, die in einer vertrauten Sprache geschrieben ist. Das beschriebene Schema ist typisch, aber in einem bestimmten Abhängigkeitsmanager kann etwas fehlen oder im Gegenteil ein neues erscheinen.
Kommentare und Diskussionen in den Kommentaren sind willkommen.