**Julia: Paquetes y Entornos** Máster Propio en Data Science y Big Data $\mathbf{24/25}$ Dpto. Ciencias de la Computación e Inteligencia Artificial - Universidad de Sevilla Cuando empieces a usar Julia, entrarás rápidamente en contacto con Pkg.jl, su gestor de paquetes. Es razonablemente fácil instalar unos pocos paquetes y empezar a usar Julia. Pero leyendo preguntas en Slack y Discourse, muchos usuarios sólo empiezan a entender relativamente tarde lo que hacen comandos como `instantiate`. Este post debería enseñarte cómo puedes ir más allá de un entorno global desordenado y hacia versiones locales bien empaquetadas que te permitan colaborar más eficazmente y hacer que tus resultados sean reproducibles. ## Empezando !!!side:1 En Julia, un *entorno*(*environment*) es un conjunto de paquetes y dependencias que se gestionan de manera aislada del resto del sistema o de otros entornos. Este concepto es útil para evitar conflictos entre diferentes versiones de paquetes y para asegurar que un proyecto específico tenga exactamente las versiones de las dependencias que necesita. Cuando instalamos Julia por primera vez, no hay entornos de trabajo $^1$. Cuando inicies la REPL con el comando `julia`, verás el prompt estándar ```terminal julia> ``` Puedes comprobar la carpeta `.julia/environments`, que debería estar vacía (normalmente, dentro de tu carpeta de usuario). Esta carpeta contiene todos tus entornos *compartidos* !!!side:2 A lo largo de lo que sigue supondremos que se ha instalado `Julia 1.10`, pero realmente no es importante la versión disponible. Desde ahí, se accede al modo REPL de Pkg.jl tecleando `]`. El prompt cambiará para indicarlo, mostrando el nombre del entorno activo $^2$: ```terminal (@v1.10) pkg> ``` El nombre del entorno aparece entre paréntesis al principio del prompt, como estamos usando la versión 1.10, el sistema crea un entorno temporal, todavía sin contenido, con el nombre del entorno. Cuando añadamos alguna dependencia a nuestro entorno, aparecerán archivos dentro de `.julia/environments`. Vamos a activar un nuevo entorno escribiendo `(@v1.10) pkg> activate MiEntorno`. No verás que se crea ningún fichero nuevo en el directorio de trabajo, ya que, como hemos dicho, esto sólo ocurrirá solo cuando se manipule de verdad el entorno añadiendo dependencias de librerías, pero el prompt ha cambiado: ```terminal (@v1.10) pkg> activate MiEntorno Activating new project at `~/MiEntorno` (MiEntorno) pkg> ``` !!!warn:Recuerda Este entorno, que no es compartido, se creará en la carpeta de trabajo, no es la de Julia. El propósito de los entornos compartidos es que puedas activarlos fácilmente desde cualquier directorio de trabajo, y por ello empiezan por `@`, así `Pkg` sabe que debe buscarlos en `.julia/environments`. Para todos los demás entornos, puedes activarlos por su nombre si te encuentras en el directorio donde se crearon, o tienes que especificar la ruta completa. Volvamos al entorno *principal* `@v1.10` por ahora. Así que activamos de nuevo `@v1.10` tecleando: ```terminal (MiEntorno) pkg> activate @v1.10 Activating new project at `~/.julia/environments/v1.10` (@v1.10) pkg> ``` Como ves, Pkg nos dijo que estábamos activando un nuevo **proyecto** (otra palabra para **entorno**, porque realmente se crea un entorno nuevo cuando uno tiene que desarrollar un proyecto nuevo), porque como vimos antes, ningún fichero existía realmente, todavía. Hay otro atajo para activar el entorno principal, que es `activate` sin argumento: ```terminal (@v1.10) pkg> activate Activating new project at `~/.julia/environments/v1.10` (@v1.10) pkg> ``` Y, además, tenemos ```terminal (@v1.10) pkg> activate . ``` para activar un entorno existente en la carpeta actual. ## Añadir un paquete Vamos a añadir nuestro primer paquete a nuestro entorno compartido `@v1.10`. Para esto, usamos el comando `add`. Elijo el paquete `MacroTools` porque tiene pocas dependencias. ```terminal (@v1.10) pkg> add MacroTools Updating registry at `~/.julia/registries/General.toml` Resolving package versions... Updating `~/.julia/environments/v1.10/Project.toml` [1914dd2f] + MacroTools v0.5.9 Updating `~/.julia/environments/v1.10/Manifest.toml` [1914dd2f] + MacroTools v0.5.9 [2a0f44e3] + Base64 [d6f4376e] + Markdown [9a3f8284] + Random [ea8e919c] + SHA v0.7.0 [9e88b42a] + Serialization ``` Como probablemente sabrás, después de hacer esto, el código de `MacroTools` se habrá descargado en tu sistema y podrás utilizarlo en tu propio código cargándolo previamente: ```terminal julia> using MacroTools ``` ¿Qué ocurrió realmente cuando ejecutamos el comando `add`? En la primera línea, puedes ver que se ha actualizado el registro general. El registro general ([https://github.com/JuliaRegistries/General](https://github.com/JuliaRegistries/General)) es una lista de paquetes de terceros que están disponibles al público, donde cada paquete lista todas sus dependencias y versiones. Puede haber otros registros, incluso privados, pero el registro general es el principal y el único relevante para la mayoría de los usuarios. `Pkg` ha actualizado por primera vez esta lista en nuestro ordenador cuando ejecutamos `add MacroTools` para que conozca las versiones más recientes de todos los paquetes del ecosistema. Puedes echarle un vistazo en `.julia/registries/` si quieres. Después de actualizar el registro, Pkg está `Resolviendo versiones de paquetes`. Primero, la última versión de `MacroTools` en el momento de escribir esto, `v0.5.9`, fue añadida a `~/.julia/environments/v1.10/Project.toml`. Así que es en este momento cuando se está creando el archivo de entorno del que hablábamos antes. Podemos ver el contenido de `Project.toml`: ```toml [deps] MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" ``` Esta información solo indica que nuestro entorno tiene una dependencia declarada, que es `MacroTools.jl`. La cadena UUID `1914dd2f-81c6...` está ahí porque ese es el identificador único *real* del paquete porque, por ejemplo, si otro hipotético registro usara una versión de `MacroTools` diferente, entonces al menos podrías especificar el que realmente quieres a través del UUID. `Pkg` también actualizó el archivo `~/.julia/environments/v1.10/Manifest.toml`. Echemos un vistazo a este: ```toml # This file is machine-generated - editing it directly is not advised julia_version = "1.10.0-rc3" manifest_format = "2.0" project_hash = "e39ab6d265da4acedccb7411db33219b8d7db4fc" [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" [[deps.MacroTools]] deps = ["Markdown", "Random"] git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf" uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.9" [[deps.Markdown]] deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[deps.Random]] deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" version = "0.7.0" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" ``` El `Manifest.toml` lista todos los paquetes que se instalaron, mientras que el `Project.toml` sólo lista la dependencia `MacroTools`. Puedes pensar en los dos archivos de esta manera: - `Project.toml`: Lo que quieres. - `Manifest.toml`: Lo que obtienes. El fichero `Project.toml` es siempre el primer fichero que se edita cuando se hacen cambios en el entorno, ya sea a través del `Pkg` en el REPL o manualmente (aunque, personalmente, no aconsejo su manipulación sin herramientas de ayuda). El fichero `Manifest.toml` es entonces el resultado de un cálculo que intenta encontrar versiones compatibles de todos los paquetes especificados en el `Project.toml` y sus dependencias. Ten en cuenta que `Project.toml` puede, en principio, especificar demandas imposibles, como dos paquetes que requieren dependencias incompatibles. `Manifest.toml`, sin embargo, debe estar siempre en un estado válido, si no se puede resolver una configuración válida de dependencias de paquetes, simplemente obtendrá un error. Podemos ver el grafo de dependencias que Pkg resolvió en el `Manifest.toml`, si nos fijamos en los campos `deps`: ![](./img/graphviz.png width=40%) !!!side:3 SHA es una librería estándar inusual porque está alojada externamente y tiene una versión. Pero la versión sigue siendo fija para cada versión de Julia a través de un archivo `.version` como [el de SHA](https://github.com/JuliaLang/julia/blob/master/stdlib/SHA.version), por lo que la mayoría de los usuarios pueden tratarla como una biblioteca estándar normal. El nodo azul se refiere al paquete externo `MacroTools`, que tiene una versión. Los nodos rojos son bibliotecas estándar. Las bibliotecas estándar se envían con Julia, por lo que normalmente no tienen su propia versión y sólo cambian con cada versión de Julia. Puedes ver todas las bibliotecas estándar en el [repositorio de Julia en GitHub](https://github.com/JuliaLang/julia/tree/master/stdlib). Las bibliotecas estándar pueden depender de otras bibliotecas estándar (como se observa en el grafo anterior) $^3$. En este ejemplo, MacroTools depende sólo de librerías estándar, pero la mayoría de los otros paquetes dependen también de otros paquetes externos. Puedes comprobar las versiones de las librerías en tu `Project.toml` y `Manifest.toml` con el comando `status` o `st`. Con la opción `-m` puedes ver las entradas de `Manifest.toml`, que pueden ser importantes para comprobar qué dependencias se han resuelto. ```terminal (@v1.10) pkg> st Status `~/.julia/environments/v1.10/Project.toml` [1914dd2f] MacroTools v0.5.9 (@v1.10) pkg> st -m Status `~/.julia/environments/v1.10/Manifest.toml` [1914dd2f] MacroTools v0.5.9 [2a0f44e3] Base64 [d6f4376e] Markdown [9a3f8284] Random [ea8e919c] SHA v0.7.0 [9e88b42a] Serialization (@v1.10) pkg> st -m SHA Status `~/.julia/environments/v1.10/Manifest.toml` [ea8e919c] SHA v0.7.0 ``` ## Números de versión Para entender cómo se resuelven las dependencias, tienes que entender [SemVer versioning](https://semver.org): *versionado semántico*, lo que significa que los números de versión son significativos, no sólo etiquetas aleatorias. Las tres partes del número de versión son `major`.`minor`.`patch`. En Julia, todas las versiones más recientes con la misma versión principal deben tener APIs públicas compatibles, por lo que el código que funciona con `v1.2.3` también debería funcionar con `v1.20.5`. La excepción es la versión mayor `0`, donde cada nueva versión menor puede considerarse potencialmente rompedora. Así, el código que funciona con `v0.2.4` debería seguir funcionando con `v0.2.13` pero no necesariamente con `v0.3.0`. Esto se debe a que los desarrolladores de paquetes quieren ser capaces de hacer cambios de ruptura incluso si no han llevado su paquete a `v1.0` todavía, una versión que normalmente lleva implícita la estabilidad de la API pública y que a menudo sólo se alcanza después de que el paquete haya existido durante un tiempo. ## Añadir variantes específicas de un paquete !!!side:4 No tiene que ser necesariamente una versión específica. También puede ser un commit o una rama de un determinado repositorio. Hasta ahora, sólo hemos utilizado el comando `add MacroTools`, que inhstaló la última versión disponible en nuestro entorno. A veces, sin embargo, podemos querer una variante específica de un paquete $^4$. Vamos a probar esto con `MacroTools`. !!!side:5 Puedes ver que tenemos una multitud de nuevas dependencias en la versión anterior, `MacroTools` consiguió reducir mucho el número de paquetes de los que depende con el tiempo, por lo que la versión anterior tira de muchas más dependencias que las nuevas. Podemos instalar la versión `v0.5.1` utilizando la sintaxis `@` $^5$: ```terminal (@v1.10) pkg> add MacroTools@0.5.1 Resolving package versions... Updating `~/.julia/environments/v1.10/Project.toml` ⌃ [1914dd2f] ↓ MacroTools v0.5.9 ⇒ v0.5.1 Updating `~/.julia/environments/v1.10/Manifest.toml` ⌅ [00ebfdb7] + CSTParser v2.5.0 ⌅ [34da2185] + Compat v2.2.1 ⌅ [864edb3b] + DataStructures v0.17.20 ⌃ [1914dd2f] ↓ MacroTools v0.5.9 ⇒ v0.5.1 [bac558e1] + OrderedCollections v1.4.1 [0796e94c] + Tokenize v0.5.24 [0dad84c5] + ArgTools v1.1.1 [56f22d72] + Artifacts [ade2ca70] + Dates [8bb1440f] + DelimitedFiles [8ba89e20] + Distributed [f43a241f] + Downloads v1.6.0 [7b1f6079] + FileWatching [b77e0a4c] + InteractiveUtils [b27032c2] + LibCURL v0.6.3 [76f85450] + LibGit2 [8f399da3] + Libdl [37e2e46d] + LinearAlgebra [56ddb016] + Logging [a63ad114] + Mmap [ca575930] + NetworkOptions v1.2.0 [44cfe95a] + Pkg v1.10.0 [de0858da] + Printf [3fa0cd96] + REPL [1a1011a3] + SharedArrays [6462fe0b] + Sockets [2f01184e] + SparseArrays [10745b16] + Statistics [fa267f1f] + TOML v1.0.0 [a4e569a6] + Tar v1.10.0 [8dfed614] + Test [cf7118a7] + UUIDs [4ec0a83e] + Unicode [e66e0078] + CompilerSupportLibraries_jll v0.5.2+0 [deac9b47] + LibCURL_jll v7.83.1+1 [29816b5a] + LibSSH2_jll v1.10.2+0 [c8ffd9c3] + MbedTLS_jll v2.28.0+0 [14a3606d] + MozillaCACerts_jll v2022.2.1 [4536629a] + OpenBLAS_jll v0.3.20+0 [83775a58] + Zlib_jll v1.2.12+3 [8e850b90] + libblastrampoline_jll v5.1.1+0 [8e850ede] + nghttp2_jll v1.47.0+0 [3f19e933] + p7zip_jll v17.4.0+0 Info Packages marked with ⌃ and ⌅ have new versions available, but those with ⌅ cannot be upgraded. To see why use `status --outdated -m` ``` Si echamos un vistazo a la nueva entrada `Manifest.toml` para `MacroTools`, vemos: ```toml [[deps.MacroTools]] deps = ["CSTParser", "Compat", "DataStructures", "Test", "Tokenize"] git-tree-sha1 = "d6e9dedb8c92c3465575442da456aec15a89ff76" uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.1" ``` Esto demuestra que incluso entre versiones de parches de un paquete, que deberían seguir la misma API pública, las dependencias pueden cambiar mucho. Si miras `Project.toml` verás que no ha cambiado. El requisito de versión sólo se aplicó durante la resolución de esta dependencia, y no se recordará ni se aplicará de nuevo en futuras operaciones de `Pkg`. Vamos a probar otra sintaxis, que es la de elegir una confirmación o rama específica de un repositorio. En este caso, usamos el commit `639d1a6`, pero también podríamos usar algo como `master` para obtener el último commit de esa rama: ```terminal (@v1.10) pkg> add MacroTools#639d1a6 Resolving package versions... Updating `~/.julia/environments/v1.10/Project.toml` [1914dd2f] ~ MacroTools v0.5.1 ⇒ v0.5.9 `https://github.com/FluxML/MacroTools.jl.git#639d1a6` Updating `~/.julia/environments/v1.10/Manifest.toml` [00ebfdb7] - CSTParser v2.5.0 [34da2185] - Compat v2.2.1 [864edb3b] - DataStructures v0.17.20 [1914dd2f] ~ MacroTools v0.5.1 ⇒ v0.5.9 `https://github.com/FluxML/MacroTools.jl.git#639d1a6` [bac558e1] - OrderedCollections v1.4.1 [0796e94c] - Tokenize v0.5.24 [0dad84c5] - ArgTools v1.1.1 [56f22d72] - Artifacts [ade2ca70] - Dates [8bb1440f] - DelimitedFiles [8ba89e20] - Distributed [f43a241f] - Downloads v1.6.0 [7b1f6079] - FileWatching [b77e0a4c] - InteractiveUtils [b27032c2] - LibCURL v0.6.3 [76f85450] - LibGit2 [8f399da3] - Libdl [37e2e46d] - LinearAlgebra [56ddb016] - Logging [a63ad114] - Mmap [ca575930] - NetworkOptions v1.2.0 [44cfe95a] - Pkg v1.10.0 [de0858da] - Printf [3fa0cd96] - REPL [1a1011a3] - SharedArrays [6462fe0b] - Sockets [2f01184e] - SparseArrays [10745b16] - Statistics [fa267f1f] - TOML v1.0.0 [a4e569a6] - Tar v1.10.0 [8dfed614] - Test [cf7118a7] - UUIDs [4ec0a83e] - Unicode [e66e0078] - CompilerSupportLibraries_jll v0.5.2+0 [deac9b47] - LibCURL_jll v7.83.1+1 [29816b5a] - LibSSH2_jll v1.10.2+0 [c8ffd9c3] - MbedTLS_jll v2.28.0+0 [14a3606d] - MozillaCACerts_jll v2022.2.1 [4536629a] - OpenBLAS_jll v0.3.20+0 [83775a58] - Zlib_jll v1.2.12+3 [8e850b90] - libblastrampoline_jll v5.1.1+0 [8e850ede] - nghttp2_jll v1.47.0+0 [3f19e933] - p7zip_jll v17.4.0+0 ``` Ten en cuenta que aunque la versión para MacroTools es de nuevo `0.5.9`, no significa necesariamente que estemos en el mismo commit que el apuntado por la versión `0.5.9` en el registro. Sólo significa que `Pkg` clonó el repositorio, comprobó el commit con el hash `639d1a6` y encontró el especificador de versión `0.5.9` en el propio fichero `Project.toml` de MacroTools. Por lo tanto, infinitas versiones de código de un paquete pueden ser tratadas como la versión `X.Y.Z` por `Pkg`, pero `X.Y.Z` sólo se refiere a exactamente una versión. Es importante recordar esta distinción al desarrollar y editar un paquete. Si echamos otro vistazo a nuestro `Manifest.toml`, podemos ver la siguiente entrada para MacroTools: ```toml [[deps.MacroTools]] deps = ["Markdown", "Random"] git-tree-sha1 = "465a4803356bcb11f6eb97df992680f13a9ba776" repo-rev = "639d1a6" repo-url = "https://github.com/FluxML/MacroTools.jl.git" uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.9" ``` Esta vez, se registró la URL del repositorio, así como la revisión, que fue `639d1a6`. Esto se debe a que tan pronto como especifica una revisión en el comando `add`, Pkg sabe que está operando fuera del registro, por lo que no puede confiar en la información del repositorio almacenada allí para MacroTools. Ten en cuenta que también puedes instalar paquetes no registrados, o bifurcaciones de paquetes registrados de esta manera, haciendo `add https://the_url_to_the_git_repository`. El manifiesto necesita almacenar la url y la revisión para que el proyecto pueda ser reproducido por otra persona. Veamos cómo es reproducir un entorno. ## Reproducir un entorno Supongamos que hemos escrito un código que depende de la versión específica de `MacroTools` que añadimos mediante `add MacroTools#639d1a6` y queremos que nuestros compañeros puedan ejecutar ese código con los paquetes exactos instalados que usamos en su momento. ¿Qué ficheros se necesitan para reproducir el estado de tu entorno? `Project.toml` contiene solo esta información: ```julia [deps] MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" ``` Por lo que necesitamos enviar también `Manifest.toml`, porque es el fichero que registra las versiones exactas de todos los paquetes. Si acabamos de recibir los archivos `Project.toml` y `Manifest.toml`, ¿cómo conseguimos instalar el entorno? !!!side:6 Si queremos simular el proceso, también borramos la carpeta `.julia/packages/MacroTools` en la que estaba almacenado el código fuente descargado de `MacroTools`. Ten en cuenta que esto significa que el código fuente no forma parte de un entorno, sino que se almacena de forma centralizada. Sería bastante derrochador descargar los mismos fuentes una y otra vez sólo porque estás utilizando diferentes entornos locales. El primer paso es copiar los archivos en una nueva carpeta que llamaremos, por ejemplo, `EntornoEquipo` en nuestro directorio de trabajo actual $^6$. Ahora reiniciemos Julia y activemos el entorno `EntornoEquipo`: ```terminal (@v1.10) pkg> activate ./EntornoEquipo Activating project at `~/EntornoEquipo` ``` Podemos comprobar los paquetes instalados mediante `st -m`: ```terminal (EntornoEquipo) pkg> st -m Status `~/EntornoEquipo/Manifest.toml` → [1914dd2f] MacroTools v0.5.9 `https://github.com/FluxML/MacroTools.jl.git#master` [2a0f44e3] Base64 [d6f4376e] Markdown [9a3f8284] Random [ea8e919c] SHA v0.7.0 [9e88b42a] Serialization Info Packages marked with → are not downloaded, use `instantiate` to download ``` Si ahora intentáramos ejecutar un código utilizando `MacroTools`, ocurriría lo siguiente: ```terminal julia> using MacroTools ERROR: ArgumentError: Package MacroTools [1914dd2f-81c6-5fcd-8719-6d5c9610ff09] is required but does not seem to be installed: - Run `Pkg.instantiate()` to install all recorded dependencies. Stacktrace: [1] _require(pkg::Base.PkgId) @ Base ./loading.jl:1306 [2] _require_prelocked(uuidkey::Base.PkgId) @ Base ./loading.jl:1200 [3] macro expansion @ ./loading.jl:1180 [inlined] [4] macro expansion @ ./lock.jl:223 [inlined] [5] require(into::Module, mod::Symbol) @ Base ./loading.jl:1144 ``` Así que tenemos que seguir el consejo que nos da la propia herramienta y llamar a `instantiate`, que descargará todo lo especificado en el `Manifest.toml` exactamente como se registró allí. De hecho, puedes estar seguro de que es exactamente lo mismo porque el `Manifest.toml` almacena los hashes del árbol git de cada dependencia. A menos que alguien borre estas partes específicas del repositorio, podrás descargar el código fuente exactamente como estaba: ```terminal (EntornoEquipo) pkg> instantiate Precompiling project... ✓ MacroTools 1 dependency successfully precompiled in 1 seconds (EntornoEquipo) pkg> st -m Status `~/EntornoEquipo/Manifest.toml` [1914dd2f] MacroTools v0.5.9 `https://github.com/FluxML/MacroTools.jl.git#master` [2a0f44e3] Base64 [d6f4376e] Markdown [9a3f8284] Random [ea8e919c] SHA v0.7.0 [9e88b42a] Serialization ``` Ahora ya no recibimos una advertencia, nuestras dependencias se han descargado correctamente, y podrás encontrar `MacroTools` descargado en la carpeta `.julia/packages` de nuevo. ## Paquetes y entornos Los paquetes y los entornos normales son bastante similares. Cada paquete debe tener un `Project.toml` que especifique su nombre, UUID, versión y dependencias. La forma más fácil de hacer un paquete para probar esto es usar el comando `generate` en el `Pkg` del REPL. Reiniciemos Julia y eliminemos `MacroTools` de nuestro entorno principal para que quede vacío: ```terminal (@v1.10) pkg> rm MacroTools Updating `~/.julia/environments/v1.10/Project.toml` [1914dd2f] - MacroTools v0.5.9 `https://github.com/FluxML/MacroTools.jl.git#639d1a6` Updating `~/.julia/environments/v1.10/Manifest.toml` [1914dd2f] - MacroTools v0.5.9 `https://github.com/FluxML/MacroTools.jl.git#639d1a6` [2a0f44e3] - Base64 [d6f4376e] - Markdown [9a3f8284] - Random [ea8e919c] - SHA v0.7.0 [9e88b42a] - Serialization ``` Ahora, generamos un nuevo paquete llamado `MyPackage`: ```terminal (@v1.10) pkg> generate MyPackage Generating project MyPackage: MyPackage/Project.toml MyPackage/src/MyPackage.jl ``` Como puedes ver, se generó un archivo `Project.toml` en el directorio `MyPackage`, con el siguiente contenido: ```toml name = "MyPackage" uuid = "025f59cc-7e1c-467d-8f56-70157e1cbbbb" authors = ["Your Name "] version = "0.1.0" ``` La única diferencia de un entorno de paquete básico a un entorno normal son esos cuatro campos. Si queremos usar `MacroTools` en nuestro paquete, podemos añadirlo manualmente a una sección `deps` en el `Project.toml`, o usamos el `Pkg` en REPL. Para ello, primero activamos el paquete como entorno, y luego añadimos `MacroTools`: ```terminal (@v1.10) pkg> activate MyPackage/ Activating project at `~/MyPackage` (MyPackage) pkg> add MacroTools Updating registry at `~/.julia/registries/General.toml` Resolving package versions... Installed MacroTools ─ v0.5.9 Updating `~/MyPackage/Project.toml` [1914dd2f] + MacroTools v0.5.9 Updating `~/MyPackage/Manifest.toml` [1914dd2f] + MacroTools v0.5.9 [2a0f44e3] + Base64 [d6f4376e] + Markdown [9a3f8284] + Random [ea8e919c] + SHA v0.7.0 [9e88b42a] + Serialization Precompiling project... ✓ MacroTools ✓ MyPackage 2 dependencies successfully precompiled in 1 seconds ``` `Project.toml` ahora tiene este aspecto: ```toml name = "MyPackage" uuid = "025f59cc-7e1c-467d-8f56-70157e1cbbbb" authors = ["Your Name "] version = "0.1.0" [deps] MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" ``` En el fichero `MyPackage/src/MyPackage.jl` ya podemos importar `MacroTools` y trabajar con él. Vamos a cambiar el contenido de ese archivo a: ```julia module MyPackage import MacroTools function test() MacroTools.@capture :(1 + 2) x_ + y_ @show x @show y return end end # module MyPackage ``` Ahora podemos importar (`import`) o usar (`using`) nuestro paquete y verificar que `MacroTools` puede ser usado por otro código fuente: ```terminal julia> using MyPackage julia> MyPackage.test() x = 1 y = 2 ``` ## Características avanzadas Existe todo un mecanismo similar a `add` pero que se usa para mantener entornos cambiantes cuando se están desarrollando librerías y paquetes haciendo uso del comando `dev`, que permite, entre otras cosas, especificar límites de compatibilidad, mantener desarrollos abiertos, etc. Como no es nuestro objetivo meternos a tanto nivel en las capacidades de Julia (aunque son muy interesantes y os aconsejo que lo hagáis), no entraremos en más detalles en esta dirección. Además, el sistema de paquetes de Julia también admite lo que se llaman *entornos apilados*, que permite importar paquetes de varios entornos y *entornos temporales*, para hacer pruebas rápidas sin desordenar el entorno principal o llenar innecesariamente el directorio de trabajo con archivos de entorno. ## Conclusión Este ha sido un breve recorrido por `Pkg` y sus principales funciones. Espero que haya quedado más claro cómo funcionan los entornos y por qué no deberías confiar en un único entorno global. Es bastante fácil crear un entorno por proyecto y los archivos de texto creados no ocupan espacio, así que no hay inconveniente en hacerlo. En resumen, podemos destacar las características clave de los entornos de Julia como: * **Independencia de Dependencias**: Cada entorno tiene su propio archivo `Project.toml` y, opcionalmente, un archivo `Manifest.toml`. El primero contiene una lista de las dependencias y sus versiones aproximadas, mientras que el segundo fija las versiones exactas de todas las dependencias. * **Facilidad de Creación y Cambio**: Es muy fácil crear un nuevo entorno o cambiar entre ellos usando la línea de comandos de Julia. Simplemente puedes usar `]` para entrar al modo de gestión de paquetes y luego escribir comandos como: * `activate .` para activar el entorno en el directorio actual. * `activate nombre_del_entorno` para activar un entorno específico. * `activate` para activar el entorno principal. * **Reproducibilidad**: Al compartir un proyecto con su archivo `Manifest.toml`, otros usuarios pueden recrear exactamente el mismo entorno con las mismas versiones de paquetes, lo que garantiza que el código funcione de manera consistente en diferentes máquinas. * **Gestión de Dependencias**: Puedes agregar, actualizar o eliminar paquetes dentro de un entorno sin afectar otros proyectos que estén utilizando versiones diferentes de esos paquetes. Para más información, puedes consultar algunas de estas fuentes: - [Pkg.jl documentation](https://pkgdocs.julialang.org/v1/). - [DrWatson.jl](https://juliadynamics.github.io/DrWatson.jl/stable/), una herramienta que intenta facilitar el proceso de creación de proyectos reproducibles. - [TestEnv.jl](https://github.com/JuliaTesting/TestEnv.jl), que ayuda con el problema de los entornos apilados en el contexto de las pruebas (no tratado aquí). - [PkgTemplates.jl](https://github.com/invenia/PkgTemplates.jl), que facilita la creación de paquetes.