cat swift/dipendenze-versioni-manifest.md
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:
- leggi changelog o release notes;
- aggiorna il vincolo di versione;
- esegui
swift package resolve; - esegui
swift test; - controlla diff di
Package.resolved; - 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:
CouponCorenon importaArgumentParser;CouponCLIimportaArgumentParser;swift buildfunziona;Package.swiftresta leggibile.