Esta página introduce múltiples subproyectos en una única construcción.
Por favor, lee primero las páginas anteriores de la Guía de inicio, en particular necesitarás entender el fichero build.sbt antes de leer esta página.
Puede ser útil mantener múltiples subproyectos relacionados en una única construcción, especialmente si unos dependen de otros y sueles modificarlos todos a la vez.
Cada subproyecto en una construcción tiene sus propios directorios de código
fuente, genera su propio fichero jar cuando ejecutas package
y en general
funcionan como cualquier otro proyecto.
Un proyecto se define declarando un lazy val
de tipo
Project. Por ejemplo:
lazy val util = (project in file("util"))
lazy val core = (project in file("core"))
El nombre del val
es usado como ID de subproyecto, el cual es usado para
referirse al subproyecto en el shell de sbt.
Opcionalmente, el directorio base puede ser omitido si es el mismo que el nombre del val.
lazy val util = project
lazy val core = project
Para extraer configuración común a lo largo de múltiples proyectos puedes
definir la configuración en el ámbito de ThisBuild
.
Para ello la parte derecha tiene que ser un valor puro o una configuración en el
ámbito de Global
o ThisBuild
y no puede haber configuraciones
predeterminadas en el ámbito de subproyectos. (Ver Ámbitos)
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"
lazy val core = (project in file("core"))
.settings(
// otras configuraciones
)
lazy val util = (project in file("util"))
.settings(
// otras configuraciones
)
Ahora podemos incrementar version
en un único lugar y se verá reflejado a lo
largo de los subproyectos cuando recargues la construcción.
Otra forma de extraer configuración común a lo largo de múltiples proyectos es
crear una secuencia llamada commonSettings
y llamar al método settings
en
cada proyecto.
lazy val commonSettings = Seq(
target := { baseDirectory.value / "target2" }
)
lazy val core = (project in file("core"))
.settings(
commonSettings,
// otras configuraciones
)
lazy val util = (project in file("util"))
.settings(
commonSettings,
// otras configuraciones
)
Los proyectos en la construcción pueden ser completamente independientes uno de otro, pero por lo general estarán relacionados mediante algún tipo de dependencia. Hay dos tipos de dependencias: agregadas y de classpath.
Una agregación implica que al ejecutar una tarea en el proyecto que agrega también será ejecutada en los proyectos agregados. Por ejemplo:
lazy val root = (project in file("."))
.aggregate(util, core)
lazy val util = (project in file("util"))
lazy val core = (project in file("core"))
En el ejemplo de arriba, el proyecto raíz agrega util
y core
. Si ejecutas sbt
con dos subproyectos como los del ejemplo e intentas compilar podrás ver cómo
los tres proyectos son compilados.
En el proyecto que hace la agregación, el proyecto raíz en este caso, puedes
controlar la agregación a nivel de tarea. Por ejemplo, para evitar agregar la
tarea update
:
lazy val root = (project in file("."))
.aggregate(util, core)
.settings(
update / aggregate := false
)
[...]
update / aggregate
es la clave agregada en el ámbito de la tarea update
(ver Ámbitos).
Nota: La agregación ejecutará las tareas agregadas en paralelo y sin orden predeterminado entre ellas.
Un proyecto puede depender del código de otro proyecto. Esto se hace añadiendo
una llamada al método dependsOn
. Por ejemplo, si core
necesita util
en su
classpath deberías de definir core
así:
lazy val core = project.dependsOn(util)
A partir de ahora el código de core
puede utilizar las clases de util
. Esto
crea además un orden entre proyectos a la hora de compilarlos. util
deberá ser
actualizado y compilado antes de que core
pueda ser compilado.
Para depender de múltiples proyectos puedes utilizar múltiples argumentos en
dependsOn
, como dependsOn(bar, baz)
.
core dependsOn(util)
implica que la configuración de compile
en core
dependerá de la configuración de compile
en util
. Esto se puede escribir
de forma más explícita como dependsOn(util % "compile->compile")
.
La ->
en "compile->compile"
significa “depende de”, por lo que
"test->compile"
significa que la configuración de test
en core
depende
de la configuración de compile
en util
.
Omitir la parte de ->config
implica ->compile
, por lo que
dependsOn(util % "test")
significa que la configuración de test
en core
depende de la configuración de Compile
en util
.
Una declaración útil es "test->test"
que significa que test
depende de test
.
Esto permite poner código auxiliar para testear en util/src/test/scala
y luego
usar dicho código en core/src/test/scala
, por ejemplo.
Puedes declarar múltiples configuraciones para una dependencia, separadas por
punto y coma. Por ejemplo, dependsOn(util % "test->test;compile->compile")
.
En proyectos extremadamente grandes con muchos ficheros y muchos subproyectos, el rendimiento de sbt puede ser menos óptimo al tener que observar qué ficheros han cambiado en una sesión interactiva por tener que realizar muchas operaciones de E/S.
sbt posee las entradas trackingInternalDependencies
y exportToInternal
.
Éstas pueden ser utilizadas para controlar si la compilación de subproyectos
dependientes ha de ser lanzada automáticamente o no cuando se llama a compile
.
Ambas claves pueden tomar uno de estos tres valores: TrackLevel.NoTracking
,
TrackLevel.TrackIfMissing
y TrackLevel.TrackAlways
. De forma predeterminada
ambas son establecidas a TrackLevel.TrackAlways
.
Cuando trackInternalDependencies
es establecido a TrackLevel.TrackIfMissing
,
sbt no volverá a intentar compilar dependencias internas (inter-proyecto)
automáticamente, a menos que no haya ficheros *.class
(o un fichero JAR
cuando exportJars
sea true
) en el directorio de salida.
Cuando la entrada es establecida a TrackLevel.NoTracking
la compilación de
dependencias internas es omitida. Fíjate en que el classpath aún sigue siendo anexado
y que el grafo de dependencias aún sigue mostrándolas como dependencias.
La razón es ahorrar el sobrecoste de E/S para observar cambios en una
construcción con muchos subproyectos durante el desarrollo. A continuación se
muestra cómo establecer todos los subproyectos a TrackIfMissing
.
lazy val root = (project in file(".")).
aggregate(....).
settings(
inThisBuild(Seq(
trackInternalDependencies := TrackLevel.TrackIfMissing,
exportJars := true
))
)
La entrada exportToInternal
permite al proyecto del cual se depende optar si
puede ser vigilado internamente o no, lo cual puede resultar útil si lo que se
quiere es hacer seguimiento de la mayoría de los subproyectos excepto unos
cuantos. La intersección de las entradas trackInternalDependencies
y
exportToInternal
será usada para determinar el nivel de seguimiento real.
A continuación se muestra un ejemplo de un proyecto optando de ser seguido o no:
lazy val dontTrackMe = (project in file("dontTrackMe")).
settings(
exportToInternal := TrackLevel.NoTracking
)
Si un proyecto no está definido para el directorio raíz en la construcción, sbt creará uno de forma predeterminada que agrega a los otros proyectos de la construcción.
Debido a que el proyecto hello-foo
ha sido definido con base = file("foo")
,
él estará contenido en el subdirectorio foo
. Sus fuentes pueden estar tanto en
foo
, como foo/Foo.scala
o en foo/src/main/scala
. La
estructura de directorios habitual se aplica a foo
a excepción
de los ficheros de definición de construcción.
En el prompt interactivo de sbt, escribe proyectos
para listar tus proyectos y
project <projectname>
para seleccionar el proyecto actual. Al ejecutar una
tarea como compile
ésta se ejecutará sobre el proyecto actual. Por eso no hay
por qué compilar el proyecto raíz necesariamente, es posible compilar solamente
un subproyecto.
Puedes ejecutar una tarea en otro proyecto especificando explícitamente el ID de
proyecto, como en subproyecto/compile
.
Las definiciones en los ficheros .sbt
no son visibles en otros ficheros
.sbt
. Para poder compartir código entre ficheros .sbt
hay que definir uno o
más ficheros de Scala en el directorio project/
en la construcción raíz.
Para más información ver Organizando la construcción.
Cualquier fichero .sbt
en foo
, por ejemplo foo/build.sbt
, será mezclado
con la definición de construcción para la construcción principal, pero con
ámbito del proyecto hello-foo
.
Si todo tu proyecto está en hello
, intenta definir una versión diferente
(version := "0.6"
) en hello/build.sbt
, hello/foo/build.sbt
, y
hello/bar/build.sbt
. Ahora show version
en el prompt interactivo de sbt
debería de tener este aspecto (respetando las versiones que hayas definido):
> show version
[info] hello-foo/*:version
[info] 0.7
[info] hello-bar/*:version
[info] 0.9
[info] hello/*:version
[info] 0.5
hello-foo/*:version
está definido en hello/foo/build.sbt
,
hello-bar/*:version
está definido en hello/bar/build.sbt
y
hello/*:version
está definido en hello/build.sbt
. Recuerda la
sintaxis para claves con ámbito. Cada clave version
está en el
ámbito de un proyecto, basado en la ubicación de build.sbt
. Pero los tres
build.sbt
forman parte de la misma definición de construcción.
Style choices:
*.sbt
files in the base directory of that project,
while the root build.sbt
declares only minimum project declarations in the form of lazy val foo = (project in file("foo"))
without the settings.
build.sbt
file
in order to keep all build definition under a single file. However, it up to you.
No puedes tener un subdirectorio de proyecto o ficheros project/*.scala
en los
subproyectos. foo/project/Build.scala
sería ignorado.