Es bastante común que los proyectos de Scala proporcionen artefactos binarios compilados para varias versiones del compilador de Scala. Como regla general, con el fin de crear varias versiones de un artefacto en una comunidad, se acostumbra usar SBT, donde esta característica está lista para usar y se configura en un par de líneas. Pero, ¿qué pasa si queremos confundirnos y crear una compilación para compilación cruzada sin usar SBT?
Para uno de mis proyectos Java, decidí crear una fachada Scala. Históricamente, todo el proyecto se ensambla utilizando Gradle, y se decidió agregar la fachada al mismo proyecto que un submódulo. Gradle en su conjunto puede compilar módulos Scala con la advertencia de que no se ha declarado ningún soporte de compilación cruzada. Hay un boleto abierto en 2017 y un par de complementos ( 1 , 2 ) que prometen agregar esta característica a su proyecto, pero hay problemas con ellos, generalmente asociados con la publicación de artefactos. Y no hay nada más en general. Decidí comprobar lo difícil que es configurar realmente la compilación para la compilación cruzada sin complementos especiales y SMS.
Primero, describimos el resultado deseado. Me gustaría que el mismo conjunto de fuentes sea compilado por tres versiones del compilador Scala: 2.11, 2.12 y 2.13 (en este punto, el 2.13.0-RC2 más reciente). Y dado que Scala 2.13 tiene un montón de cambios incompatibles hacia atrás en las colecciones, me gustaría poder agregar conjuntos de fuentes adicionales para el código específico de cada uno de los compiladores. Nuevamente, en SBT, todo esto se agrega a un par de líneas de configuración. Veamos qué se puede hacer en Gradle.

La primera dificultad que uno tiene que enfrentar es que la versión del compilador se calcula a partir de la versión de la dependencia declarada en la biblioteca scala. Además, todas las dependencias que tienen un prefijo para la versión Scala del compilador también deben cambiarse. Es decir Para cada versión del compilador, la lista de dependencias debe ser diferente. Además, el conjunto de indicadores para diferentes versiones del compilador es realmente diferente. Algunas banderas cambiaron de nombre entre versiones, mientras que otras simplemente se marcaron como obsoletas o se eliminaron por completo. Decidí que tratar de captar todos los matices de diferentes compiladores en un archivo de compilación parece ser una tarea demasiado difícil y su soporte adicional aún más difícil. Por lo tanto, decidí explorar otras posibles formas de resolver este problema. Pero, ¿qué sucede si creamos varias configuraciones de configuraciones para la misma estructura de directorios del proyecto?
En la declaración para incluir submódulos en el proyecto Gradle, puede especificar el directorio donde se ubicará la raíz del submódulo y el nombre del archivo responsable de su configuración. Especifiquemos el mismo directorio para varias importaciones y creemos varias copias del script de compilación para cada versión del compilador.
settings.gradlerootProject.name = 'test' include 'java-library' include 'scala-facade_2.11' project(':scala-facade_2.11').with { projectDir = file('scala-facade') buildFileName = 'build-2.11.gradle' } include 'scala-facade_2.12' project(':scala-facade_2.12').with { projectDir = file('scala-facade') buildFileName = 'build-2.12.gradle' } include 'scala-facade_2.13' project(':scala-facade_2.13').with { projectDir = file('scala-facade') buildFileName = 'build-2.13.gradle' }
No está mal, pero de vez en cuando podemos obtener extraños errores de compilación relacionados con el hecho de que los tres scripts de compilación usan el mismo directorio de compilación. Podemos solucionar esto configurándolos para cada compilación:
build-2.12.gradle plugins { id 'scala' } buildDir = 'build-2.12' clean { delete 'build-2.12' } // ...
Ahora es muy hermoso. Con solo un problema, que tal compilación volverá loco a su IDE favorito, y lo más probable es que la edición posterior de su proyecto tenga que hacerse con instrumentos. Pensé que esto no es un gran problema, porque siempre puede comentar el exceso de importaciones de submódulos y convertir la compilación cruzada en una compilación regular, con la cual su IDE probablemente sepa cómo funcionar.
¿Qué pasa con los conjuntos de fuentes adicionales? Nuevamente, con archivos separados, resultó ser bastante simple, crear un nuevo directorio y configurarlo como un conjunto de origen.
build-2.12.gradle // ... sourceSets { compat { scala { srcDir 'src/main/scala-2.12-' } } main { scala { compileClasspath += compat.output } } test { scala { compileClasspath += compat.output runtimeClasspath += compat.output } } } // ...
build-2.13.gradle // ... sourceSets { compat { scala { srcDir 'src/main/scala-2.13+' } } main { scala { compileClasspath += compat.output } } test { scala { compileClasspath += compat.output runtimeClasspath += compat.output } } } // ...
La estructura final del proyecto se ve así:

Aquí también puede separar piezas comunes individuales en archivos de configuración externos e importarlas en la compilación para reducir el número de repeticiones. Pero para mí resultó bastante bien, declarativamente, aislado y compatible con todos los complementos posibles de Gradle.
En total, el problema se resolvió, la flexibilidad de Gradle fue suficiente para expresar una configuración bastante no trivial con elegancia, y Scala cross build es posible no solo usando SBT, y si por una razón u otra usas Gradle para construir un proyecto Scala, la compilación cruzada es una oportunidad para ti También disponible. Espero que esta publicación sea de utilidad. Gracias por su atencion