From 812bce1c2a1a57d825136eea8a34b30227220928 Mon Sep 17 00:00:00 2001 From: Maciej Gajek Date: Fri, 23 Aug 2024 18:48:28 +0200 Subject: [PATCH] Add ComposeBuild, correctly combine classpaths of modules that have dependencies, WIP - test still needs fixing --- .../scala/scala/build/bsp/BloopSession.scala | 22 +- .../src/main/scala/scala/build/bsp/Bsp.scala | 3 +- .../main/scala/scala/build/bsp/BspImpl.scala | 217 +++-------- .../scala/build/bsp/BuildServerProxy.scala | 4 +- .../buildtargets/ManagesBuildTargets.scala | 4 +- .../ManagesBuildTargetsImpl.scala | 4 +- .../scala/build/compose/ComposeBuild.scala | 354 +++++++++++------- .../scala/scala/build/compose/Inputs.scala | 2 +- .../scala/build/compose/InputsComposer.scala | 6 +- .../compose/InputsComposerTest.scala | 4 +- .../compose/InputsComposerUtils.scala | 4 +- .../scala/scala/cli/commands/bsp/Bsp.scala | 2 +- .../scala/scala/cli/commands/run/Run.scala | 2 +- .../cli/commands/setupide/SetupIde.scala | 28 +- .../cli/commands/shared/SharedOptions.scala | 38 +- .../cli/integration/BspTestDefinitions.scala | 7 +- .../compose/ComposeBspTestDefinitions.scala | 94 +++++ 17 files changed, 431 insertions(+), 364 deletions(-) rename modules/build/src/test/scala/scala/build/{input => }/compose/InputsComposerTest.scala (97%) rename modules/build/src/test/scala/scala/build/{input => }/compose/InputsComposerUtils.scala (93%) diff --git a/modules/build/src/main/scala/scala/build/bsp/BloopSession.scala b/modules/build/src/main/scala/scala/build/bsp/BloopSession.scala index 226b25f656..d038d021ca 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BloopSession.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BloopSession.scala @@ -3,17 +3,17 @@ package scala.build.bsp import com.swoval.files.PathWatchers import java.util.concurrent.atomic.AtomicReference -import scala.build.Build + import scala.build.compiler.BloopCompiler -import scala.build.compose.{Inputs, input as compose} import scala.build.input.{Module, OnDisk, SingleFile, Virtual} +import scala.build.{Build, compose} final class BloopSession( - val inputs: Inputs, - // val inputsHash: String, TODO Fix inputs hash comparing - val remoteServer: BloopCompiler, - val bspServer: BspServer, - val watcher: Build.Watcher + val inputs: compose.Inputs, + // val inputsHash: String, TODO Fix inputs hash comparing + val remoteServer: BloopCompiler, + val bspServer: BspServer, + val watcher: Build.Watcher ) { def resetDiagnostics(localClient: BspClient): Unit = for { module <- inputs.modules @@ -65,10 +65,10 @@ final class BloopSession( object BloopSession { def apply( - inputs: Inputs, - remoteServer: BloopCompiler, - bspServer: BspServer, - watcher: Build.Watcher + inputs: compose.Inputs, + remoteServer: BloopCompiler, + bspServer: BspServer, + watcher: Build.Watcher ): BloopSession = new BloopSession(inputs, remoteServer, bspServer, watcher) final class Reference { diff --git a/modules/build/src/main/scala/scala/build/bsp/Bsp.scala b/modules/build/src/main/scala/scala/build/bsp/Bsp.scala index 48359b1c02..e0b86588b1 100644 --- a/modules/build/src/main/scala/scala/build/bsp/Bsp.scala +++ b/modules/build/src/main/scala/scala/build/bsp/Bsp.scala @@ -2,8 +2,9 @@ package scala.build.bsp import java.io.{InputStream, OutputStream} +import scala.build.compose import scala.build.errors.BuildException -import scala.build.input.{Module, ScalaCliInvokeData, compose} +import scala.build.input.{Module, ScalaCliInvokeData} import scala.concurrent.Future trait Bsp { diff --git a/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala b/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala index 4e235c2c5e..380747ac67 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala @@ -10,12 +10,19 @@ import org.eclipse.lsp4j.jsonrpc.messages.ResponseError import java.io.{InputStream, OutputStream} import java.util.UUID import java.util.concurrent.{CompletableFuture, Executor} + import scala.build.EitherCps.{either, value} import scala.build.* import scala.build.bsp.buildtargets.{ManagesBuildTargets, ProjectName} import scala.build.compiler.BloopCompiler -import scala.build.compose.Inputs -import scala.build.errors.{BuildException, CompositeBuildException, Diagnostic, ParsingInputsException} +import scala.build.compose.{ComposeBuild, Inputs} +import scala.build.errors.{ + BuildException, + CompositeBuildException, + Diagnostic, + ParsingInputsException +} +import scala.build.input.{Module, ScalaCliInvokeData} import scala.build.internal.Constants import scala.build.options.{BuildOptions, Scope} import scala.collection.mutable.ListBuffer @@ -38,12 +45,12 @@ import scala.util.{Failure, Success} * the output stream of bytes */ final class BspImpl( - argsToInputs: Seq[String] => Either[BuildException, Inputs], - bspReloadableOptionsReference: BspReloadableOptions.Reference, - threads: BspThreads, - in: InputStream, - out: OutputStream, - actionableDiagnostics: Option[Boolean] + argsToInputs: Seq[String] => Either[BuildException, Inputs], + bspReloadableOptionsReference: BspReloadableOptions.Reference, + threads: BspThreads, + in: InputStream, + out: OutputStream, + actionableDiagnostics: Option[Boolean] )(using ScalaCliInvokeData) extends Bsp { import BspImpl.{ @@ -92,142 +99,18 @@ final class BspImpl( currentBloopSession: BloopSession, reloadableOptions: BspReloadableOptions, maybeRecoverOnError: ProjectName => BuildException => Option[BuildException] = _ => e => Some(e) - ): Either[(BuildException, ProjectName), PreBuildProject] = - either[(BuildException, ProjectName)] { - val logger = reloadableOptions.logger - val buildOptions = reloadableOptions.buildOptions - val verbosity = reloadableOptions.verbosity - logger.log("Preparing build") - - val persistentLogger = new PersistentDiagnosticLogger(logger) - val bspServer = currentBloopSession.bspServer - - val prebuildModules = for (module <- currentBloopSession.inputs.modulesBuildOrder) yield { - val mainProjectName = module.projectName - val testProjectName = module.scopeProjectName(Scope.Test) - - // allInputs contains elements from using directives - val (crossSources, allInputs) = value { - CrossSources.forModuleInputs( - inputs = module, - preprocessors = Sources.defaultPreprocessors( - buildOptions.archiveCache, - buildOptions.internal.javaClassNameVersionOpt, - () => buildOptions.javaHome().value.javaCommand - ), - logger = persistentLogger, - suppressWarningOptions = buildOptions.suppressWarningOptions, - exclude = buildOptions.internal.exclude, - maybeRecoverOnError = maybeRecoverOnError(mainProjectName) - ).left.map(_ -> mainProjectName) - } - - val sharedOptions = crossSources.sharedOptions(buildOptions) - - if (verbosity >= 3) - pprint.err.log(crossSources) - - val scopedSources = - value(crossSources.scopedSources(buildOptions).left.map(_ -> mainProjectName)) - - if (verbosity >= 3) - pprint.err.log(scopedSources) - - val sourcesMain = value { - scopedSources.sources(Scope.Main, sharedOptions, allInputs.workspace, persistentLogger) - .left.map(_ -> mainProjectName) - } - - val sourcesTest = value { - scopedSources.sources(Scope.Test, sharedOptions, allInputs.workspace, persistentLogger) - .left.map(_ -> testProjectName) - } - - if (verbosity >= 3) - pprint.err.log(sourcesMain) - - val options0Main = sourcesMain.buildOptions - val options0Test = sourcesTest.buildOptions.orElse(options0Main) - - val generatedSourcesMain = - sourcesMain.generateSources(allInputs.generatedSrcRoot(Scope.Main)) - val generatedSourcesTest = - sourcesTest.generateSources(allInputs.generatedSrcRoot(Scope.Test)) - - bspServer.setExtraDependencySources(options0Main.classPathOptions.extraSourceJars) - bspServer.setExtraTestDependencySources(options0Test.classPathOptions.extraSourceJars) - bspServer.setGeneratedSources(mainProjectName, generatedSourcesMain) - bspServer.setGeneratedSources(testProjectName, generatedSourcesTest) - - val (classesDir0Main, scalaParamsMain, artifactsMain, projectMain, buildChangedMain) = - value { - val res = Build.prepareBuild( - allInputs, - sourcesMain, - generatedSourcesMain, - options0Main, - None, - Scope.Main, - currentBloopSession.remoteServer, - persistentLogger, - localClient, - maybeRecoverOnError(mainProjectName) - ) - res.left.map(_ -> mainProjectName) - } - - val (classesDir0Test, scalaParamsTest, artifactsTest, projectTest, buildChangedTest) = - value { - val res = Build.prepareBuild( - allInputs, - sourcesTest, - generatedSourcesTest, - options0Test, - None, - Scope.Test, - currentBloopSession.remoteServer, - persistentLogger, - localClient, - maybeRecoverOnError(testProjectName) - ) - res.left.map(_ -> testProjectName) - } - - localClient.setGeneratedSources(mainProjectName, generatedSourcesMain) - localClient.setGeneratedSources(testProjectName, generatedSourcesTest) - - val mainScope = PreBuildData( - sourcesMain, - options0Main, - classesDir0Main, - scalaParamsMain, - artifactsMain, - projectMain, - generatedSourcesMain, - buildChangedMain - ) - - val testScope = PreBuildData( - sourcesTest, - options0Test, - classesDir0Test, - scalaParamsTest, - artifactsTest, - projectTest, - generatedSourcesTest, - buildChangedTest - ) - - if (actionableDiagnostics.getOrElse(true)) { - val projectOptions = options0Test.orElse(options0Main) - projectOptions.logActionableDiagnostics(persistentLogger) - } - - PreBuildModule(module, mainScope, testScope, persistentLogger.diagnostics) - } - - PreBuildProject(prebuildModules) - } + ): Either[(BuildException, ProjectName), ComposeBuild.PreBuildProject] = + ComposeBuild( + buildOptions = reloadableOptions.buildOptions, + inputs = currentBloopSession.inputs, + logger = reloadableOptions.logger, + compiler = currentBloopSession.remoteServer, + buildClient = localClient, + bspServer = Some(currentBloopSession.bspServer), + actionableDiagnostics = actionableDiagnostics, + verbosity = reloadableOptions.verbosity, + maybeRecoverOnError = maybeRecoverOnError + ).prepareBuild() private def buildE( currentBloopSession: BloopSession, @@ -236,7 +119,7 @@ final class BspImpl( ): Either[(BuildException, ProjectName), Unit] = { def doBuildOnce( moduleInputs: Module, - data: PreBuildData, + data: ComposeBuild.PreBuildData, scope: Scope ): Either[(BuildException, ProjectName), Build] = Build.buildOnce( @@ -254,7 +137,7 @@ final class BspImpl( either[(BuildException, ProjectName)] { val preBuild = value(prepareBuild(currentBloopSession, reloadableOptions)) for (preBuildModule <- preBuild.prebuildModules) do { - val moduleInputs = preBuildModule.inputs + val moduleInputs = preBuildModule.module // TODO notify only specific build target if ( notifyChanges && (preBuildModule.mainScope.buildChanged || preBuildModule.testScope.buildChanged) @@ -327,22 +210,7 @@ final class BspImpl( executor ) - preBuild.thenCompose { x => - Thread.sleep(1000) - x.foreach{ r => - r.prebuildModules.foreach{ m => - pprint.err.log(m.mainScope.project.projectName) - m.mainScope.sources.paths.foreach(path => pprint.err.log(os.read(path._1))) - m.mainScope.project.classPath.foreach(p => pprint.err.log(p.toString)) - } - r.prebuildModules.foreach { m => - pprint.err.log(m.testScope.project.projectName) - m.testScope.sources.paths.foreach(path => pprint.err.log(os.read(path._1))) - m.testScope.project.classPath.foreach(p => pprint.err.log(p.toString)) - } - } - CompletableFuture.supplyAsync(() => x, executor) - }.thenCompose { + preBuild.thenCompose { case Left((ex, projectName)) => val taskId = new b.TaskId(UUID.randomUUID().toString) @@ -379,14 +247,13 @@ final class BspImpl( new b.CompileResult(b.StatusCode.ERROR) ) case Right(params) => - println(params) for (targetId <- currentBloopSession.bspServer.targetIds) actualLocalClient.resetBuildExceptionDiagnostics(targetId) for { preBuildModule <- params.prebuildModules targetId <- currentBloopSession.bspServer - .targetProjectIdOpt(preBuildModule.inputs.projectName) + .targetProjectIdOpt(preBuildModule.module.projectName) .toSeq } do actualLocalClient.reportDiagnosticsForFiles( @@ -396,7 +263,7 @@ final class BspImpl( ) doCompile().thenCompose { res => - def doPostProcess(inputs: Module, data: PreBuildData, scope: Scope): Unit = + def doPostProcess(inputs: Module, data: ComposeBuild.PreBuildData, scope: Scope): Unit = for (sv <- data.project.scalaCompiler.map(_.scalaVersion)) Build.postProcess( data.generatedSources, @@ -413,7 +280,7 @@ final class BspImpl( CompletableFuture.supplyAsync( () => { for (preBuildModule <- params.prebuildModules) do { - val moduleInputs = preBuildModule.inputs + val moduleInputs = preBuildModule.module doPostProcess(moduleInputs, preBuildModule.mainScope, Scope.Main) doPostProcess(moduleInputs, preBuildModule.testScope, Scope.Test) } @@ -451,9 +318,9 @@ final class BspImpl( * a new [[BloopSession]] */ private def newBloopSession( - inputs: Inputs, - reloadableOptions: BspReloadableOptions, - presetIntelliJ: Boolean = false + inputs: Inputs, + reloadableOptions: BspReloadableOptions, + presetIntelliJ: Boolean = false ): BloopSession = { val logger = reloadableOptions.logger val buildOptions = reloadableOptions.buildOptions @@ -504,8 +371,8 @@ final class BspImpl( * change on subsequent workspace/reload requests) */ override def run( - initialInputs: Inputs, - initialBspOptions: BspReloadableOptions + initialInputs: Inputs, + initialBspOptions: BspReloadableOptions ): Future[Unit] = { val logger = initialBspOptions.logger val verbosity = initialBspOptions.verbosity @@ -607,10 +474,10 @@ final class BspImpl( * a future containing a valid workspace/reload response */ private def reloadBsp( - currentBloopSession: BloopSession, - previousInputs: Inputs, - newInputs: Inputs, - reloadableOptions: BspReloadableOptions + currentBloopSession: BloopSession, + previousInputs: Inputs, + newInputs: Inputs, + reloadableOptions: BspReloadableOptions ): CompletableFuture[AnyRef] = { val previousTargetIds = currentBloopSession.bspServer.targetIds val wasIntelliJ = currentBloopSession.bspServer.isIntelliJ diff --git a/modules/build/src/main/scala/scala/build/bsp/BuildServerProxy.scala b/modules/build/src/main/scala/scala/build/bsp/BuildServerProxy.scala index d55edb100c..f4bcc038a6 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BuildServerProxy.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BuildServerProxy.scala @@ -4,10 +4,10 @@ import ch.epfl.scala.bsp4j as b import java.util.concurrent.CompletableFuture -import scala.build.GeneratedSource import scala.build.bsp.buildtargets.{ManagesBuildTargets, ProjectName} -import scala.build.input.{Module, compose} +import scala.build.input.Module import scala.build.options.Scope +import scala.build.{GeneratedSource, compose} /** A wrapper for [[BspServer]], allowing to reload the workspace on the fly. * @param bspServer diff --git a/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargets.scala b/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargets.scala index 6158f491e8..819c9bc341 100644 --- a/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargets.scala +++ b/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargets.scala @@ -3,10 +3,10 @@ package scala.build.bsp.buildtargets import ch.epfl.scala.bsp4j.BuildTargetIdentifier import ch.epfl.scala.bsp4j as b -import scala.build.GeneratedSource -import scala.build.input.{Module, compose} +import scala.build.input.Module import scala.build.internal.Constants import scala.build.options.Scope +import scala.build.{GeneratedSource, compose} trait ManagesBuildTargets { def targetIds: List[b.BuildTargetIdentifier] diff --git a/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargetsImpl.scala b/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargetsImpl.scala index 23c8754df1..c9bd71dd98 100644 --- a/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargetsImpl.scala +++ b/modules/build/src/main/scala/scala/build/bsp/buildtargets/ManagesBuildTargetsImpl.scala @@ -2,12 +2,12 @@ package scala.build.bsp.buildtargets import ch.epfl.scala.bsp4j as b -import scala.build.GeneratedSource import scala.build.bsp.buildtargets.ManagesBuildTargets import scala.build.errors.{BuildException, WorkspaceError} -import scala.build.input.{Module, compose} +import scala.build.input.Module import scala.build.internal.Constants import scala.build.options.Scope +import scala.build.{GeneratedSource, compose} import scala.collection.mutable import scala.util.Try diff --git a/modules/build/src/main/scala/scala/build/compose/ComposeBuild.scala b/modules/build/src/main/scala/scala/build/compose/ComposeBuild.scala index c6b5812a46..4676ea7c0c 100644 --- a/modules/build/src/main/scala/scala/build/compose/ComposeBuild.scala +++ b/modules/build/src/main/scala/scala/build/compose/ComposeBuild.scala @@ -1,61 +1,70 @@ package scala.build.compose -import scala.build.{BloopBuildClient, CrossSources, Logger} +import dependency.ScalaParameters + +import java.nio.file.FileSystemException +import scala.build.* +import scala.build.EitherCps.{either, value} import scala.build.bsp.BspServer import scala.build.bsp.buildtargets.ProjectName -import scala.build.compose.input as compose -import scala.build.errors.BuildException -import scala.build.options.BuildOptions import scala.build.compiler.ScalaCompiler -import scala.build.{Artifacts, Project} -import scala.build.input.Module +import scala.build.errors.{BuildException, Diagnostic} +import scala.build.input.{Module, ScalaCliInvokeData} +import scala.build.options.{BuildOptions, MaybeScalaVersion, Scope} object ComposeBuild { - private final case class PreBuildData( - sources: Sources, - buildOptions: BuildOptions, - classesDir: os.Path, - scalaParams: Option[ScalaParameters], - artifacts: Artifacts, - project: Project, - generatedSources: Seq[GeneratedSource], - buildChanged: Boolean - ) - - private final case class PreBuildProject(prebuildModules: Seq[PreBuildModule]) - - private final case class PreBuildModule( - module: Module, - mainScope: PreBuildData, - testScope: PreBuildData, - diagnostics: Seq[Diagnostic] - ) + final case class PreBuildData( + sources: Sources, + buildOptions: BuildOptions, + classesDir: os.Path, + scalaParams: Option[ScalaParameters], + artifacts: Artifacts, + project: Project, + generatedSources: Seq[GeneratedSource], + buildChanged: Boolean = false + ) + + final case class PreBuildProject(prebuildModules: Seq[PreBuildModule]) + + final case class PreBuildModule( + module: Module, + mainScope: PreBuildData, + testScope: PreBuildData, + diagnostics: Seq[Diagnostic] + ) } case class ComposeBuild( - buildOptions: BuildOptions, - inputs: Inputs, - logger: Logger, - compiler: ScalaCompiler, - buildClient: BloopBuildClient, - bspServer: Option[BspServer], - verbosity: Int = 0, - maybeRecoverOnError: ProjectName => BuildException => Option[BuildException] = _ => e => Some(e) - ) { + buildOptions: BuildOptions, + inputs: Inputs, + logger: Logger, + compiler: ScalaCompiler, + buildClient: BloopBuildClient, + bspServer: Option[BspServer], + actionableDiagnostics: Option[Boolean], + verbosity: Int = 0, + maybeRecoverOnError: ProjectName => BuildException => Option[BuildException] = _ => e => Some(e) +)(using ScalaCliInvokeData) { import ComposeBuild.* /** Prepares the build for all modules in the inputs, */ - def prepareBuild(): Either[(BuildException, ProjectName), PreBuildProject] = either { + def prepareBuild(): Either[(BuildException, ProjectName), PreBuildProject] = inputs match + case composeInputs: ComposedInputs=> prepareComposeBuild(composeInputs) + case SimpleInputs(singleModule) => + for (singlePreBuildModule <- prepareModule(singleModule)) + yield PreBuildProject(Seq(singlePreBuildModule)) + + private def prepareComposeBuild(composeInputs: ComposedInputs): Either[(BuildException, ProjectName), PreBuildProject] = either { logger.log("Preparing composed build") - val prebuildModules: Seq[PreBuildModule] = for (module <- inputs.modulesBuildOrder) yield { - value(prepareModule(module)) - } + val prebuildModules: Seq[PreBuildModule] = + for (module <- inputs.modulesBuildOrder) yield value(prepareModule(module)) val prebuildModulesWithLinkedDeps = { - val preBuildDataMap: Map[ProjectName, PreBuildModule] = prebuildModules.map(m => m.module.projectName -> m) + val preBuildDataMap: Map[ProjectName, PreBuildModule] = + prebuildModules.map(m => m.module.projectName -> m).toMap prebuildModules.map { prebuildModule => val additionalMainClassPath = prebuildModule.module.moduleDependencies @@ -66,17 +75,25 @@ case class ComposeBuild( classPath = (oldMainProject.classPath.toSet ++ additionalMainClassPath).toSeq ) + val mainProjectChanged = writeProject(newMainProject) + + pprint.err.log(oldMainProject.classPath) + pprint.err.log(newMainProject.classPath) + val additionalTestClassPath = prebuildModule.module.moduleDependencies .map(preBuildDataMap) .flatMap(_.testScope.project.classPath) val oldTestProject = prebuildModule.testScope.project val newTestProject = oldTestProject.copy( - classPath = (oldTestProject.classPath.toSet ++ additionalMainClassPath ++ additionalTestClassPath).toSeq + classPath = + (oldTestProject.classPath.toSet ++ additionalMainClassPath ++ additionalTestClassPath).toSeq ) + val testProjectChanged = writeProject(newTestProject) + prebuildModule.copy( - mainScope = prebuildModule.mainScope.copy(project = newMainProject), - testScope = prebuildModule.mainScope.copy(project = newTestProject) + mainScope = prebuildModule.mainScope.copy(project = newMainProject, buildChanged = mainProjectChanged), + testScope = prebuildModule.testScope.copy(project = newTestProject, buildChanged = testProjectChanged) ) } } @@ -84,78 +101,81 @@ case class ComposeBuild( PreBuildProject(prebuildModulesWithLinkedDeps) } - def prepareModule(module: Module): Either[(BuildException, ProjectName), PreBuildModule] = either { - val mainProjectName = module.projectName - val testProjectName = module.scopeProjectName(Scope.Test) - - // allInputs contains elements from using directives - val (crossSources, allInputs) = value { - CrossSources.forModuleInputs( - inputs = module, - preprocessors = Sources.defaultPreprocessors( - buildOptions.archiveCache, - buildOptions.internal.javaClassNameVersionOpt, - () => buildOptions.javaHome().value.javaCommand - ), - logger = logger, - suppressWarningOptions = buildOptions.suppressWarningOptions, - exclude = buildOptions.internal.exclude, - maybeRecoverOnError = maybeRecoverOnError(mainProjectName) - ).left.map(_ -> mainProjectName) - } + private def prepareModule(module: Module): Either[(BuildException, ProjectName), PreBuildModule] = + either { + val persistentLogger = new PersistentDiagnosticLogger(logger) + val mainProjectName = module.projectName + val testProjectName = module.scopeProjectName(Scope.Test) + + // allInputs contains elements from using directives + val (crossSources, allInputs) = value { + CrossSources.forModuleInputs( + inputs = module, + preprocessors = Sources.defaultPreprocessors( + buildOptions.archiveCache, + buildOptions.internal.javaClassNameVersionOpt, + () => buildOptions.javaHome().value.javaCommand + ), + logger = persistentLogger, + suppressWarningOptions = buildOptions.suppressWarningOptions, + exclude = buildOptions.internal.exclude, + maybeRecoverOnError = maybeRecoverOnError(mainProjectName) + ).left.map(_ -> mainProjectName) + } - val sharedOptions = crossSources.sharedOptions(buildOptions) + val sharedOptions = crossSources.sharedOptions(buildOptions) - if (verbosity >= 4) - pprint.err.log(crossSources) + if (verbosity >= 4) + pprint.err.log(crossSources) - val scopedSources = - value(crossSources.scopedSources(buildOptions).left.map(_ -> mainProjectName)) + val scopedSources = + value(crossSources.scopedSources(buildOptions).left.map(_ -> mainProjectName)) - if (verbosity >= 4) - pprint.err.log(scopedSources) + if (verbosity >= 4) + pprint.err.log(scopedSources) - val sourcesMain = value { - scopedSources.sources(Scope.Main, sharedOptions, allInputs.workspace, persistentLogger) - .left.map(_ -> mainProjectName) - } + val sourcesMain = value { + scopedSources.sources(Scope.Main, sharedOptions, allInputs.workspace, persistentLogger) + .left.map(_ -> mainProjectName) + } - val sourcesTest = value { - scopedSources.sources(Scope.Test, sharedOptions, allInputs.workspace, persistentLogger) - .left.map(_ -> testProjectName) - } + val sourcesTest = value { + scopedSources.sources(Scope.Test, sharedOptions, allInputs.workspace, persistentLogger) + .left.map(_ -> testProjectName) + } - if (verbosity >= 4) - pprint.err.log(sourcesMain) + if (verbosity >= 4) + pprint.err.log(sourcesMain) - val options0Main = sourcesMain.buildOptions - val options0Test = sourcesTest.buildOptions.orElse(options0Main) + val options0Main = sourcesMain.buildOptions + val options0Test = sourcesTest.buildOptions.orElse(options0Main) - val generatedSourcesMain = - sourcesMain.generateSources(allInputs.generatedSrcRoot(Scope.Main)) - val generatedSourcesTest = - sourcesTest.generateSources(allInputs.generatedSrcRoot(Scope.Test)) + val generatedSourcesMain = + sourcesMain.generateSources(allInputs.generatedSrcRoot(Scope.Main)) + val generatedSourcesTest = + sourcesTest.generateSources(allInputs.generatedSrcRoot(Scope.Test)) - // Notify the Bsp server (if there is any) about changes to the project params - bspServer.foreach(_.setExtraDependencySources(options0Main.classPathOptions.extraSourceJars)) - bspServer.foreach(_.setExtraTestDependencySources(options0Test.classPathOptions.extraSourceJars)) - bspServer.foreach(_.setGeneratedSources(mainProjectName, generatedSourcesMain)) - bspServer.foreach(_.setGeneratedSources(testProjectName, generatedSourcesTest)) + // Notify the Bsp server (if there is any) about changes to the project params + bspServer.foreach(_.setExtraDependencySources(options0Main.classPathOptions.extraSourceJars)) + bspServer.foreach( + _.setExtraTestDependencySources(options0Test.classPathOptions.extraSourceJars) + ) + bspServer.foreach(_.setGeneratedSources(mainProjectName, generatedSourcesMain)) + bspServer.foreach(_.setGeneratedSources(testProjectName, generatedSourcesTest)) - // Notify the build client about generated sources so that it can modify diagnostics coming to the remote client e.g. IDE or console (not really a client, but you get it) - buildClient.setGeneratedSources(mainProjectName, generatedSourcesMain) - buildClient.setGeneratedSources(testProjectName, generatedSourcesTest) + // Notify the build client about generated sources so that it can modify diagnostics coming to the remote client e.g. IDE or console (not really a client, but you get it) + buildClient.setGeneratedSources(mainProjectName, generatedSourcesMain) + buildClient.setGeneratedSources(testProjectName, generatedSourcesTest) - val (classesDir0Main, scalaParamsMain, artifactsMain, projectMain, buildChangedMain) = - value { - val res = Build.prepareBuild( + val (classesDir0Main, scalaParamsMain, artifactsMain, projectMain) = value { + val res = prepareProject( allInputs, sourcesMain, generatedSourcesMain, options0Main, None, Scope.Main, - currentBloopSession.remoteServer, + compiler, persistentLogger, buildClient, maybeRecoverOnError(mainProjectName) @@ -163,16 +183,15 @@ case class ComposeBuild( res.left.map(_ -> mainProjectName) } - val (classesDir0Test, scalaParamsTest, artifactsTest, projectTest, buildChangedTest) = - value { - val res = Build.prepareBuild( + val (classesDir0Test, scalaParamsTest, artifactsTest, projectTest) = value { + val res = prepareProject( allInputs, sourcesTest, generatedSourcesTest, options0Test, None, Scope.Test, - currentBloopSession.remoteServer, + compiler, persistentLogger, buildClient, maybeRecoverOnError(testProjectName) @@ -180,34 +199,117 @@ case class ComposeBuild( res.left.map(_ -> testProjectName) } - val mainScope = PreBuildData( - sourcesMain, - options0Main, - classesDir0Main, - scalaParamsMain, - artifactsMain, - projectMain, - generatedSourcesMain, - buildChangedMain - ) - - val testScope = PreBuildData( - sourcesTest, - options0Test, - classesDir0Test, - scalaParamsTest, - artifactsTest, - projectTest, - generatedSourcesTest, - buildChangedTest - ) - - if (actionableDiagnostics.getOrElse(true)) { - val projectOptions = options0Test.orElse(options0Main) - projectOptions.logActionableDiagnostics(persistentLogger) + val mainScope = PreBuildData( + sourcesMain, + options0Main, + classesDir0Main, + scalaParamsMain, + artifactsMain, + projectMain, + generatedSourcesMain + ) + + val testScope = PreBuildData( + sourcesTest, + options0Test, + classesDir0Test, + scalaParamsTest, + artifactsTest, + projectTest, + generatedSourcesTest + ) + + if (actionableDiagnostics.getOrElse(true)) { + val projectOptions = options0Test.orElse(options0Main) + projectOptions.logActionableDiagnostics(persistentLogger) + } + + PreBuildModule(module, mainScope, testScope, persistentLogger.diagnostics) + } + + //FIXME It's a copied part of Build.prepareBuild() + private def prepareProject( + inputs: Module, + sources: Sources, + generatedSources: Seq[GeneratedSource], + options: BuildOptions, + compilerJvmVersionOpt: Option[Positioned[Int]], + scope: Scope, + compiler: ScalaCompiler, + logger: Logger, + buildClient: BloopBuildClient, + maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e) + ): Either[BuildException, (os.Path, Option[ScalaParameters], Artifacts, Project)] = either { + + val options0 = + // FIXME: don't add Scala to pure Java test builds (need to add pure Java test runner) + if (sources.hasJava && !sources.hasScala && scope != Scope.Test) + options.copy( + scalaOptions = options.scalaOptions.copy( + scalaVersion = options.scalaOptions.scalaVersion.orElse { + Some(MaybeScalaVersion.none) + } + ) + ) + else + options + val params = value(options0.scalaParams) + + val scopeParams = + if (scope == Scope.Main) Nil + else Seq(scope.name) + + buildClient.setProjectParams(scopeParams ++ value(options0.projectParams)) + + val classesDir0 = Build.classesDir(inputs.workspace, inputs.projectName, scope) + + val artifacts = value(options0.artifacts(logger, scope, maybeRecoverOnError)) + + value(Build.validate(logger, options0)) + + val project = value { + Build.buildProject( + inputs, + sources, + generatedSources, + options0, + compilerJvmVersionOpt, + scope, + logger, + artifacts, + maybeRecoverOnError + ) } - PreBuildModule(module, mainScope, testScope, persistentLogger.diagnostics) + (classesDir0, params, artifacts, project) + } + + //FIXME It's a copied part of Build.prepareBuild() + private def writeProject(project: Project): Boolean = { + val projectChanged = compiler.prepareProject(project, logger) + + if (projectChanged) { + if (compiler.usesClassDir && os.isDir(project.classesDir)) { + logger.debug(s"Clearing ${project.classesDir}") + os.list(project.classesDir).foreach { p => + logger.debug(s"Removing $p") + try os.remove.all(p) + catch { + case ex: FileSystemException => + logger.debug(s"Ignoring $ex while cleaning up $p") + } + } + } + if (os.exists(project.argsFilePath)) { + logger.debug(s"Removing ${project.argsFilePath}") + try os.remove(project.argsFilePath) + catch { + case ex: FileSystemException => + logger.debug(s"Ignoring $ex while cleaning up ${project.argsFilePath}") + } + } + } + projectChanged } } diff --git a/modules/build/src/main/scala/scala/build/compose/Inputs.scala b/modules/build/src/main/scala/scala/build/compose/Inputs.scala index 65a4052d90..45bedc1dce 100644 --- a/modules/build/src/main/scala/scala/build/compose/Inputs.scala +++ b/modules/build/src/main/scala/scala/build/compose/Inputs.scala @@ -73,7 +73,7 @@ case class ComposedInputs( } def buildOrderForModule(module: Module): Seq[Module] = { - buildOrderForModule(module, Set.empty).map(nameMap) + val buildOrderWithTarget = buildOrderForModule(module, Set.empty).map(nameMap) buildOrderWithTarget.dropRight(1) } diff --git a/modules/build/src/main/scala/scala/build/compose/InputsComposer.scala b/modules/build/src/main/scala/scala/build/compose/InputsComposer.scala index b2640caec3..4467ad74c1 100644 --- a/modules/build/src/main/scala/scala/build/compose/InputsComposer.scala +++ b/modules/build/src/main/scala/scala/build/compose/InputsComposer.scala @@ -45,20 +45,20 @@ object InputsComposer { } yield fromArgs.orElse(fromCwd) } - private[input] object Keys { + private[compose] object Keys { val modules = "modules" val roots = "roots" val dependsOn = "dependsOn" } - private[input] case class ModuleDefinition( + private[compose] case class ModuleDefinition( name: String, roots: Seq[String], dependsOn: Seq[String] = Nil ) // TODO Check for module dependencies that do not exist - private[input] def readAllModules(modules: Option[Value]) + private[compose] def readAllModules(modules: Option[Value]) : Either[BuildException, Seq[ModuleDefinition]] = modules match { case Some(Tbl(values)) => EitherSequence.sequence { values.toSeq.map(readModule) diff --git a/modules/build/src/test/scala/scala/build/input/compose/InputsComposerTest.scala b/modules/build/src/test/scala/scala/build/compose/InputsComposerTest.scala similarity index 97% rename from modules/build/src/test/scala/scala/build/input/compose/InputsComposerTest.scala rename to modules/build/src/test/scala/scala/build/compose/InputsComposerTest.scala index 8d1bcf3168..fb4c9189c1 100644 --- a/modules/build/src/test/scala/scala/build/input/compose/InputsComposerTest.scala +++ b/modules/build/src/test/scala/scala/build/compose/InputsComposerTest.scala @@ -1,10 +1,10 @@ -package scala.build.input.compose +package scala.build.compose import scala.build.Build import scala.build.bsp.buildtargets.ProjectName +import scala.build.compose.InputsComposer import scala.build.errors.BuildException import scala.build.input.Module -import scala.build.input.compose.InputsComposer import scala.build.internal.Constants import scala.build.options.BuildOptions import scala.build.tests.{TestInputs, TestUtil} diff --git a/modules/build/src/test/scala/scala/build/input/compose/InputsComposerUtils.scala b/modules/build/src/test/scala/scala/build/compose/InputsComposerUtils.scala similarity index 93% rename from modules/build/src/test/scala/scala/build/input/compose/InputsComposerUtils.scala rename to modules/build/src/test/scala/scala/build/compose/InputsComposerUtils.scala index 0d0ce51532..1f953cb6be 100644 --- a/modules/build/src/test/scala/scala/build/input/compose/InputsComposerUtils.scala +++ b/modules/build/src/test/scala/scala/build/compose/InputsComposerUtils.scala @@ -1,10 +1,10 @@ -package scala.build.input.compose +package scala.build.compose import scala.build.Build -import scala.build.options.BuildOptions import scala.build.bsp.buildtargets.ProjectName import scala.build.errors.BuildException import scala.build.input.Module +import scala.build.options.BuildOptions object InputsComposerUtils { def argsToEmptyModules( diff --git a/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala b/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala index 90f614a82f..5bd886b446 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala @@ -8,7 +8,7 @@ import scala.build.EitherCps.{either, value} import scala.build.* import scala.build.bsp.{BspReloadableOptions, BspThreads} import scala.build.errors.BuildException -import scala.build.input.{Module, compose} +import scala.build.input.Module import scala.build.internals.EnvVar import scala.build.options.{BuildOptions, Scope} import scala.cli.commands.ScalaCommand diff --git a/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala b/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala index 60346cd9bf..5e6393e6d8 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala @@ -11,8 +11,8 @@ import java.util.concurrent.atomic.AtomicReference import scala.build.EitherCps.{either, value} import scala.build.* +import scala.build.compose.{ComposedInputs, SimpleInputs} import scala.build.errors.{BuildException, InputsException} -import scala.build.input.compose.{ComposedInputs, SimpleInputs} import scala.build.input.{Module, ScalaCliInvokeData, SubCommand} import scala.build.internal.{Constants, Runner, ScalaJsLinkerConfig} import scala.build.internals.ConsoleUtils.ScalaCliConsole diff --git a/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala b/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala index 7509633a2e..388210c078 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala @@ -7,13 +7,13 @@ import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker import com.google.gson.GsonBuilder import java.nio.charset.{Charset, StandardCharsets} + import scala.build.EitherCps.{either, value} import scala.build.* import scala.build.bsp.IdeInputs -import scala.build.compose.Inputs +import scala.build.compose.{ComposedInputs, Inputs, InputsComposer, SimpleInputs} import scala.build.errors.{BuildException, WorkspaceError} -import scala.build.input.compose.{ComposedInputs, InputsComposer, SimpleInputs} -import scala.build.input.{Module, OnDisk, Virtual, WorkspaceOrigin, compose} +import scala.build.input.{Module, OnDisk, Virtual, WorkspaceOrigin} import scala.build.internal.Constants import scala.build.internals.EnvVar import scala.build.options.{BuildOptions, Scope} @@ -84,12 +84,12 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] { } def runSafe( - options: SharedOptions, - inputs: Inputs, - logger: Logger, - buildOptions: BuildOptions, - previousCommandName: Option[String], - args: Seq[String] + options: SharedOptions, + inputs: Inputs, + logger: Logger, + buildOptions: BuildOptions, + previousCommandName: Option[String], + args: Seq[String] ): Unit = writeBspConfiguration( SetupIdeOptions(shared = options), @@ -126,11 +126,11 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] { override def sharedOptions(options: SetupIdeOptions): Option[SharedOptions] = Some(options.shared) private def writeBspConfiguration( - options: SetupIdeOptions, - inputs: Inputs, - buildOptions: BuildOptions, - previousCommandName: Option[String], - args: Seq[String] + options: SetupIdeOptions, + inputs: Inputs, + buildOptions: BuildOptions, + previousCommandName: Option[String], + args: Seq[String] ): Either[BuildException, Option[os.Path]] = either { val virtualInputs = inputs.modules.flatMap(_.elements).collect { diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala index 4d27c2f774..a1c6a8e478 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala @@ -16,16 +16,15 @@ import dependency.parser.DependencyParser import java.io.{File, InputStream} import java.nio.file.Paths import java.util.concurrent.atomic.AtomicBoolean + import scala.build.EitherCps.{either, value} import scala.build.Ops.EitherOptOps -import scala.build.* import scala.build.bsp.buildtargets.ProjectName import scala.build.compiler.{BloopCompilerMaker, ScalaCompilerMaker, SimpleScalaCompilerMaker} -import scala.build.compose.Inputs +import scala.build.compose.{Inputs, InputsComposer} import scala.build.directives.DirectiveDescription import scala.build.errors.{AmbiguousPlatformError, BuildException, ConfigDbException, Severity} -import scala.build.input.compose.InputsComposer -import scala.build.input.{Element, Module, ResourceDirectory, ScalaCliInvokeData, compose} +import scala.build.input.{Element, Module, ResourceDirectory, ScalaCliInvokeData} import scala.build.interactive.Interactive import scala.build.interactive.Interactive.{InteractiveAsk, InteractiveNop} import scala.build.internal.util.WarningMessages @@ -36,10 +35,17 @@ import scala.build.options.{BuildOptions, ComputeVersion, Platform, ScalacOpt, S import scala.build.preprocessing.directives.ClasspathUtils.* import scala.build.preprocessing.directives.Toolkit.maxScalaNativeWarningMsg import scala.build.preprocessing.directives.{Python, Toolkit} -import scala.build.options as bo +import scala.build.{compose, options as bo, *} import scala.cli.ScalaCli import scala.cli.commands.publish.ConfigUtil.* -import scala.cli.commands.shared.{HasGlobalOptions, ScalaJsOptions, ScalaNativeOptions, SharedOptions, SourceGeneratorOptions, SuppressWarningOptions} +import scala.cli.commands.shared.{ + HasGlobalOptions, + ScalaJsOptions, + ScalaNativeOptions, + SharedOptions, + SourceGeneratorOptions, + SuppressWarningOptions +} import scala.cli.commands.tags import scala.cli.commands.util.JvmUtils import scala.cli.commands.util.ScalacOptionsUtil.* @@ -228,11 +234,11 @@ final case class SharedOptions( override def global: GlobalOptions = GlobalOptions(logging = logging, globalSuppress = suppress.global, powerOptions = powerOptions) - private def scalaJsOptions(opts: ScalaJsOptions): options.ScalaJsOptions = { - import opts._ - options.ScalaJsOptions( + private def scalaJsOptions(opts: ScalaJsOptions): bo.ScalaJsOptions = { + import opts.* + bo.ScalaJsOptions( version = jsVersion, - mode = options.ScalaJsMode(jsMode), + mode = bo.ScalaJsMode(jsMode), moduleKindStr = jsModuleKind, checkIr = jsCheckIr, emitSourceMaps = jsEmitSourceMaps, @@ -250,9 +256,9 @@ final case class SharedOptions( ) } - private def linkerOptions(opts: ScalaJsOptions): options.scalajs.ScalaJsLinkerOptions = { - import opts._ - options.scalajs.ScalaJsLinkerOptions( + private def linkerOptions(opts: ScalaJsOptions): bo.scalajs.ScalaJsLinkerOptions = { + import opts.* + bo.scalajs.ScalaJsLinkerOptions( linkerPath = jsLinkerPath .filter(_.trim.nonEmpty) .map(os.Path(_, Os.pwd)), @@ -269,9 +275,9 @@ final case class SharedOptions( private def scalaNativeOptions( opts: ScalaNativeOptions, maxDefaultScalaNativeVersions: List[(String, String)] - ): options.ScalaNativeOptions = { - import opts._ - options.ScalaNativeOptions( + ): bo.ScalaNativeOptions = { + import opts.* + bo.ScalaNativeOptions( version = nativeVersion, modeStr = nativeMode, ltoStr = nativeLto, diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index af75903945..aed3aeb6ae 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1,15 +1,12 @@ package scala.cli.integration -import ch.epfl.scala.bsp4j.{BuildTargetEvent, JvmTestEnvironmentParams} import ch.epfl.scala.bsp4j as b +import ch.epfl.scala.bsp4j.{BuildTargetEvent, JvmTestEnvironmentParams} import com.eed3si9n.expecty.Expecty.expect import com.google.gson.{Gson, JsonElement} import java.net.URI import java.nio.file.Paths -import java.util.concurrent.{ExecutorService, ScheduledExecutorService} - -import scala.annotation.tailrec import scala.async.Async.{async, await} import scala.cli.integration.compose.ComposeBspTestDefinitions import scala.concurrent.ExecutionContext.Implicits.global @@ -805,7 +802,7 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg .take(if (actualScalaVersion.startsWith("3")) 1 else 2) .mkString(".") - withBsp(inputs, Seq(".")) { (root, localClient, remoteServer) => + withBsp(inputs, Seq(".", "-v", "-v", "-v")) { (root, localClient, remoteServer) => async { val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala) val target = { diff --git a/modules/integration/src/test/scala/scala/cli/integration/compose/ComposeBspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/compose/ComposeBspTestDefinitions.scala index 8391c2dcde..73af94f46d 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/compose/ComposeBspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/compose/ComposeBspTestDefinitions.scala @@ -195,6 +195,100 @@ trait ComposeBspTestDefinitions extends ScalaCliSuite { _: BspTestDefinitions => } } + test("composed bsp modules should share classpath of modules they depend on") { + val testInputs = TestInputs( + os.rel / Constants.moduleConfigFileName -> + """[modules.core] + |dependsOn = ["utils"] + | + |[modules.utils] + |roots = ["Utils.scala", "Utils2.scala"] + |""".stripMargin, + os.rel / "core" / "Core.scala" -> + """//> using dep com.lihaoyi::pprint:0.6.6 + | + |object Core extends App { + | pprint.println(Utils.util) + | pprint.println(Utils2.util) + | pprint.println(os.pwd) + |} + |""".stripMargin, + os.rel / "Utils.scala" -> + """//> using dep com.lihaoyi::os-lib:0.9.1 + |object Utils { def util: String = os.pwd.baseName}""".stripMargin, + os.rel / "Utils2.scala" -> "object Utils2 { def util: String = \"util2\"}" + ) + + val actualScalaMajorVersion = actualScalaVersion.split("\\.") + .take(if (actualScalaVersion.startsWith("3")) 1 else 2) + .mkString(".") + + withBsp(testInputs, Seq("--power", ".")) { (root, _, remoteServer) => + async { + val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala) + val coreTarget = { + val targets = buildTargetsResp.getTargets.asScala.map(_.getId).toSeq + expect(targets.length == 4) + expect(extractMainTargetsOfModules(targets).size == 2) + expect(extractTestTargetsOfModules(targets).size == 2) + extractMainTargets(targets.filter(_.getUri.contains("core"))) + } + val utilsTarget = { + val targets = buildTargetsResp.getTargets.asScala.map(_.getId).toSeq + expect(targets.length == 4) + expect(extractMainTargetsOfModules(targets).size == 2) + expect(extractTestTargetsOfModules(targets).size == 2) + extractMainTargets(targets.filter(_.getUri.contains("utils"))) + } + + val coreTargetUri = TestUtil.normalizeUri(coreTarget.getUri) + checkTargetUri(root, coreTargetUri) + + val utilsTargetUri = TestUtil.normalizeUri(utilsTarget.getUri) + checkTargetUri(root, utilsTargetUri) + + { + val resp = await { + remoteServer + .buildTargetDependencySources(new b.DependencySourcesParams(List(utilsTarget).asJava)) + .asScala + } + val foundTargets = resp.getItems.asScala.map(_.getTarget.getUri).toSeq + expect(foundTargets == Seq(utilsTargetUri)) + val foundDepSources = resp.getItems.asScala + .flatMap(_.getSources.asScala) + .toSeq + .map { uri => + val idx = uri.lastIndexOf('/') + uri.drop(idx + 1) + } + + expect(foundDepSources.exists(_.startsWith(s"os-lib_$actualScalaMajorVersion-0.9.1"))) + } + + { + val resp = await { + remoteServer + .buildTargetDependencySources(new b.DependencySourcesParams(List(coreTarget).asJava)) + .asScala + } + val foundTargets = resp.getItems.asScala.map(_.getTarget.getUri).toSeq + expect(foundTargets == Seq(coreTargetUri)) + val foundDepSources = resp.getItems.asScala + .flatMap(_.getSources.asScala) + .toSeq + .map { uri => + val idx = uri.lastIndexOf('/') + uri.drop(idx + 1) + } + + expect(foundDepSources.exists(_.startsWith(s"pprint_$actualScalaMajorVersion-0.6.6"))) + expect(foundDepSources.exists(_.startsWith(s"os-lib_$actualScalaMajorVersion-0.9.1"))) + } + } + } + } + private def extractMainTargetsOfModules(targets: Seq[BuildTargetIdentifier]) : Seq[BuildTargetIdentifier] = targets.collect {