Esta página explica los ámbitos. Se supone que has leído y entendido las páginas anteriores, Definiciones de construcción y Grafos de tareas.
Anteriormente fingimos que una clave tal como name
correspondía
a una única entrada en el mapa de pares clave-valor de sbt. Esto fue una
simplificación.
En realidad cada clave puede tener un valor asociado en más de un contexto, llamado ámbito.
Algunos ejemplos concretos:
compile
puede tener un valor para tus fuentes principales y otro
diferente para tus ficheros de test, si quisieras compilarlos de forma
distinta.
packageOptions
(que contiene opciones para crear paquetes jar)
puede tener diferentes valores cuando se empaquetan ficheros de clases
(packageBin
) o paquetes de código fuente (packageSrc
).
No existe un único valor para una clave name
data, porque el valor puede
diferir según el ámbito.
Sin embargo, existe un único valor para una cierta clave en un ámbito.
Si piensas en sbt cuando procesa una lista de entradas para generar un mapa
de clave-valor que describan un proyecto, tal y como se
explicó antes, las claves en ese mapa de clave-valor son claves
con ámbito. Cada entrada definida en la definición de construcción (por
ejemplo en build.sbt
) se aplica también a una clave con ámbito.
Frecuentemente el ámbito está implícito o existe uno predeterminado, pero si
el ámbito predeterminado no es el que te interesa deberás mencionar
explícitamente el ámbito que deseas en build.sbt
.
Un eje de ámbito es un constructor de tipo similar a Option[A]
que es usado
para formar un componente en un ámbito.
Existen tres ejes de ámbito:
Si el concepto de eje no te resulta familiar podemos pensar en un cubo de color RGB como ejemplo:
En el modelo de color RGB todos los colores son representados por un punto en el cubo cuyos ejes corresponden a las componentes rojo, verde y azul codificadas por un número. De forma similar un ámbito total en sbt está formado por el valor de la tupla de un subproyecto, una configuración y un tarea:
projA / Compile / console / scalacOptions
Que es la sintaxis de barra, introducida en sbt 1.1, equivalente a:
scalacOptions in (
Select(projA: Reference),
Select(Compile: ConfigKey),
Select(console.key)
)
Si pones múltiples proyectos en una única construcción, cada proyecto necesitará su propia configuración. Es decir, las claves pueden tener un ámbito u otro de acuerdo al proyecto.
El eje de proyecto puede también ser establecido a ThisBuild
, que quiere decir
la “construcción entera” por lo que una entrada se aplica a toda la construcción
en lugar de a un único proyecto.
La configuración a nivel de construcción es frecuentemente utilizada como configuración predeterminada cuando un proyecto no define una entrada específica. Explicaremos configuraciones a nivel de construcción más adelante en esta página.
Una configuración de dependencia (o simplemente “configuración”) define un grafo de dependencias de bibliotecas, potencialmente con su propio classpath, ficheros fuentes, paquetes generados, etc… El concepto de configuración de dependencia proviene de Ivy, el cual es usado por sbt para gestionar dependencias y de los ámbitos de Maven.
Algunas configuraciones que verás en sbt:
Compile
que define la construcción de los ficheros principales
(src/main/scala
).
Test
que define cómo construir los tests (src/test/scala
).
Runtime
que define el classpath para la tarea run
.
De forma predeterminada, todas las claves asociadas a la compilación,
empaquetado y ejecución tienen como ámbito una configuración y por tanto pueden
funcionar de forma diferente en cada configuración. Los ejemplos más ovios son
las claves tarea compile
, package
y run
, y todas las claves que afectan
a dichas claves (tales como sourceDirectories
, scalacOptions
o
fullClasspath
) también tienen una configuración como ámbito.
Otra cosa a tener en cuenta sobre una configuración es que puede extender otras configuraciones. La siguiente figura muestra la relación de extensión entre las configuraciones más comunes.
Test
y IntegrationTest
extienden Runtime
; Runtime
extiende Compile
;
CompileInternal
extiende Compile
, Optional
, y Provided
.
La configuración puede afectar a cómo funciona una tarea. Por ejemplo, la tarea
packageSrc
es afectada por la entrada packageOptions
.
Para soportar esto, una clave tarea (tal como packageSrc
) puede tener un
ámbito para otra clave (tal como packageOptions
).
Las distintas tareas que construyen un paquete (packageSrc
, packageBin
,
packageDoc
) pueden compartir claves relacionadas con el empaquetado, tales
como artifactName
y packageOptions
Esas claves pueden tener distintos
valores para cada tarea de empaquetado.
Zero
Cada eje de ámbito puede ser rellenado tanto con una instancia del tipo del eje
(análogamente a como ocurre con Some(_)
), o con el valor especial Zero
.
Podemos pensar en Zero
como None
.
Zero
es un comodín universal para todos los ejes de ámbito pero su uso directo
debería de estar reservado para sbt y, en todo caso, para los autores de
plugins.
Global
es un ámbito que establece Zero
para todos los ejes:
Zero / Zero / Zero
. En otras palabras, Global / clave
es un atajo para
Zero / Zero / Zero / clave
.
Si creas una entrada en build.sbt
con una clave plana entonces tendrá como ámbito
(subproyecto actual / configuración Zero
/ tarea Zero
):
lazy val root = (project in file("."))
.settings(
name := "hello"
)
Si ejecutas sbt y lanzas inspect name
podrás comprobar que es proporcionada por
ProjectRef(uri("file:/private/tmp/hello/"), "root") / name
, es decir,
el proyecto es ProjectRef(uri("file:/Users/xxx/hello/"), "root")
y ni el
ámbito de la configuración ni el de la tarea son mostrados (lo que significa
que son Zero
).
Una clave plana a la derecha también tiene como ámbito
(subproyecto actual / configuración Zero
/ tarea Zero
):
organization := name.value
Los tipos de cualesquiera de los ejes de ámbito están extendidos para tener un
operador /
. El argumento de /
puede ser una clave u otro eje de ámbito. Así
que por ejemplo, aunque no hay ninguna razón de peso para hacer lo siguiente,
se podría tener una instancia de la clave name
en el ámbito de la
configuración Compile
:
Compile / name := "hello"
o podrías establecer name
en el ámbito de la tarea packageBin
(algo inútil,
sólo es un ejemplo).
packageBin / name := "hello"
o podrías establecer name
con múltiples ejes de ámbito, por ejemplo con la
tarea packageBin
en la configuración Compile
:
Compile / packageBin / name := "hello"
o puedes utilizar Global
:
// same as Zero / Zero / Zero / concurrentRestrictions
Global / concurrentRestrictions := Seq(
Tags.limitAll(1)
)
(Global / concurrentRestrictions
es convertido implícitamente a
Zero / Zero / Zero / concurrentRestrictions
, estableciendo todos los ejes a
ámbito Zero
. De forma predeterminada las tareas y las configuraciones ya son
Zero
de forma predeterminada por lo que la única utilidad de emplear esto es
la de establecer el proyecto a Zero
en lugar de a
ProjectRef(uri("file:/tmp/hello/"), "root") / Zero / Zero / concurrentRestrictions
)
En la línea de comandos y en el shell de sbt, sbt muestra (y analiza) claves con ámbito como esta:
ref / Config / intask / key
ref
identifica el eje de subproyecto. Puede ser <project-id>
,
ProjectRef(uri("file:..."), "id")
, o ThisBuild
para denotar el ámbito de
la construcción entera.
Config
identifica el eje de configuración utilizando el identificador de
Scala empezando por mayúscula.
intask
identifica el eje de tarea.
key
identifica la clave a la que se le aplica el ámbito.
Zero
puede aparecer en cada eje.
Si se omiten partes del ámbito de la clave, éstas serán inferidas siguiendo las siguientes reglas:
Para más información ver Interactuar con el sistema de configuración.
fullClasspath
especifica simplemente una clave, por lo que los ámbitos
predeterminados son utilizados: proyecto actual, una configuración dependiente
de la clave y el ámbito de tarea Zero
.
Test / fullClasspath
emplea una configuración, por lo que es fullClasspath
en la configuración Test
, con valores predeterminados para el resto de ejes.
root / fullClasspath
especifica el proyecto root
, donde el proyecto es
identificado por el identificador de proyecto.
root / Zero / fullClasspath
especifica el proyecto root
y Zero
para la
configuración, en lugar de la predeterminada.
doc / fullClasspath
especifica la clave fullClasspath
en el ámbito de la
tarea doc
, con los valores predeterminados para el eje del proyecto y el de
la configuración.
ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspath
especifica el proyecto ProjectRef(uri("file:/tmp/hello/"), "root")
.
Además especifca la configuración Test
, dejando el eje de tarea al valor
predeterminado.
ThisBuild / version
establece el eje de subproyecto a la “construcción
entera” donde la construcción es ThisBuild
, con la configuración
predeterminada.
Zero / fullClasspath
establece el eje de subproyecto a Zero
,
con la configuración predeterminada.
root / Compile / doc / fullClasspath
establece todos los ejes de ámbito.
En el shell de sbt, puedes utilizar el comando inspect
para comprender las
claves y sus ámbitos. Prueba inspect test / fullClasspath
:
$ sbt
sbt:Hello> inspect Test / fullClasspath
[info] Task: scala.collection.Seq[sbt.internal.util.Attributed[java.io.File]]
[info] Description:
[info] The exported classpath, consisting of build products and unmanaged and managed, internal and external dependencies.
[info] Provided by:
[info] ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspath
[info] Defined at:
[info] (sbt.Classpaths.classpaths) Defaults.scala:1639
[info] Dependencies:
[info] Test / dependencyClasspath
[info] Test / exportedProducts
[info] Test / fullClasspath / streams
[info] Reverse dependencies:
[info] Test / testLoader
[info] Delegates:
[info] Test / fullClasspath
[info] Runtime / fullClasspath
[info] Compile / fullClasspath
[info] fullClasspath
[info] ThisBuild / Test / fullClasspath
[info] ThisBuild / Runtime / fullClasspath
[info] ThisBuild / Compile / fullClasspath
[info] ThisBuild / fullClasspath
[info] Zero / Test / fullClasspath
[info] Zero / Runtime / fullClasspath
[info] Zero / Compile / fullClasspath
[info] Global / fullClasspath
[info] Related:
[info] Compile / fullClasspath
[info] Runtime / fullClasspath
En la primera línea, se puede apreciar que esta es una tarea (y no una entrada,
tal y como se explica en Definiciones de construcción).
El valor resultante de la tarea es del tipo
scala.collection.Seq[sbt.Attributed[java.io.File]]
.
“Provided by” indica la clave con ámbito que define el valor, en este caso
ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspath
(que es la
clave fullClasspath
en el ámbito de la configuración Test
y el proyecto
ProjectRef(uri("file:/tmp/hello/"), "root")
project).
“Dependencies” fue explicado en detalle en la página anterior.
“Delegates” será explicado más adelante.
Si ejecutas inspect fullClasspath
(en oposición al ejemplo de arriba,
inspect Test / fullClasspath
) podrás apreciar la diferencia. Debido a que la
configuración es omitida, es autodetectada como Compile
.
inspect Compile / fullClasspath
debería por tanto ser lo mismo que
inspect fullClasspath
.
Si ejecutas inspect ThisBuild / Zero / fullClasspath
podrás obtener otro ejemplo.
De forma predeterminada fullClasspath
no está definido en la ámbito de la
configuración Zero
.
Una vez más, para más información ver Interactuar con el sistema de configuración.
Un ámbito necesita ser especificado si la clave en cuestión ya está asociada
a otro ámbito.
Por ejemplo, la tarea compile
, de forma predeterminada, tiene como ámbito
las configuraciones Compile
y Test
y no existe fuera de dichos ámbitos.
Para cambiar el valor asociado con la clave compile
necesitas escribir
Compile / compile
o Test / compile
. Utilizar solamente compile
definiría
una nueva tarea compile
en el ámbito del proyecto actual, en lugar de
sobrescribir la tarea de compilación estándar, la cual tiene como ámbito una
configuración.
Si obtienes un error como “Reference to undefined setting“, con frecuencia significará que no has especificado un ámbito, o que has especificado el ámbito equivocado. La clave que estás utilizando puede estar definida en otro ámbito. sbt intentará sugerir lo que querías decir como parte del mensaje de error. Busca cosas tipo “Did you mean Compile / compile?”
Una forma de pensar en esto es que un nombre es solo una parte de una clave.
En realidad, todas las claves consisten tanto en un nombre como en un ámbito
(donde el ámbito tiene tres ejes).
La expresión completa Compile / packageBin / packageOptions
es un nombre de
clave, dicho de otra forma.
packageOptions
a secas también es un nombre de clave, pero uno diferente (uno
donde los ámbitos son implícitamente establecidos: proyecto actual,
configuración Zero
y tarea Zero
).
Una técnica avanzada para extraer configuración común a todos los subproyectos
es definir valores en el ámbito de ThisBuild
.
Si una clave que tiene como ámbito un subproyecto en particular no se encuentra,
sbt la buscará en el ámbito de ThisBuild
.
Usando este mecanismo, podemos definir valores predeterminados a nivel de
construcción para claves usadas con frecuencia tales como version
,
scalaVersion
y organization
.
ThisBuild / organization := "com.example",
ThisBuild / scalaVersion := "2.12.18",
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "Hello",
publish / skip := true
)
lazy val core = (project in file("core"))
.settings(
// other settings
)
lazy val util = (project in file("util"))
.settings(
// other settings
)
Convenientemente, existe una función inThisBuild(...)
que definirá tanto
la clave como el valor al ámbito de ThisBuild
. Definir valores aquí sería
equivalente a prefijar cada uno con ThisBuild /
allá donde fuera posible.
Debido a la naturaleza de la delegación de ámbito que
explicaremos más adelante, la configuración a nivel de construcción debería ser
utilizada sólo para valores puros o para valores en el ámbito de Global
o
ThisBuild
.
Una clave con ámbito puede no haber sido definida, si no tiene un valor asociado en su ámbito.
Para cada eje de ámbito, sbt tiene un camino de búsqueda alternativo consistente
en otros valores con ámbito. Habitualmente, si una clave no tiene asociado un
valor en un ámbito específico, sbt intentará obtener un valor de un ámbito más
general, tal como el ámbito ThisBuild
.
Esta característica te permitirá establecer un valor una única vez en un ámbito general, permitiendo múltiples ámbitos específicos que heredan el valor. Lo discutiremos con mas detalle más tarde en Delegación de ámbito.