Gestión de DependenciasTutoriales

Swift Package Manager – Paquetes y Dependencias

En esta ocasión aprenderemos sobre Swift Package Manager, el gestor de paquetes de Swift, una herramienta con la cual podremos crear paquetes / proyectos ya sean librerías o ejecutables, gestionar las dependencias de estos y en general administrar de una manera más fácil y consensuada la distribución de nuestro código Swift.

Swift Package Manager fue introducido en la versión 3.0 de Swift y desde entonces viene mejorando mucho en cada versión, volviéndose mucho más funcional y estable. Actualmente se encuentra muy bien integrado con el sistema de compilación de Swift, de esta manera se automatiza el proceso de descarga, compilación y vinculación de dependencias tal y como se espera de una herramienta de este tipo y en estos tiempos.

Visión conceptual

Antes de entrar en materia repasaremos ciertos conceptos básicos manejados en este artículo relacionados con la funcionalidad del Swift Package Manager.

Módulos

Swift organiza el código en módulos. Cada módulo especifica un espacio de nombres e impone controles de acceso sobre qué partes de ese código se pueden usar fuera del mismo. Un programa puede tener todo su código en un solo módulo, o puede importar otros módulos como dependencias. Además del puñado de módulos proporcionados por el sistema, como Darwin en macOS o Glibc en Linux, la mayoría de las dependencias requieren que el código se descargue y construya para poder ser utilizado.

Cuando hacemos uso del código de un módulo para resolver un problema este no está limitado a un caso en particular, por el contrario este módulo o librería externa se puede reutilizar en otras situaciones. Por ejemplo, un módulo que nos ayuda con las peticiones de red se puede compartir entre una aplicación para compartir fotos y una aplicación de clima. El uso de módulos nos ayuda a agilizar los tiempo de desarrollo, permitiendo construir nuestro proyecto (o ciertas áreas de este) sobre el código bien escrito (en la mayoría de los casos) de otros desarrolladores en lugar de volver a implementar la misma funcionalidad nosotros mismos y perder un tiempo precioso para al final, muchas veces, ni siquiera obtener un mejor desempeño.

Paquetes

Un paquete consta de archivos fuentes y un archivo de manifiesto. El archivo de manifiesto, llamado Package.swift, define el nombre del paquete y su contenido utilizando el módulo PackageDescription. Un paquete cuenta con uno o más targets (objetivos). Cada target especifica un producto y puede declarar una o más dependencias.

Productos

Mediante un target podemos especificar un producto, ya sea una librería o un ejecutable. Una librería contiene un módulo que puede ser importado por otro código Swift y un ejecutable es un programa que puede ejecutar el sistema operativo.

Dependencias

Las dependencias de un target son módulos requeridos por el código del paquete. Una dependencia consiste en una URL relativa o absoluta que apunta al origen del paquete así como un conjunto de requisitos para la versión compatible del paquete que podemos usar en nuestro proyecto. El rol de Package Manager es reducir los costos de coordinación al automatizar el proceso de descarga y creación de todas las dependencias para un proyecto. Esto resulta en la mayoría de las veces en un proceso recursivo: una dependencia puede tener sus propias dependencias, cada una de las cuales a su vez también pueden tener otras más, formando un árbol de dependencias, que gestionar a mano sería extremadamente engorroso. Por esta razón es que Package Manager nos ayuda con la descarga e instalación de todo lo necesario para satisfacer el árbol de dependencias.

¿Qué es el super comando swift?

Mediante el comando swift ejecutamos la gran mayoría de nuestras acciones. Muchas veces se confunde con Swift REPL (Read Eval Print Loop) ya que si lo ejecutas sin parámetros entra por defecto en este modo. Pero hagamos una pausa y responsamos la siguiente pregunta primero:

¿Qué es Swift REPL?

Swift REPL es un modo del comando swift que a su vez nos brinda la funcionalidad de un interprete interactivo de instrucciones Swift, traduciendo las siglas sería algo como: Ciclo de Lectura , Evaluación e Impresión. Pero al mismo tiempo mediante Swift REPL también podemos a su vez acceder al depurador LLDB para tareas más que evidentes. Pasemos a la terminal para ejemplificar lo que acabo de comentar:

Probando Swift REPL

En esta imagen vemos la ejecución del modo Swift REPL del comando swift en Ubuntu (en macOS es similar). Podemos constatar como las instrucciones que pasamos son leídas (Read) e interpretadas (Eval) para al final mostrarnos la respectiva salida en pantallas (Print), tal y como también se observa pudiéramos seguir escribiendo (Loop) hasta que decidamos salir con la opción :q (al estilo vi / vim).

Respondiendo a la primera pregunta: Cuando observamos todo el panorama nos percatamos que con el comando swift podemos hacer muchas cosas, es como un meta-comando lleno de funcionalidades y hasta un modo interactivo. Por esta razón lo he llamado el super comando swift, en la ayuda funge como “Swift Compiler” (Compilador Swift) pero el que realmente compila el código fuente es el comando swiftc. Este comando (swift) viene siendo como una interfaz a nivel de terminal desde la cual (como ya comenté) podemos entrar en el modo interactivo de Swift REPL, podemos compilar o interpretar un archivo, y también podemos acceder a Package Manager.

Si en alguna de estas capturas te preguntas que interprete de comandos estoy usando o como instalar cierta herramienta que uso y que no viene por defecto pues te invito a leer nuestro artículo sobre la configuración de un entorno de desarrollo Swift (iOS, macOS, tvOS y watchOS) en sistemas macOS.

Conociendo la versión instalada

Siempre es útil conocer la versión de Swift que tenemos instalada y esto lo conseguimos ejecutando el siguiente parámetro sobre el super comando swift. En la terminal de macOS ejecutemos lo siguiente:

…obtenemos la siguiente salida en pantalla:

Como deben de imaginar el comando swift se instala en el sistema junto a Xcode…

En el caso de Linux hay que seguir algunos pasos que ya hemos descrito en nuestra guía sobre la instalación del lenguaje Swift en sistema Linux.

Obteniendo ayuda

Un comando con tantas opciones resulta bien confuso, así que junto a opciones como este propio artículo y recursos similares como la documentación oficial de Apple y del proyecto Swift, también contamos con una documentación propia del comando: una lista y descripción de los parámetros que podemos pasar sobre este nuestro super comando swift. Para acceder a este modo ejecutemos en la terminal el parámetro -help:

Showing Swift Help

…en esta imagen vemos claramente que la salida en pantalla no cabe en el marco de la terminal, así que lo ejecutaremos de esta otra manera haciendo un pipeline redirigiendo el flujo de la salida de este comando hacia less:

Puedes copiar el comando anterior aquí:

Showing Swift Help with Less

Puedes copiar el comando anterior aquí:

Ahora nos podemos desplazar por la documentación mediante las flechas de arriba y abajo del teclado y salimos presionando la tecla q;

Scripting

Luego de leer la documentación tendremos una mejor idea de todo cuanto podemos hacer con este comando, entre ellas la lectura de un archivo externo o la creación de un script y esto lo podemos probar de una manera bien sencilla. Creemos un archivo:

…luego lo abrimos y le añadimos la siguinete línea:

…salvamos y pasamos este archivo fuente al comando swift:

Executing Swift Script

El resultado lo podemos observar en la imagen anterior. Visto esto:

¿Acaso podemos crear un script al clásico estilo Bash o Python?

La respuesta es sí, modifiquemos el archivo fuente anterior a la siguiente versión:

Salvamos el archivo y en la terminal le damos permisos de ejecución:

…luego de esto ya lo podemos ejecutar:

Testing Swift Script

Puedes copiar el comando anterior aquí:

En este punto ya tenemos un script programado en Swift que podemos instalar en nuestro ordenador personal o servidor para automatizar ciertas tareas.

¿Cómo usar Swift Package Manager?

En este punto y luego de esta introducción al comando swift y a todo cuanto podemos hacer con él. Hagamos una revisión de los comandos básicos de Package Manager que nos permitirán llevar a cabo esas tareas más frecuentes del día a día.

Creando un paquete 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.

PlayingCard

Este ejemplo consta de cuatro paquetes distintos que interactúan entre ellos. Dicho esto comencemos con la creación del primero, como ya conocemos a Package Manager se accede a través del comando swift así que pasamos directamente a la creación del paquete:

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

Ahora ejecutamos el siguiente comando:

Creating Swift Library Package

Puedes copiar el comando anterior aquí:

Este comando nos crea un paquete de tipo librería con toda la estructura de directorios necesaria:

Estructura de Directorios
Directorio / FicheroDescripción
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.
.buildEn este directorio oculto (comienza por un punto “.”) es donde Swift coloca nuestro ejecutable compilado.
PackagesEs donde Package Manager almacena las dependencias descargadas. Si miramos dentro veremos todas las dependencias de nuestro proyecto más las propias dependencias de estas.
SourcesEs donde debemos colocar nuestro código fuente. En este momento, contiene solo “MyTest“, que a su vez solo contiene main.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.
TestsAquí es donde colocamos nuestros tests compatibles con XCTest.
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.
.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.

Para continuar con nuestros ejemplos se impone añadir algunas dependencias en pos de poder ejemplificar otras opciones de Package Manager. Editemos el fichero Package.swift a la versión siguiente:

Salvamos el archivo y acto seguido compilamos nuestro paquete ejecutando el siguiente comando:

Swift Build

Puedes copiar el comando anterior aquí:

Aquí tenemos una salida en pantalla bien sencilla dado que es una librería que no tiene dependencias y prácticamente tampoco tiene códigos. Digo prácticamente ya que por defecto Package Manager nos crea un fichero fuente del mismo nombre de la librería, en este caso PlayingCard.swift en el cual nos crea una estructura del mismo nombre así como una variable de tipo String y de nombre text con la cadena de texto asociado “Hello, World!”:

Showing Default Content of Library Package

Código que vamos a sobreescribir con el siguiente:

En este ejemplo hay segmentos que no abordaré debido a que no es el objetivo de este artículo y también porque ya han sido explicados. Me refiero a las extensiones, la sobrecarga de operadores y a los protocolos… pueden referirse a estos links para más información. Básicamente en este código creamos una estructura con dos constantes, una de nombre rank donde almacenaremos el rango de una carta (Diamantes, Corazones, Espadas) y en suit el tipo de la misma (As, dos, tres, Jota, Reina, Rey). Luego extendemos nuestra estructura para que adopte los protocolos Equatable, Comparable y CustomStringConvertible junto a sus respectivos requerimientos.

El paso siguiente para terminar con este paquete será la creación de dos archivos fuentes más junto a este que se nos fue creado por defecto. Rank.swift:

…y Suit.swift:

Como ya comenté, los contenidos aplicados en estos ejemplos ya han sido abordados en este sitio, incluida las enumeraciones vistas en estos dos últimos ficheros.

Si en este momento compilamos nuevamente el paquete:

Swift Build with More Source Codes

…comprobamos que se han detectado automáticamente los nuevos archivos de código fuente y se han compilado correctamente. Por último imaginemos que subimos este paquete a github para que cualquier otra persona pueda usar las funcionalidades de  nuestra librería.

FisherYates

Nuestro segundo paquete será FisherYates, el cual se encargará de barajar las cartas. Este módulo también lo crearemos como una librería (ya vimos como hacerlo) ya que es una funcionalidad que podremos utilizar en varios proyectos. El fichero Package.swift luciría así:

Dentro de la carpeta Source tendremos dos archivos, el primero será FisherYates.swift:

…y el segundo random.swift:

Recuerden que el objetivo de estos ejemplos no es lo que hacen como tal, los estamos compartiendo para que quede los más real posible. Este último paquete es también una librería, en él solo se declaran funcionalidades para luego ser usadas desde un proyecto ejecutable o también desde otra librería. En el archivo FisherYates.swift proponemos dos extensiones a los protocolos Collection y MutableCollection, por su parte el archivo random.swift hace una distinción entre macOS y Linux enfocado en la ejecución de la función arc4random_uniform (línea 7) que solo está disponible en macOS y por ende en Linux abogamos por su homólogo random (línea 23).

Esto sería todo sobre este paquete, pero antes de continuar compilamos el módulo y verificamos que todo vaya bien. Al igual que con el paquete anterior imaginemos que lo subimos a github para que cualquier otra persona pueda usar las funcionalidades de esta librería.

DeckOfPlayingCards

El paquete DeckOfPlayingCards combina las dos librerías anteriores: define un tipo Deck que usa el método shuffle de FisherYates en una matriz de valores de PlayingCard. Para usar los módulos FisherYates y PlayingCards, el paquete DeckOfPlayingCards debe declarar estos paquetes como dependencias en el manifiesto Package.swift:

En las líneas 14 y 15 obtenemos los paquetes / módulos anteriores desde github, tal y como les comenté que imaginasen subían. No les digo que realmente lo hagan debido a que es solo un ejemplo y porque como ven estos ya han sido subidos por Apple.

El contenido del fichero DeckOfPlayingCards.swift:

Salvamos y compilamos:

Swift Build Getting Dependencies

…como pueden observar las librerías que antes hemos desarrollado se han descargado desde la fuente establecida (github), se han compilado y enlazado dentro de este nuevo módulo que acabamos de finalizar. Al igual que con las dos librerías anteriores imaginemos que esta también la subimos a github, ya sabemos las razones.

Creando un paquete de tipo Ejecutable

Tras abordar lo básico en cuanto a crear dos librerías y luego otra librería más pero que en este caso hace uso de las dos anteriores, pues ya estamos listos para crear un paquete ejecutable que se llamará Dealer y que hará uso de esta última librería (DeckOfPlayingCards).

Los pasos son similares a los anteriores y la tabla donde explicamos la estructura de directorios es igual de válida. Comenzamos por crear la carpeta del paquete:

…entramos en el directorio:

…y dentro de este ejecutamos:

Swift Package Init Help

Puedes copiar el comando anterior aquí:

Pues sí, no hay que saberlo todo de memoria aunque con el tiempo es inevitable, así que la ayuda es nuestra mejor aliada. Aquí pueden ver las opciones con las que contamos, donde se incluyen library (la que hemos usado hasta el momento) y executable que será nuestra próxima opción. Dicho esto continuamos:

Creating Swift Executable Package

Puedes copiar el comando anterior aquí:

El archivo manifiesto (Package.swift) de nuestro paquete ejecutable luce así:

Como ya se habrán imaginado en la línea 14 definimos como única dependencia a nuestra última librería DeckOfPlayingCards.

Ahora, a diferencia de los paquetes de tipo librería, los ejecutables no vienen por defecto con un fichero de código fuente cuyo nombre es idéntico al del módulo, en este caso el fichero se llama main.swift y constituye el punto de entrada de nuestro ejecutable. Pueden abrirlo y ver su contenido por defecto, el cual ya les adelanto es el clásico “Hello, world!”.

El contenido del fichero main.swift para nuestro paquete luciría así: