cat swift/dipendenze-versioni-manifest.md

Lezione 4145 min

Dipendenze, versioni e manifest

Gestire dipendenze in SwiftPM significa scegliere versioni, target e prodotti con cura, evitando accoppiamenti inutili.

Le dipendenze sono potenti, ma ogni dipendenza aggiunge un costo:

  • download;
  • build;
  • sicurezza;
  • compatibilità;
  • API esterne da imparare;
  • rischio di breaking change.

SwiftPM rende semplice aggiungere dipendenze, ma la scelta deve restare progettuale.

Dichiarare una dipendenza

Nel Package.swift:

dependencies: [
  .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0")
]

Poi in un target:

.executableTarget(
  name: "coupon-cli",
  dependencies: [
    .product(name: "ArgumentParser", package: "swift-argument-parser")
  ]
)

Nota: dichiarare una dipendenza nel package non significa che ogni target la usa. Devi collegarla al target specifico.

Dipendenza al target giusto

Se CouponCLI usa ArgumentParser, non metterlo in CouponCore.

Buono:

CouponCLI -> ArgumentParser
CouponCLI -> CouponCore
CouponCore -> nessuna dipendenza esterna

Questo mantiene il dominio leggero.

Strategie di versione

SwiftPM supporta vari modi per indicare una versione.

Comune:

.package(url: "...", from: "1.5.0")

Significa: usa una versione compatibile a partire da 1.5.0, secondo semantic versioning.

Puoi anche vincolare:

.package(url: "...", exact: "1.5.0")

Usa exact con cautela: può rendere più difficile risolvere dipendenze.

Branch e revision

Durante sviluppo puoi usare branch:

.package(url: "...", branch: "main")

O revision:

.package(url: "...", revision: "abc123")

Sono utili in casi temporanei, ma per librerie pubbliche preferisci versioni taggate.

Package.resolved

SwiftPM genera Package.resolved, che registra le versioni risolte.

Per applicazioni ed eseguibili è spesso utile committarlo, così CI è team usano le stesse versioni.

Per librerie, la scelta può dipendere dal flusso del progetto. L’importante è essere coerenti.

Products e target non sono la stessa cosa

Nel manifest è facile confondere products e targets.

Un target è un modulo compilabile:

.target(name: "CouponCore")

Un product è ciò che rendi disponibile fuori dal package:

.library(
  name: "CouponKit",
  targets: ["CouponCore"]
)

Puoi avere target interni che non diventano product pubblici. Questo è utile per tenere separati dettagli di implementazione, test helper o adattatori non destinati agli utenti della libreria.

Dipendenze solo dove servono

Se una dipendenza serve solo ai test, mettila nel test target.

.testTarget(
  name: "CouponCoreTests",
  dependencies: [
    "CouponCore"
  ]
)

Se una dipendenza serve solo alla CLI, non deve entrare nella libreria:

.executableTarget(
  name: "CouponCLI",
  dependencies: [
    "CouponCore",
    .product(name: "ArgumentParser", package: "swift-argument-parser")
  ]
)

Questa scelta riduce tempi di build, superficie pubblica e accoppiamento.

Evitare dipendenze transitive nel dominio

Se il tuo dominio importa una libreria HTTP, tutto ciò che dipende dal dominio si porta dietro HTTP.

Meglio creare un protocollo nel core:

public protocol CouponRepository {
  func couponDisponibili() async throws -> [Coupon]
}

E implementarlo nel target HTTP.

Manifest leggibile

Non lasciare che Package.swift diventi un groviglio.

Ordine consigliato:

let package = Package(
  name: "...",
  platforms: [...],
  products: [...],
  dependencies: [...],
  targets: [...]
)

Se cresce molto, usa formattazione coerente e nomi chiari.

Plugin e strumenti

Alcuni strumenti SwiftPM possono essere usati come plugin o command plugin, ad esempio strumenti di benchmark o formattazione. Non introdurre plugin prima di aver chiarito il flusso base:

swift build
swift test
swift run

Prima stabilità, poi automazione.

Aggiornare una dipendenza

Aggiornare non significa solo cambiare numero nel manifest.

Procedura consigliata:

  1. leggi changelog o release notes;
  2. aggiorna il vincolo di versione;
  3. esegui swift package resolve;
  4. esegui swift test;
  5. controlla diff di Package.resolved;
  6. aggiorna eventuale documentazione.

Se una dipendenza è critica, crea un test che protegga l’integrazione minima. Non affidarti solo alla compilazione.

Dipendenze e API pubblica

Attenzione a non esporre tipi di una dipendenza nella tua API pubblica senza volerlo.

Fragile:

public func parse(_ command: ArgumentParser.Command) -> Coupon

Ora chi usa la tua libreria deve conoscere ArgumentParser.

Meglio:

public func coupon(from input: CouponInput) throws -> Coupon

Tieni le librerie esterne ai bordi, soprattutto se il tuo package vuole essere riusabile.

Checklist

Prima di aggiungere una dipendenza:

  • serve davvero?
  • in quale target deve vivere?
  • posso isolarla dietro un protocollo?
  • quale range di versione uso?
  • la licenza è compatibile?
  • la dipendenza è mantenuta?
  • il package resta buildabile in CI?

Esercizio

Aggiungi swift-argument-parser solo al target CLI.

Poi verifica:

  • CouponCore non importa ArgumentParser;
  • CouponCLI importa ArgumentParser;
  • swift build funziona;
  • Package.swift resta leggibile.