Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions modules/build/src/main/scala/scala/build/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,10 @@ object Build {
workspace = inputs.workspace,
updateSemanticDbs = true,
scalaVersion = sv,
buildOptions = build.options
buildOptions =
inputs.originalWorkspaceOpt.fold(build.options)(
build.options.withResolvedSemanticDbSourceRoot
)
).left.foreach(_.foreach(logger.message(_)))
case _ =>
}
Expand Down Expand Up @@ -942,7 +945,8 @@ object Build {
options.scalaOptions.semanticDbOptions.generateSemanticDbs.getOrElse(false)
val semanticDbTargetRoot = options.scalaOptions.semanticDbOptions.semanticDbTargetRoot
val semanticDbSourceRoot =
options.scalaOptions.semanticDbOptions.semanticDbSourceRoot.getOrElse(inputs.workspace)
options.scalaOptions.semanticDbOptions.semanticDbSourceRoot
.getOrElse(inputs.originalWorkspaceOpt.getOrElse(inputs.workspace))

val scalaCompilerParamsOpt = artifacts.scalaOpt match {
case Some(scalaArtifacts) =>
Expand Down
5 changes: 4 additions & 1 deletion modules/build/src/main/scala/scala/build/bsp/BspImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,10 @@ final class BspImpl(
currentBloopSession.inputs.workspace,
updateSemanticDbs = true,
scalaVersion = sv,
buildOptions = data.buildOptions
buildOptions =
currentBloopSession.inputs.originalWorkspaceOpt.fold(data.buildOptions)(
data.buildOptions.withResolvedSemanticDbSourceRoot
)
).left.foreach(_.foreach(showGlobalWarningOnce))

if (res.getStatusCode == b.StatusCode.OK)
Expand Down
26 changes: 17 additions & 9 deletions modules/build/src/main/scala/scala/build/bsp/BspServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import java.util as ju
import java.util.concurrent.{CompletableFuture, TimeUnit}

import scala.build.Logger
import scala.build.input.Inputs
import scala.build.internal.Constants
import scala.build.options.Scope
import scala.concurrent.{Future, Promise}
Expand All @@ -31,6 +32,13 @@ class BspServer(
@volatile private var intelliJ: Boolean = presetIntelliJ
def isIntelliJ: Boolean = intelliJ

@volatile private var bspBaseDirectoryOverride: Option[os.Path] = None

override def newInputs(inputs: Inputs): Unit = {
super.newInputs(inputs)
bspBaseDirectoryOverride = inputs.originalWorkspaceOpt
}

def clientOpt: Option[BuildClient] = client

@volatile private var extraDependencySources: Seq[os.Path] = Nil
Expand Down Expand Up @@ -276,16 +284,16 @@ class BspServer(
val res0 = res.duplicate()
stripInvalidTargets(res0)
for (target <- res0.getTargets.asScala) {
val capabilities = target.getCapabilities
capabilities.setCanDebug(true)
target.getCapabilities.setCanDebug(true)
val baseDirectory = new File(new URI(target.getBaseDirectory))
if (
isIntelliJ && baseDirectory.getName == Constants.workspaceDirName &&
baseDirectory
.getParentFile != null
) {
val newBaseDirectory = baseDirectory.getParentFile.toPath.toUri.toASCIIString
target.setBaseDirectory(newBaseDirectory)
bspBaseDirectoryOverride match {
case Some(originalWs) =>
target.setBaseDirectory(originalWs.toNIO.toUri.toASCIIString)
case None
if isIntelliJ && baseDirectory.getName == Constants.workspaceDirName
&& baseDirectory.getParentFile != null =>
target.setBaseDirectory(baseDirectory.getParentFile.toPath.toUri.toASCIIString)
case _ => // leave Bloop's value untouched
}
}
res0
Expand Down
14 changes: 9 additions & 5 deletions modules/build/src/main/scala/scala/build/input/Inputs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ final case class Inputs(
mayAppendHash: Boolean,
workspaceOrigin: Option[WorkspaceOrigin],
enableMarkdown: Boolean,
allowRestrictedFeatures: Boolean
allowRestrictedFeatures: Boolean,
originalWorkspaceOpt: Option[os.Path]
) {

def isEmpty: Boolean = elements.isEmpty
Expand Down Expand Up @@ -75,7 +76,8 @@ final case class Inputs(
copy(
workspace = elements.homeWorkspace(directories),
mayAppendHash = false,
workspaceOrigin = Some(WorkspaceOrigin.HomeDir)
workspaceOrigin = Some(WorkspaceOrigin.HomeDir),
originalWorkspaceOpt = originalWorkspaceOpt.orElse(Some(workspace))
)
def avoid(forbidden: Seq[os.Path], directories: Directories): Inputs =
if forbidden.exists(workspace.startsWith) then inHomeDir(directories) else this
Expand Down Expand Up @@ -159,7 +161,8 @@ object Inputs {
mayAppendHash = needsHash,
workspaceOrigin = Some(workspaceOrigin),
enableMarkdown = enableMarkdown,
allowRestrictedFeatures = allowRestrictedFeatures
allowRestrictedFeatures = allowRestrictedFeatures,
originalWorkspaceOpt = None
)
}

Expand Down Expand Up @@ -476,11 +479,12 @@ object Inputs {
mayAppendHash = true,
workspaceOrigin = None,
enableMarkdown = enableMarkdown,
allowRestrictedFeatures = false
allowRestrictedFeatures = false,
originalWorkspaceOpt = None
)

def empty(projectName: String): Inputs =
Inputs(Nil, None, os.pwd, projectName, false, None, true, false)
Inputs(Nil, None, os.pwd, projectName, false, None, true, false, None)

def baseName(p: os.Path) = if (p == os.root || p.lastOpt.isEmpty) "" else p.baseName

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ import scala.util.{Failure, Success, Try}

trait BspSuite { this: ScalaCliSuite =>
protected def extraOptions: Seq[String]
def initParams(root: os.Path): b.InitializeBuildParams =
def initParams(
root: os.Path,
clientName: String = "Scala CLI ITs"
): b.InitializeBuildParams =
new b.InitializeBuildParams(
"Scala CLI ITs",
clientName,
"0",
Constants.bspVersion,
root.toNIO.toUri.toASCIIString,
Expand Down Expand Up @@ -74,7 +77,8 @@ trait BspSuite { this: ScalaCliSuite =>
bspEnvs: Map[String, String] = Map.empty,
reuseRoot: Option[os.Path] = None,
stdErrOpt: Option[os.RelPath] = None,
extraOptionsOverride: Seq[String] = extraOptions
extraOptionsOverride: Seq[String] = extraOptions,
bspClientName: String = "Scala CLI ITs"
)(
f: (
os.Path,
Expand All @@ -91,7 +95,8 @@ trait BspSuite { this: ScalaCliSuite =>
bspEnvs,
reuseRoot,
stdErrOpt,
extraOptionsOverride
extraOptionsOverride,
bspClientName
)((root, client, server, _: b.InitializeBuildResult) => f(root, client, server))

def withBspInitResults[T](
Expand All @@ -103,7 +108,8 @@ trait BspSuite { this: ScalaCliSuite =>
bspEnvs: Map[String, String] = Map.empty,
reuseRoot: Option[os.Path] = None,
stdErrOpt: Option[os.RelPath] = None,
extraOptionsOverride: Seq[String] = extraOptions
extraOptionsOverride: Seq[String] = extraOptions,
bspClientName: String = "Scala CLI ITs"
)(
f: (
os.Path,
Expand Down Expand Up @@ -151,7 +157,9 @@ trait BspSuite { this: ScalaCliSuite =>
TestBspClient.connect(proc.stdout, proc.stdin, pool)
remoteServer = remoteServer0
val initRes: b.InitializeBuildResult = Await.result(
whileBspServerIsRunning(remoteServer.buildInitialize(initParams(root)).asScala),
whileBspServerIsRunning(
remoteServer.buildInitialize(initParams(root, bspClientName)).asScala
),
Duration.Inf
)
Await.result(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2445,4 +2445,101 @@ abstract class BspTestDefinitions extends ScalaCliSuite
}
}
}

for {
isIntelliJ <- Seq(false, true)
clientDescription = if isIntelliJ then "IntelliJ" else "any client"
bspClientName = if isIntelliJ then "IntelliJ" else "test"
} {
def mainTargetOf(
remoteServer: b.BuildServer & b.ScalaBuildServer & b.JavaBuildServer & b.JvmBuildServer &
TestBspClient.WrappedSourcesBuildServer
): b.BuildTarget = {
val buildTargetsResp = remoteServer.workspaceBuildTargets().asScala.await
val targets = buildTargetsResp.getTargets.asScala.toSeq
targets.find(t => !t.getId.getUri.contains("-test")).getOrElse {
sys.error(s"No main build target found in ${targets.map(_.getId.getUri)}")
}
}

def baseDirectoryOf(target: b.BuildTarget): os.Path =
os.Path(Paths.get(new URI(target.getBaseDirectory)))

def collectSemDbFiles(root: os.Path): Seq[os.Path] =
os.walk(root)
.filter(_.last.endsWith(".semanticdb"))
.filter(p => !p.segments.exists(_ == "bloop-internal-classes"))

test(s"workspaceBuildTargets baseDirectory: default workspace ($clientDescription)") {
withBsp(
inputs = TestInputs(
os.rel / "Hello.scala" ->
"""object Hello {
| def main(args: Array[String]): Unit = println("Hello")
|}
|""".stripMargin
),
args = Seq("."),
bspClientName = bspClientName
) { (root, _, remoteServer) =>
Future {
val mainTarget = mainTargetOf(remoteServer)
val baseDir = baseDirectoryOf(mainTarget)
if isIntelliJ then expect(baseDir == root)
else expect(baseDir == root / Constants.workspaceDirName)

val compileResp =
remoteServer
.buildTargetCompile(new b.CompileParams(List(mainTarget.getId).asJava))
.asScala
.await
expect(compileResp.getStatusCode == b.StatusCode.OK)

val semDbFiles = collectSemDbFiles(root)
expect(semDbFiles.nonEmpty)
expect(semDbFiles.forall(_.segments.contains(Constants.workspaceDirName)))
}
}
}

if !Properties.isWin then
test(s"workspaceBuildTargets baseDirectory: non-writable workspace ($clientDescription)") {
val simpleHelloInputsInDir = TestInputs(
os.rel / "dir" / "Hello.scala" ->
"""object Hello {
| def main(args: Array[String]): Unit = println("Hello")
|}
|""".stripMargin
)
simpleHelloInputsInDir.fromRoot { root =>
os.perms.set(root / "dir", "r-xr-xr-x")
try
withBsp(
simpleHelloInputsInDir,
Seq("dir"),
reuseRoot = Some(root),
bspClientName = bspClientName
) {
(_, _, remoteServer) =>
Future {
val mainTarget = mainTargetOf(remoteServer)
val baseDir = baseDirectoryOf(mainTarget)
val expectedBaseDir = root / "dir"
expect(baseDir == expectedBaseDir)

val compileResp =
remoteServer
.buildTargetCompile(new b.CompileParams(List(mainTarget.getId).asJava))
.asScala
.await
expect(compileResp.getStatusCode == b.StatusCode.OK)

val semDbFilesUnderRoot = collectSemDbFiles(root)
expect(semDbFilesUnderRoot.isEmpty)
}
}
finally os.perms.set(root / "dir", "rwxr-xr-x")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ final case class BuildOptions(

import BuildOptions.JavaHomeInfo

/** When the build workspace was moved to a virtual directory (e.g. after
* [[scala.build.input.Inputs.checkAttributes]] fallback) and the user did not set an explicit
* semanticdb source root, use the given original workspace so semanticdb paths stay relative to
* the user's project root.
*/
def withResolvedSemanticDbSourceRoot(originalWorkspace: os.Path): BuildOptions =
if scalaOptions.semanticDbOptions.semanticDbSourceRoot.isEmpty then
copy(scalaOptions =
scalaOptions.copy(
semanticDbOptions = scalaOptions.semanticDbOptions.copy(
semanticDbSourceRoot = Some(originalWorkspace)
)
)
)
else this

lazy val platform: Positioned[Platform] =
scalaOptions.platform.getOrElse(Positioned(List(Position.Custom("DEFAULT")), Platform.JVM))

Expand Down