diff --git a/src/main/scala/bloop/integrations/gradle/BloopParameters.scala b/src/main/scala/bloop/integrations/gradle/BloopParameters.scala index e8eaa86d..b35df070 100644 --- a/src/main/scala/bloop/integrations/gradle/BloopParameters.scala +++ b/src/main/scala/bloop/integrations/gradle/BloopParameters.scala @@ -1,6 +1,7 @@ package bloop.integrations.gradle import java.io.File +import java.util.ArrayList import bloop.integrations.gradle.syntax._ @@ -9,6 +10,9 @@ import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.Optional +import scala.collection.JavaConverters._ +import org.gradle.api.provider.ListProperty + /** * Project extension to configure Bloop. * @@ -21,6 +25,7 @@ import org.gradle.api.tasks.Optional * stdLibName = "scala-library" // or "scala3-library_3" * includeSources = true * includeJavaDoc = false + * extendUserConfigurations = ["myCustomMainConfig"] * } * }}} */ @@ -58,6 +63,13 @@ case class BloopParametersExtension(project: Project) { @Input @Optional def getIncludeJavadoc: Property[java.lang.Boolean] = includeJavadoc_ + private val extendUserConfigurations_ : ListProperty[String] = + project.getObjects().listProperty(classOf[String]) + extendUserConfigurations_.set(new ArrayList[String]()) + + @Input @Optional def getExtendUserConfigurations: ListProperty[String] = + extendUserConfigurations_ + def createParameters: BloopParameters = { val defaultTargetDir = project.getRootProject.workspacePath.resolve(".bloop").toFile @@ -66,7 +78,8 @@ case class BloopParametersExtension(project: Project) { Option(compilerName_.getOrNull), Option(stdLibName_.getOrNull), includeSources_.get, - includeJavadoc_.get + includeJavadoc_.get, + extendUserConfigurations_.get.asScala.toList ) } } @@ -76,5 +89,6 @@ case class BloopParameters( compilerName: Option[String], stdLibName: Option[String], includeSources: Boolean, - includeJavadoc: Boolean + includeJavadoc: Boolean, + extendUserConfigurations: List[String] ) diff --git a/src/main/scala/bloop/integrations/gradle/BloopPlugin.scala b/src/main/scala/bloop/integrations/gradle/BloopPlugin.scala index d2c6d51a..9b2653a1 100644 --- a/src/main/scala/bloop/integrations/gradle/BloopPlugin.scala +++ b/src/main/scala/bloop/integrations/gradle/BloopPlugin.scala @@ -1,11 +1,14 @@ package bloop.integrations.gradle import bloop.integrations.gradle.syntax._ +import bloop.integrations.gradle.tasks.PluginUtils import bloop.integrations.gradle.tasks.BloopInstallTask import bloop.integrations.gradle.tasks.ConfigureBloopInstallTask +import scala.collection.JavaConverters._ import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.tasks.SourceSet import org.gradle.api.artifacts.Configuration /** @@ -27,21 +30,48 @@ final class BloopPlugin extends Plugin[Project] { s"Applying bloop plugin to project ${project.getName}", Seq.empty: _* ) + project.createExtension[BloopParametersExtension]("bloop", project) - val bloopConfig = project.getConfigurations().create("bloopConfig") - bloopConfig.setDescription( - "A configuration for Bloop to be able to export artifacts in all other configurations." - ) + project.afterEvaluate( + new org.gradle.api.Action[Project] { + def execute(project: Project): Unit = { - // Make this configuration not visbile in dependencyInsight reports - bloopConfig.setVisible(false) - // Allow this configuration to be resolved - bloopConfig.setCanBeResolved(true) - // This configuration is not meant to be consumed by other projects - bloopConfig.setCanBeConsumed(false) + val bloopParams = project.getExtension[BloopParametersExtension].createParameters + + if (PluginUtils.hasJavaScalaPlugin(project)) { + project.allSourceSets.foreach { sourceSet => + val bloopConfigName = generateBloopConfigName(sourceSet) + val bloopConfig = createBloopConfigForSourceSet(bloopConfigName, project) + val compatibleConfigNames = + findCompatibleConfigNamesFromSourceSet(sourceSet) ++ + bloopParams.extendUserConfigurations + + extendCompatibleConfigurationAfterEvaluate( + project, + bloopConfig, + compatibleConfigNames, + Set.empty + ) + } + } - extendCompatibleConfigurationAfterEvaluate(project, bloopConfig) + if (PluginUtils.hasAndroidPlugin(project)) { + // In the Android world, we don't have source sets for each variant, so instead + // we create an Android-specific configuration that we can use to resolve all + // relevant artifacts in all variants (the empty extend configs) + val bloopAndroidConfig = createBloopConfigForSourceSet("bloopAndroidConfig", project) + + extendCompatibleConfigurationAfterEvaluate( + project, + bloopAndroidConfig, + Set.empty, + incompatibleAndroidConfigurations + ) + } + } + } + ) // Creates two tasks: one to configure the plugin and the other one to generate the config files val configureBloopInstall = @@ -52,7 +82,19 @@ final class BloopPlugin extends Plugin[Project] { () } - private[this] val incompatibleConfigurations = Set[String]( + private def findCompatibleConfigNamesFromSourceSet(sourceSet: SourceSet): Set[String] = Set( + sourceSet.getApiConfigurationName(), + sourceSet.getImplementationConfigurationName(), + sourceSet.getCompileOnlyConfigurationName(), + // sourceSet.getCompileOnlyApiConfigurationName(), + sourceSet.getCompileClasspathConfigurationName(), + sourceSet.getRuntimeOnlyConfigurationName(), + sourceSet.getRuntimeClasspathConfigurationName(), + sourceSet.getRuntimeElementsConfigurationName(), + "scalaCompilerPlugins" + ) + + private[this] val incompatibleAndroidConfigurations = Set[String]( "incrementalScalaAnalysisElements", "incrementalScalaAnalysisFormain", "incrementalScalaAnalysisFortest", @@ -61,30 +103,50 @@ final class BloopPlugin extends Plugin[Project] { "zinc" ) - def extendCompatibleConfigurationAfterEvaluate( + private def createBloopConfigForSourceSet( + bloopConfigName: String, + project: Project + ): Configuration = { + val bloopConfig = project.getConfigurations().create(bloopConfigName) + bloopConfig.setDescription( + "A configuration for Bloop to be able to export artifacts in all other configurations." + ) + + // Make this configuration not visbile in dependencyInsight reports + bloopConfig.setVisible(false) + // Allow this configuration to be resolved + bloopConfig.setCanBeResolved(true) + // This configuration is not meant to be consumed by other projects + bloopConfig.setCanBeConsumed(false) + + bloopConfig + } + + /** + * Makes the input configuration extend valid compatible configurations. + * Note that if extendConfigNames is empty, all resolvable and non-whitelisted + * configurations will be extended automatically. + */ + private def extendCompatibleConfigurationAfterEvaluate( project: Project, - bloopConfig: Configuration + bloopSourceSetConfig: Configuration, + compatibleConfigNames: Set[String], + incompatibleConfigNames: Set[String] ): Unit = { // Use consumer instead of Scala closure because of Scala 2.11 compat val extendConfigurationIfCompatible = new java.util.function.Consumer[Configuration] { def accept(config: Configuration): Unit = { if ( - config != bloopConfig && config.isCanBeResolved && - !incompatibleConfigurations.contains(config.getName()) + config != bloopSourceSetConfig && + config.isCanBeResolved && + (compatibleConfigNames.isEmpty || compatibleConfigNames.contains(config.getName())) && + !incompatibleConfigNames.contains(config.getName()) ) { - bloopConfig.extendsFrom(config) + bloopSourceSetConfig.extendsFrom(config) } } } - // Important to do this after evaluation so Gradle can add all the necessary project configuration before we extend them - project.afterEvaluate( - // Use Action instead of Scala closure because of Scala 2.11 compat - new org.gradle.api.Action[Project] { - def execute(project: Project): Unit = { - project.getConfigurations.forEach(extendConfigurationIfCompatible) - } - } - ) + project.getConfigurations.forEach(extendConfigurationIfCompatible) } } diff --git a/src/main/scala/bloop/integrations/gradle/model/BloopConverter.scala b/src/main/scala/bloop/integrations/gradle/model/BloopConverter.scala index 9c986f94..35c554f0 100644 --- a/src/main/scala/bloop/integrations/gradle/model/BloopConverter.scala +++ b/src/main/scala/bloop/integrations/gradle/model/BloopConverter.scala @@ -191,22 +191,12 @@ class BloopConverter(parameters: BloopParameters) { targetDir ) - val bloopConfig = project.getConfiguration("bloopConfig") - val allArtifacts = getConfigurationArtifacts(bloopConfig) - - // get all configurations dependencies - these go into the resolutions as the user can create their own config dependencies (e.g. compiler plugin jar) - // some configs aren't allowed to be resolved - hence the catch - // this can bring too many artifacts into the resolution section (e.g. junit on main projects) but there's no way to know which artifact is required by which sourceset - // filter out internal scala plugin configurations - - // val allArtifacts2 = project.getConfigurations.asScala - // .filter(_.isCanBeResolved) - // .flatMap(getConfigurationArtifacts) - - // System.out.println(s""" - // |All artifacts (new): $allArtifacts - // |All artifacts (old): $allArtifacts2 - // """.stripMargin) + // We obtain all the artifacts in this configuration, which has been + // configured to extend all valid resolvable configurations to obtain the + // artifact jars present in all of the variants in an Android project + val bloopAndroidConfig = project.getConfiguration("bloopAndroidConfig") + assert(bloopAndroidConfig != null, "Missing bloopAndroidConfig!") + val allArtifacts = getConfigurationArtifacts(bloopAndroidConfig) val additionalModules = allArtifacts .filterNot(f => allOutputsToSourceSets.contains(f.getFile)) @@ -361,11 +351,12 @@ class BloopConverter(parameters: BloopParameters) { targetDir ) - // get all configurations dependencies - these go into the resolutions as the user can create their own config dependencies (e.g. compiler plugin jar) - // some configs aren't allowed to be resolved - hence the catch - // this can bring too many artifacts into the resolution section (e.g. junit on main projects) but there's no way to know which artifact is required by which sourceset - // filter out internal scala plugin configurations - val bloopConfig = project.getConfiguration("bloopConfig") + // Each source set has a bloop config name that extends the most important + // source set configurations to properly retrieve all relevant artifact jars + val bloopConfigName = generateBloopConfigName(sourceSet) + val bloopConfig = project.getConfiguration(bloopConfigName) + assert(bloopConfig != null, s"Missing $bloopConfigName configuration in project!") + val modules = getConfigurationArtifacts(bloopConfig) .filter(f => !allArchivesToSourceSets.contains(f.getFile) && diff --git a/src/main/scala/bloop/integrations/gradle/syntax.scala b/src/main/scala/bloop/integrations/gradle/syntax.scala index 7663a634..6a0ccebe 100644 --- a/src/main/scala/bloop/integrations/gradle/syntax.scala +++ b/src/main/scala/bloop/integrations/gradle/syntax.scala @@ -123,4 +123,12 @@ object syntax { implicit class FileExtension(file: File) { def /(child: String): File = new File(file, child) } + + def capitalize(x: String): String = Option(x) match { + case Some(s) if s.nonEmpty => s.head.toUpper + s.tail + case _ => x + } + + def generateBloopConfigName(sourceSet: SourceSet): String = + s"bloop${capitalize(sourceSet.getName())}Config" } diff --git a/src/test/scala/bloop/integrations/gradle/ConfigGenerationSuite.scala b/src/test/scala/bloop/integrations/gradle/ConfigGenerationSuite.scala index 88e7eb01..1b67f6a1 100644 --- a/src/test/scala/bloop/integrations/gradle/ConfigGenerationSuite.scala +++ b/src/test/scala/bloop/integrations/gradle/ConfigGenerationSuite.scala @@ -3632,7 +3632,10 @@ abstract class ConfigGenerationSuite extends BaseConfigSuite { | |configurations { | scalaCompilerPlugin - | bloopConfig.extendsFrom(scalaCompilerPlugin) + |} + | + |bloop { + | extendUserConfigurations = [configurations.scalaCompilerPlugin.name] |} | |dependencies {