Esta página describe las definiciones de construcción (build definitions),
incluyendo algo de “teoría” y la sintaxis de build.sbt
. Se supone que has
instalado una versión reciente de sbt, como sbt 1.9.8, que sabes cómo
usar sbt y que has leído las páginas anteriores de la Guía de inicio.
Esta página explica la definición de construcción de build.sbt
.
Como parte de tu definición de construcción debes de especificar la versión de sbt que tu construcción utiliza.
Esto permitirá a la gente con diferentes versiones del lanzador de sbt
construir los mismos proyectos con resultados consistentes. Para hacer esto,
crea un fichero llamado project/build.properties
en el que se especifica la
versión de sbt como sigue:
sbt.version=1.9.8
Si la versión requerida no está disponible localmente, el lanzador sbt
se la
descargará por ti. Si este fichero no está presente, el lanzador sbt
eligirá
una versión arbitraria, lo cual no es aconsejable debido a que hará que tu
construcción no sea portable.
Una definición de construcción es definida en build.sbt
y consiste en un
conjunto de proyectos (de tipo Project
). Debido a
que el término project puede ser ambiguo, con frecuencia utilizaremos el subproyecto para referirnos a ellos en esta guía.
Por ejemplo, en build.sbt
se define el subproyecto ubicado en el directorio
actual así:
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7"
)
Cada subproyecto es configurado como pares clave-valor.
Por ejemplo, una clave es name
y se mapea a una cadena de texto, el nombre
de tu subproyecto. Los pares clave-valor se listan bajo el método
.settings(...)
de tal forma:
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7"
)
build.sbt
define subproyectos, los cuales contienen una secuencia de pares
clave-valor llamados expresiones de configuración (expression settings)
utilizando un DSL de build.sbt
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "hello"
)
Echemos un vistazo más de cerca al DSL de build.sbt:
Cada entrada es llamada una expresion de configuración. De entre ellas, algunas son también llamadas expresiones de tareas. Veremos la diferencia entre ellas más tarde en esta página.
Una expresión de configuración consiste en tres partes:
:=
A la izquierda, name
, version
y scalaVersion
son claves. Una clave es
una instancia de
SettingKey[T]
,
TaskKey[T]
o
InputKey[T]
donde T
es el tipo esperado. Los tipos de claves son explicados más abajo.
Debido a que la clave name
tiene tipo SettingsKey[String]
, el operador :=
aplicado a name
también está tipado como String
. Si usas el tipo incorrecto
la definición de construcción no compilará:
lazy val root = (project in file("."))
.settings(
name := 42 // no compila
)
En build.sbt
también se pueden entremezclar val
, lazy val
y def
. Los
object
y class
de primer nivel no están permitidos en build.sbt
. De
hacerlo deberían ir en el directorio project/
como ficheros fuente de Scala.
Hay tres sabores de claves:
SettingKey[T]
: una clave para un valor calculado una única vez (el valor
es calculado cuando se carga un subproyecto)
TaskKey[T]
: una clave para un valor, llamado una tarea que ha de ser
recalculado cada vez, potencialmente con efectos colaterales.
InputKey[T]
: una clave para una tarea con argumentos de línea de comandos
como entrada. Para más información mira Tareas de entrada.
Las claves preconfiguradas son simplemente campos de un objeto llamado
Keys. Un build.sbt
tiene de forma implícita un
import sbt.Keys._
, por lo que sbt.Keys.name
puede ser accedido como name
.
Las claves personalizadas pueden ser definidas con sus respectivos métodos de
creación: settingKey
, taskKey
e inputKey
. Cada método espera el tipo del valor
asociado con la clave además de su descripción. El nombre de la clave es tomado
del val
al cual la clave es asignada. Por ejemplo, para definir una clave para
una nueva tarea llamada hello
,
lazy val hello = taskKey[Unit]("An example task")
Aquí hemos usado el hecho de que un fichero .sbt
puede contener val
y def
además de configuración. Todas estas definiciones son evaluadas antes de la
configuración sin importar en qué lugar del fichero han sido definidas.
Nota: Tipicamente, se usan
lazy val
en lugar deval
para evitar problemas de orden durante la inicialización.
Se dice de TaskKey[T]
que define una tarea. Las tareas son operaciones
tales como compile
o package
. A su vez pueden devolver Unit
(Unit
es el
tipo de Scala para void
) o pueden devolver un valor relacionado con la tarea,
por ejemplo el de package
es un TaskKey[File]
y su valor es el fichero jar que
crea.
Cada vez que inicias la ejecución de una tarea, por ejemplo escribiendo
compile
en el prompt interactivo de sbt, sbt volverá a ejecutar cualesquiera
tareas implicadas exactamente una vez.
Los pares clave-valor de sbt que describen al subproyecto pueden ser
almacenados en forma de cadena fija para configuraciones tales como name
,
pero tienen que poder guardar código ejecutable para tareas tales como
compile
. Incluso si dicho código ejecutable acaba devolviendo una cadena,
tiene que ser ejecutado cada vez.
Una cierta clave siempre se refiere o bien a una tarea o a una entrada plana. El que tenga que ser ejecutada cada vez o no es una propiedad de la clave, no del valor.
Utilizando :=
se puede asignar un valor a una entrada y una computación a una
tarea. Para una entrada, el valor será computado una única vez durante la carga
del proyecto. Para una tarea, la computación será evaluada cada vez que la
tarea sea ejecutada.
Por ejemplo, para implementar la tarea hello
de la sección anterior:
lazy val hello = taskKey[Unit]("An example task")
lazy val root = (project in file("."))
.settings(
hello := { println("Hello!") }
)
Ya vimos un ejemplo sobre cómo definir entradas cuando definimos el nombre del proyecto:
lazy val root = (project in file("."))
.settings(
name := "hello"
)
Desde una perspectiva del sistema de tipos, el Setting
creado a partir de una
clave tarea es ligeramente diferente de la creada para una clave entrada.
taskKey := 42
resulta en Setting[Task[T]]
mientras que settingKey := 42
resulta en Setting[T]
. En la mayoría de los casos esto no supone ninguna
diferencia; la clave tarea aún sigue creando un valor de tipo T
cuando la
tarea es ejecutada.
La diferencia de tipos entre T
y Task[T]
tiene la siguiente implicación:
una entrada no puede depender de una tarea, debido a que una entrada es
evaluada una única vez durante la carga de un proyecto y no es re-evaluada. Más
información sobre esto en Grafos de tareas.
En el shell de sbt, puedes escribir el nombre de cualquier tarea para ejecutar
dicha tarea. Esta es la razón por la que al escribir compile
se ejecuta la tarea compile
.
compile
es una clave tarea.
Si escribes el nombre de una clave entrada en lugar de una clave tarea el valor
de la clave entrada será mostrado. Escribir el nombre de una clave tarea
ejecuta la tarea pero muestra el valor resultante. Para ver el resultado de una
tarea utiliza show <tarea>
en lugar de simplemente <tarea>
. La convención
para nombres de claves es utilizar camelCase
por lo que el nombre de linea de
comandos y los identificadores de Scala son los mismos.
Para aprender más acerca de cualquier clave escribe inspect <clave>
en el
prompt interactivo de sbt. Alguna de la información que inspect
muestra no
tendrá sentido (de momento), pero al principio de todo mostrará el tipo del
valor y una breve descripción de esa entrada.
Puedes incluir sentencias import
al principio de build.sbt
. No necesitan
estar separadas por líneas en blanco.
Existen algunas importaciones implícitas predeterminadas:
import sbt._
import Keys._
(Además, si tienes autoplugins, los nombres marcados bajo autoImport
serán
importados.)
La configuración puede ser escrita directamente en el fichero build.sbt
en
lugar de ponerla dentro de la llamada a .settings(...)
. Podemos llamar a esto
el “estilo plano”.
ThisBuild / version := "1.0"
ThisBuild / scalaVersion := "2.12.18"
Esta sintaxis es la recomendada para configuraciones con ámbito ThisBuild
plugins añadidos.
Mira secciones posteriores acerca de los ámbitos y los plugins.
Para depender de bibliotecas de terceros hay dos opciones. La primera es copiar
jars en lib/
(dependencias no gestionadas) y la otra es añadir dependencias
gestionadas, que en build.sbt
tienen este aspecto:
val derby = "org.apache.derby" % "derby" % "10.4.1.3"
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += derby
)
Así es como se puede añadir una dependencia gestionada de la biblioteca Apache Derby versión 10.4.1.3.
La clave libraryDependencies
posee dos complejidades: +=
en lugar de :=
y
el método %
. +=
añade al valor antiguo de la clave en lugar de
reemplazarlo, como se explica en Grafos de tareas. El método %
es utilizado para construir un identificador de módulo Ivy a partir de cadenas,
como se explica en Dependencias de bibliotecas.
De momento nos saltaremos los detalles de las dependencias de bibliotecas hasta más tarde en la Guía de inicio. Hay una página entera que habla de ellas más tarde.