Swift Package Manager – Creando Librerías y Ejecutables

Hoy mediante Swift Package Manager aprenderemos a crear y gestionar una librería, varias de hecho. Crearemos también un ejecutable en el cual integraremos dichas librerías.

Durante nuestra carrera como desarrolladores nos veremos en ocaciones ante la necesidad de reutilizar código entre distintos proyectos, ya sea por política de cierta empresa o decisión propia. Lo que sí puedes dar por hecho es que te verás en esta situación, y cuando esto suceda tendrás que conocer los pasos que aquí te muestro.

Nota: En este artículo asumimos que el lector ya ha leído nuestro artículo sobre instalación y primeros pasos de Swift Package Manager.

Creando paquetes de tipo librería

Para esta sección voy a hacer uso de un ejemplo que muestran en el proyecto Swift para ejemplificar estos temas y que me parece bien didáctico y por ende ideal para este artículo.

DeckOfPlayingCards

Este ejemplo consta de cuatro paquetes distintos que interactúan entre ellos. Un paquete ejecutable y tres librerías, de estas tres DeckOfPlayingCards utiliza como dependencias a las otras dos restantes.

Debido a que el proceso de creación de una librería es el mismo para todas nos enfocaremos en DeckOfPlayingCards pero desde ya os digo que lo que haremos aquí es válido para las otras dos que obtendremos desde Github.

Dicho esto comencemos con la creación de DeckOfPlayingCards. Como ya aprendimos a Swift Package Manager se accede a través del comando swift así que pasamos directamente a la creación del paquete a través de la terminal.

mkdir DeckOfPlayingCards

Comenzamos por crear una carpeta vacía para nuestro proyecto y nos movemos dentro de esta con el comando:

cd DeckOfPlayingCards

Ahora creamos un proyecto de tipo librería con los siguiente comando:

Puedes copiar el comando anterior aquí:

swift package init --type library

En este punto hagamos una pausa para describir los directorios y archivos que han sido creados dentro de nuestra carpeta. Por lo general tendremos las siguiente estructura:

Directorio / FicheroDescripción
SourcesEn este directorio es donde colocamos nuestro código fuente. En este momento, contiene solo el directorio PlayingCard, que a su vez solo contiene PlayingCard.swift que es el punto de entrada de nuestra aplicación. Evidentemente podemos agregarle más códigos fuentes y estos se compilarán automáticamente como parte de nuestro proyecto.
PackagesEn este directorio es donde Swift Package Manager almacena las dependencias descargadas. Si miramos dentro veremos todas las dependencias de nuestro proyecto más las propias dependencias de estas.
TestsEn este directorio es donde colocamos nuestros tests compatibles con XCTest.
Package.swiftNuestro paquete se describe por completo dentro de este fichero que en realidad es un código fuente en Swift. En él especificamos la versión de Swift con la que trabajaremos así como las dependencias de nuestro paquete y las versiones mínimas o máximas de estas.
playingcard.xcodeprojMediante este fichero es que podemos abrir el proyecto en Xcode, y se crea solo si hemos ejecutado el comando antes comentado.
README.mdEste fichero es bien famoso en la industria y es donde describimos lo que hace nuestro proyecto para que otros lo lean y sepan de que va.
.buildEste directorio (oculto) se crea una vez que compilamos por primera vez. Aquí es donde Swift coloca nuestro ejecutable compilado.
.gitignoreTambién tenemos este otro archivo oculto donde establecemos los ficheros y directorios que git tiene que ignorar, ya que por alguna razón no deseamos que se gestionen mediante este o sencillamente porque se generan dinámicamente.

Es importante recalcar que esta estructura de directorios es similar para los paquetes de tipo ejecutables como veremos más adelante.

Luego de familiarizarnos con la estructura de directorios y ficheros continuemos con nuestro ejemplo. Para esto tendremos que editar el manifiesto Package.swift con nuestro editor de texto favorito.

En mi caso lo hago con Visual Studio Code, desde la propia terminal abro el fichero con el siguiente comando:

code Package.swift

Este archivo luego de editado debe lucir de esta manera:

// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "DeckOfPlayingCards",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(
            name: "DeckOfPlayingCards",
            targets: ["DeckOfPlayingCards"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/josuevhn/playingcard.git", from: "1.0.0"),
        .package(url: "https://github.com/josuevhn/fisheryates.git", from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "DeckOfPlayingCards",
            dependencies: ["FisherYates", "PlayingCard"]),
        .testTarget(
            name: "DeckOfPlayingCardsTests",
            dependencies: ["DeckOfPlayingCards"]),
    ]
)

Lo que hemos hecho es agregar las dos dependencias que les había comentado más arriba y que vienen a brindar funcionalidades a esta librería. Exacto, una librería que depende de dos más, un caso bastante frecuente de hecho.

Salvamos el archivo y volvemos a la terminal a favor del siguiente paso, crear el proyecto de Xcode:

Puedes copiar el comando anterior aquí:

swift package generate-xcodeproj

Compilando

Acto seguido compilamos el proyecto para cerciorarnos de que todo está bien, tal y como podemos ver en la imagen.

Puedes copiar ese comando aquí:

swift build

Nota: Si te encuentras con algún problema mi consejo sería que volvieras sobre tus pasos y volvieras desde el inicio. Si aún así el problema persiste déjame un mensaje en los comentarios de este mismo artículo.

Abriendo el proyecto en Xcode

En este punto ya podemos abrir Xcode para interactuar con nuestra librería de una manera más cómoda.

¿Cómo abrimos Xcode?

Con doble click sobre el fichero DeckOfPlayingCards.xcodeproj sería más que suficiente. En mi caso como ya estoy en la terminal me resulta más conveniente hacerlo con el siguiente comando:

open DeckOfPlayingCards.xcodeproj

Ya en Xcode abrimos el fichero DeckOfPlayingCards.swift que se encuentra dentro de Sources > DeckOfPlayingCards. Sobreescribimos el contenido por defecto con el código a continuación:

import FisherYates
import PlayingCard

public struct Deck {

    fileprivate var cards: [PlayingCard]

    public static func standard52CardDeck() -> Deck {

        let suits: [Suit] = [.spades, .hearts, .diamonds, .clubs]
        let ranks: [Rank] = [.two, .three, .four, .five, .six, .seven, .eight, .nine, .ten, .jack, .queen, .king, .ace]

        var cards: [PlayingCard] = []

        for rank in ranks {

            for suit in suits {

                cards.append(PlayingCard(rank: rank, suit: suit))

            } // for

        } // for

        return Deck(cards)

    } // standard52CardDeck

    public init(_ cards: [PlayingCard]) {

        self.cards = cards

    } // init

    public mutating func shuffle() {

        cards.shuffle()

    } // shuffle

    public mutating func deal() -> PlayingCard? {

        guard !cards.isEmpty else {
            
            return nil
            
        } // guard

        return cards.removeLast()
    
    } // deal

} // Deck

// MARK: - ExpressibleByArrayLiteral
extension Deck : ExpressibleByArrayLiteral {

    public init(arrayLiteral elements: PlayingCard...) {

        self.init(elements)

    } // init

} // extension Deck : ExpressibleByArrayLiteral

// MARK: - Equatable
extension Deck : Equatable {}

public func ==(lhs: Deck, rhs: Deck) -> Bool {

    return lhs.cards == rhs.cards

} // func ==

Como podemos observar en las primeras líneas de este código estamos importante nuestras librerías de Github tal y como hacemos con las librerías propias de Apple o de terceros.

Salvamos el fichero y compilamos. Esto último lo podemos hacer desde el propio Xcode siguiendo la vía de siempre: Product > Build o de manera más rápida presionando ⌘B.

Subiendo el projecto a Github

En este sitio ya hemos hablado sobre Git y también sobre Github así que no alargaré mucho más este artículo hablando sobre temas que puedes consultar en este otro artículo: XXX. Por este motivo los siguientes pasos los iré comentando brevemente.

Comenzamos por asumir que ya tenemos nuestra repositorio creado en Github en espera de que subamos el proyecto. Desde la terminal y dentro de la carpeta DeckOfPlayingCards inicializamos nuestro repositorio Git:

Los comandos anteriores los puedes copiar aquí:

git init
git status
git add .

Lo que hemos hecho es básicamente inicializar el repositorio, verificar el estado del mismo y agregar los archivos de nuestro paquete al staging area de nuestro repositorio Git. Pero esto no es todo, continuemos:

IMAGEN

Los comandos anteriores los puedes copiar aquí:

git commit -m "Initial project version"
git tag 1.0.0
"git remote add origin https://github.com/josuevhn/deckofplayingcards.git"
git push -u origin master
git push origin --tags

En estos últimos pasos hemos creado un commit del estado inicial de nuestro proyecto. Luego viene un paso que es fundamental para nuestro ejemplo y es crear un tag con la versión de nuestro proyecto, tag por el cual se guiará Swift Package Manager para encontrar la versión exacta que hemos declarado en la sección de dependencias.

La siguiente línea la hemos declarado entre comillas porque en tu caso la URL cambiará haciendo referencia a tu usuario de Github. Así que aquí tendrás que cambiar la URL por la tuya propia.

Continuamos haciendo push de nuestro paquete hacia Github y finalizamos haciendo push también de las tags que hayamos definido, en nuestro caso es solo una.

Creando paquetes de tipo ejecutable

Tras abordar lo básico en cuanto a crear una librería, definir sus dependencias y subirla a Github. Ya estamos listos para crear un paquete ejecutable, el nuestro se llamará Dealer y hará uso de la librería que hemos creado.

Los pasos son similares a los anteriores. Pero antes subimos un nivel y salimos de la carpeta DeckOfPlayingCards donde nos habíamos quedado:

cd ..

En este punto ya podemos crear la carpeta de nuestro paquete ejecutable así como el paquete como tal:

Puedes copiar el comando anterior aquí:

swift package init --type executable

Repetimos el proceso que ejecutamos con DeckOfPlayingCards y abrimos el manifiesto Package.swift. Nuevamente modificamos los apartados de dependencias y target, quedando así:

// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Dealer",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/josuevhn/deckofplayingcards.git", from: "1.0.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "Dealer",
            dependencies: ["DeckOfPlayingCards"]),
        .testTarget(
            name: "DealerTests",
            dependencies: ["Dealer"]),
    ]
)

Salvamos el fichero, compilamos y ejecutamos el paquete:

Puedes copiar los comandos anteriores aquí:

swift build
swift run

Viendo que todo está bien y no muestra el mensaje:

Hello, world!

que viene en todos los paquetes por defecto. En la imagen anterior también podemos observar como se descarga la dependencia de nuestro proyecto así como las dependencias de esta.

Proseguimos creando el proyecto de Xcode:

swift package generate-xcodeproj

y lo abrimos. Ya en Xcode nos dirigimos al archivo main.swift ubicado en Sources > Dealer.

Cambiamos el contenido por defecto de este archivo por el siguiente código:

#if os(Linux)

import Glibc

srandom(UInt32(clock()))

#endif

import DeckOfPlayingCards

let numberOfCards = 10

var deck = Deck.standard52CardDeck()

deck.shuffle()

for _ in 0..<numberOfCards {

    guard let card = deck.deal() else {

        print("No quedan más cartas")
        
        break
    
    } // guard

    print(card)

} // for

No te preocupes si al copiar el código Xcode te muestra algún error con respecto a que no encuentra el módulo DeckOfPlayingCards. Una vez que salves los cambios y compiles el proyecto todo irá bien.

Ejecutando el proyecto

Lo único que nos queda es ejecutar el proyecto y hacer uso de todas las librerías que estamos usando, dependencias de nuestro ejecutable que podemos gestionar fácilmente mediante Swift Package Manager.

Podemos ejecutarlo desde el propio Xcode estableciendo el scheme activo a My Mac (o su equivalente en castellano) y por último Run o ⌘R. Desde la terminal con el comando que ya habíamos visto:

Puedes copiar los comandos anteriores aquí:

swift run

Actualizando dependencias

Interactuar con dependencias también incluye la actualización de las mismas, ya sea refiriéndonos a las que ya hemos definido como a nuevas que necesitamos incluir.

Dicho esto, imaginemos que la persistencia de datos de nuestra aplicación de cartas la queremos gestionar con MongoDB. Para esto solo tendríamos que ir al manifiesto Package.swift y agregar los datos respectivos a las secciones dependencies y targets.

La versión final de Package.swift sería la siguiente:

// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Dealer",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/josuevhn/deckofplayingcards.git", from: "1.0.0"),
        .package(url: "https://github.com/mongodb/mongo-swift-driver.git", from: "0.1.3")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "Dealer",
            dependencies: ["DeckOfPlayingCards", "MongoSwift"]),
        .testTarget(
            name: "DealerTests",
            dependencies: ["Dealer"]),
    ]
)

luego desde la terminal:

Puedes copiar el comando anterior aquí:

swift package update

Continuamos importando el driver de MongoDB en la parte superior del fichero fuente main.swift :

import MongoSwift

No es necesario hacer más nada, salvamos el fichero y compilamos. Si todo ha ido bien hemos incluido una dependencia más y está lista para ser usada.

Nota: Lo más probable es que si no tenemos MongoDB instalado en nuestro sistema al compilar veamos varios errores, y esto es de esperar ya que básicamente no encuentra el resto de librerías necesarias que se distribuyen con la instalación de MongoDB.

Conclusión

Como hemos podido constatar Swift Package Manager es muy útil: nos permite crear paquetes de varios tipos y también gestionar las dependencias que estos puedan tener.

Swift Package Manager es el gestor de paquetes oficial del proyecto Swift, siempre lo ha sido. Pero desde su integración con Xcode 11 va tomando el lugar que se merece dentro de la comunidad. El proyecto Vapor utiliza Swift Package Manager y cada vez son más los proyectos que se suman.

Así que toca aprenderlo e ir migrando esos proyectos que aún hoy gestionamos mediante CocoaPods o Carthage.

Falta aún mucho por aprender en nuestro camino a convertirnos en iOS Developer. Suscríbete a nuestra lista de correo y síguenos en nuestras redes sociales. Mantente al tanto de todas nuestras publicaciones.

Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.

Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.

¡Cualquier duda o sugerencia, ya sea errores a corregir o ejemplos a añadir, será más que bienvenida, necesaria!

Deja una respuesta

Su dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para mejorar su experiencia. Asumiremos que está de acuerdo con esto, pero puede optar por no participar si lo desea. Aceptar Leer Más

RECIBE CONTENIDO SIMILAR EN TU CORREO

RECIBE CONTENIDO SIMILAR EN TU CORREO

Suscríbete a nuestra lista de correo y mantente actualizado con las nuevas publicaciones.

Se ha suscrito correctamente!