É bastante comum que os projetos Scala forneçam artefatos binários compilados para várias versões do compilador Scala. Como regra, com o objetivo de criar várias versões de um artefato em uma comunidade, é habitual usar o SBT, onde esse recurso está pronto para uso imediato e é configurado em algumas linhas. Mas e se quisermos ficar confusos e criar uma compilação para compilação cruzada sem usar o SBT?
Para um dos meus projetos Java, decidi criar uma fachada Scala. Historicamente, todo o projeto é montado usando Gradle, e foi decidido adicionar a fachada ao mesmo projeto que um submódulo. Gradle como um todo pode compilar os módulos Scala com a ressalva de que nenhum suporte de compilação cruzada foi declarado. Há um ticket aberto em 2017 e alguns plugins ( 1 , 2 ) que prometem adicionar esse recurso ao seu projeto, mas há problemas com eles, geralmente associados à publicação de artefatos. E não há mais nada em geral. Decidi verificar como é realmente difícil configurar a compilação para compilação cruzada sem plugins e SMS especiais.
Primeiro, descrevemos o resultado desejado. Eu gostaria que o mesmo conjunto de fontes fosse compilado por três versões do compilador Scala: 2.11, 2.12 e 2.13 (neste momento, o 2.13.0-RC2 mais atual). E como há muitas alterações incompatíveis com versões anteriores nas coleções no Scala 2.13, eu gostaria de poder adicionar conjuntos de fontes adicionais para o código específico de cada compilador. Novamente, no SBT, tudo isso é adicionado a algumas linhas de configuração. Vamos ver o que pode ser feito em Gradle.

A primeira dificuldade que você precisa enfrentar é que a versão do compilador é calculada a partir da versão da dependência declarada na scala-library. Além disso, todas as dependências que têm um prefixo para a versão Scala do compilador também precisam ser alteradas. I.e. Para cada versão do compilador, a lista de dependências deve ser diferente. Além disso, o conjunto de sinalizadores para diferentes versões do compilador é realmente diferente. Alguns sinalizadores foram renomeados entre versões, enquanto outros foram simplesmente marcados como obsoletos ou completamente removidos. Decidi que tentar capturar todas as nuances de diferentes compiladores em um arquivo de construção parece ser uma tarefa muito difícil e seu suporte adicional ainda mais difícil. Portanto, decidi explorar outras maneiras possíveis de resolver esse problema. Mas e se criarmos várias construções de configurações para a mesma estrutura de diretório do projeto?
Na declaração de inclusão de submódulos no projeto Gradle, é possível especificar o diretório em que a raiz do submódulo e o nome do arquivo responsável por sua configuração serão localizados. Vamos especificar o mesmo diretório para várias importações e criar várias cópias do script de construção para cada versão do 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' }
Nada mal, mas, de tempos em tempos, podemos obter erros estranhos de compilação relacionados ao fato de os três scripts de compilação usarem o mesmo diretório de compilação. Podemos corrigir isso, definindo-os para cada build:
build-2.12.gradle plugins { id 'scala' } buildDir = 'build-2.12' clean { delete 'build-2.12' } // ...
Agora está muito bonito. Com apenas um problema, que essa compilação deixará seu IDE favorito louco e, provavelmente, outras edições do seu projeto precisarão ser feitas usando instrumentos. Eu pensei que isso não é um grande problema, porque você sempre pode simplesmente comentar o excesso de importações de submódulos e transformar a compilação cruzada em uma compilação regular, com a qual o IDE provavelmente sabe trabalhar.
E os conjuntos de fontes adicionais? Novamente, com arquivos separados, isso acabou sendo bastante simples, crie um novo diretório e configure-o como um conjunto de fontes.
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 } } } // ...
A estrutura final do projeto é assim:

Aqui você também pode separar partes comuns individuais em arquivos de configuração externos e importá-los para a compilação, a fim de reduzir o número de repetições. Mas, para mim, ficou muito bem, declarativamente, isolado e compatível com todos os possíveis plugins Gradle.
No total, o problema foi resolvido, a flexibilidade de Gradle foi suficiente para expressar uma configuração não trivial de maneira bastante elegante, e a compilação cruzada do Scala é possível não apenas usando o SBT, e se por um motivo ou outro você usar o Gradle para criar um projeto do Scala, faça a compilação cruzada como uma oportunidade para você também disponível. Espero que alguém neste post seja útil. Obrigado pela atenção.