Skip to content

Commit

Permalink
Merge pull request #758 from scalacenter/topic/weak-cyclic-dependency…
Browse files Browse the repository at this point in the history
…-checker

Handle recursive dependencies gracefully
  • Loading branch information
jvican authored Dec 13, 2018
2 parents ab2752a + 48aaa6b commit 8f8cc9d
Show file tree
Hide file tree
Showing 26 changed files with 526 additions and 268 deletions.
9 changes: 5 additions & 4 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ matrix:
"show version" \
"set pgpPublicRing in Global := file(\"/drone/.gnupg/pubring.asc\")" \
"set pgpSecretRing in Global := file(\"/drone/.gnupg/secring.asc\")" \
"docs/docusaurusPublishGhpages" \
"releaseBloop"
"releaseBloop" \
"docs/docusaurusPublishGhpages"

RELEASE_GITHUB_BREW:
- bin/sbt-ci.sh \
"frontend/updateHomebrewFormula" \
"frontend/updateScoopFormula" \
"frontend/githubRelease"
OS:
- windows
Expand Down Expand Up @@ -85,7 +86,7 @@ pipeline:
- ./frontend/src/test/resources/cross-test-build-1.0/target/generation-cache-file
volumes:
- /cache:/cache
cache_key: [ DRONE_REPO_OWNER, DRONE_REPO_NAME, DRONE_BRANCH ]
cache_key: [ DRONE_REPO_OWNER, DRONE_REPO_NAME ]
when:
ref: [ refs/heads/master, refs/tags/*, refs/pull/*/head ]
matrix:
Expand Down Expand Up @@ -214,4 +215,4 @@ pipeline:
- ./frontend/src/test/resources/cross-test-build-1.0/target/generation-cache-file
volumes:
- /cache:/cache
cache_key: [ DRONE_REPO_OWNER, DRONE_REPO_NAME, DRONE_BRANCH ]
cache_key: [ DRONE_REPO_OWNER, DRONE_REPO_NAME ]
66 changes: 38 additions & 28 deletions backend/src/main/scala/bloop/ScalaInstance.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package bloop
import java.io.File
import java.net.URLClassLoader
import java.nio.file.{Files, Path, Paths}
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.{BasicFileAttributes, FileTime}
import java.util.Properties

import bloop.internal.build.BloopScalaInfo
import bloop.logging.{ DebugFilter, Logger }
import bloop.logging.{DebugFilter, Logger}

import scala.util.control.NonFatal

Expand Down Expand Up @@ -59,9 +59,17 @@ final class ScalaInstance private (
override val hashCode: Int = {
val attributedJars =
allJars.toSeq.map { jar =>
val attrs = Files.readAttributes(jar.toPath, classOf[BasicFileAttributes])
(jar, attrs.lastModifiedTime(), attrs.size())
val (lastModified, size) = {
if (jar.exists()) (FileTime.fromMillis(0), 0)
else {
val attrs = Files.readAttributes(jar.toPath, classOf[BasicFileAttributes])
(attrs.lastModifiedTime(), attrs.size())
}
}

(jar, lastModified, size)
}

attributedJars.hashCode()
}
}
Expand All @@ -83,11 +91,13 @@ object ScalaInstance {
* cannot be resolved, users will get a resolution error instead of a weird compilation
* error when compilation via Zinc starts.
*/
def apply(scalaOrg: String,
scalaName: String,
scalaVersion: String,
allJars: Seq[AbsolutePath],
logger: Logger): ScalaInstance = {
def apply(
scalaOrg: String,
scalaName: String,
scalaVersion: String,
allJars: Seq[AbsolutePath],
logger: Logger
): ScalaInstance = {
val jarsKey = allJars.map(_.underlying).sortBy(_.toString).toList
if (allJars.nonEmpty) {
def newInstance = {
Expand Down Expand Up @@ -129,25 +139,25 @@ object ScalaInstance {
private[this] var cachedBloopScalaInstance: Option[ScalaInstance] = null

/**
* Returns the default scala instance that is used in bloop's classloader.
*
* A Scala instance is always required to compile with Zinc. As a result,
* Java projects that have no Scala configuration and want to be able to
* re-use Zinc's Javac infrastructure for incremental compilation need to
* have a dummy Scala instance available.
*
* This method is responsible for creating this dummy Scala instance. The
* instance is fully functional and the jars for the instance come from Bloop
* class loader which depends on all the Scala jars. Here we get the jars that are
* usually used in scala jars from the protected domain of every class.
*
* This could not work in case a user has set a strict security manager in the
* environment in which Bloop is running at. However, this is unlikely because
* Bloop will run on different machines that normal production-ready machines.
* In these machines, security managers don't usually exist and if they do don't
* happen to be so strict as to prevent getting the location from the protected
* domain.
*/
* Returns the default scala instance that is used in bloop's classloader.
*
* A Scala instance is always required to compile with Zinc. As a result,
* Java projects that have no Scala configuration and want to be able to
* re-use Zinc's Javac infrastructure for incremental compilation need to
* have a dummy Scala instance available.
*
* This method is responsible for creating this dummy Scala instance. The
* instance is fully functional and the jars for the instance come from Bloop
* class loader which depends on all the Scala jars. Here we get the jars that are
* usually used in scala jars from the protected domain of every class.
*
* This could not work in case a user has set a strict security manager in the
* environment in which Bloop is running at. However, this is unlikely because
* Bloop will run on different machines that normal production-ready machines.
* In these machines, security managers don't usually exist and if they do don't
* happen to be so strict as to prevent getting the location from the protected
* domain.
*/
def scalaInstanceFromBloop(logger: Logger): Option[ScalaInstance] = {
def findLocationForClazz(clazz: Class[_]): Option[Path] = {
try Some(Paths.get(clazz.getProtectionDomain.getCodeSource.getLocation.toURI))
Expand Down
12 changes: 8 additions & 4 deletions backend/src/main/scala/bloop/io/Paths.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import java.nio.file.{
}
import java.util

import bloop.logging.Logger
import bloop.logging.{DebugFilter, Logger}
import io.github.soc.directories.ProjectDirectories

object Paths {
Expand Down Expand Up @@ -112,6 +112,7 @@ object Paths {

def visitFileFailed(t: Path, e: IOException): FileVisitResult = {
logger.error(s"Unexpected failure when visiting ${t}: '${e.getMessage}'")
logger.trace(e)
FileVisitResult.CONTINUE
}

Expand All @@ -126,9 +127,12 @@ object Paths {
): FileVisitResult = FileVisitResult.CONTINUE
}

val opts = util.EnumSet.of(FileVisitOption.FOLLOW_LINKS)
Files.walkFileTree(base.underlying, opts, maxDepth, visitor)
out.toList
if (!base.exists) Nil
else {
val opts = util.EnumSet.of(FileVisitOption.FOLLOW_LINKS)
Files.walkFileTree(base.underlying, opts, maxDepth, visitor)
out.toList
}
}

/**
Expand Down
4 changes: 2 additions & 2 deletions bin/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,14 @@ def coursier_bootstrap(target, main):
http = []
https = []

if "http_proxy" in os.environ:
if os.environ.get("http_proxy"):
http_proxy = urlparse(os.environ['http_proxy'])
http = [
"-Dhttp.proxyHost="+http_proxy.hostname,
"-Dhttp.proxyPort="+str(http_proxy.port)
]

if "https_proxy" in os.environ:
if os.environ.get("https_proxy"):
https_proxy = urlparse(os.environ['https_proxy'])
https = [
"-Dhttps.proxyHost="+https_proxy.hostname,
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ val bloop = project
.settings(
releaseEarly := { () },
skip in publish := true,
crossSbtVersions := Seq("1.1.0", "0.13.16")
crossSbtVersions := Seq("1.1.0", "0.13.16"),
commands += BuildDefaults.exportProjectsInTestResourcesCmd
)

/***************************************************************************************************/
Expand Down
4 changes: 2 additions & 2 deletions config/src/main/scala/bloop/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ object Config {

case class File(version: String, project: Project)
object File {
final val LatestVersion = "1.1.0-M2"

// We cannot have the version coming from the build tool
final val LatestVersion = "1.1.2"
private[bloop] val empty = File(LatestVersion, Project.empty)

private[bloop] def dummyForTests: File = {
Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/scala/bloop/docs/Sonatype.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ case class Release(version: String, lastModified: Date) {
}

object Sonatype {
lazy val release = Sonatype.fetchLatest("releases")
lazy val release = Sonatype.fetchLatest("staging")

// Copy-pasted from https://github.com/scalameta/metals/blob/994e5e6746ad327ce727d688ad9831e0fbb69b3f/metals-docs/src/main/scala/docs/Snapshot.scala
lazy val current: Release = Release(BuildInfo.version, new Date())
Expand Down
28 changes: 7 additions & 21 deletions frontend/src/main/scala/bloop/Cli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -283,28 +283,14 @@ object Cli {
bloop.util.ProxySetup.updateProxySettings(commonOpts.env.toMap, logger)
val currentState = State.loadActiveStateFor(configDirectory, pool, cliOptions.common, logger)

def interpretActionIn(dir: Path) = {
waitUntilEndOfWorld(action, cliOptions, pool, dir, logger, userArgs, cancel) {
Interpreter.execute(action, currentState).map { newState =>
State.stateCache.updateBuild(newState.copy(status = ExitStatus.Ok))
// Persist successful result on the background for the new state -- it doesn't block!
val persistOut = (msg: String) => newState.commonOptions.ngout.println(msg)
Tasks.persist(newState, persistOut).runAsync(ExecutionContext.ioScheduler)
newState
}
}
}

val dir = configDirectory.underlying
if (Files.exists(configDirectory.underlying)) interpretActionIn(dir)
else {
action match {
case Run(Commands.Help(_) | Commands.About(_), _) =>
// Interpret the action only if the commands are help or about
interpretActionIn(dir)
case _ =>
logger.error(Feedback.missingConfigDirectory(configDirectory))
ExitStatus.InvalidCommandLineOption
waitUntilEndOfWorld(action, cliOptions, pool, dir, logger, userArgs, cancel) {
Interpreter.execute(action, currentState).map { newState =>
State.stateCache.updateBuild(newState.copy(status = ExitStatus.Ok))
// Persist successful result on the background for the new state -- it doesn't block!
val persistOut = (msg: String) => newState.commonOptions.ngout.println(msg)
Tasks.persist(newState, persistOut).runAsync(ExecutionContext.ioScheduler)
newState
}
}
}
Expand Down
78 changes: 49 additions & 29 deletions frontend/src/main/scala/bloop/bsp/BloopBspServices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.nio.file.{FileSystems, PathMatcher}
import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
import java.util.concurrent.atomic.AtomicInteger

import bloop.cli.validation.Validate
import bloop.{CompileMode, Compiler, ScalaInstance}
import bloop.cli.{BloopReporter, Commands, ExitStatus, ReporterKind}
import bloop.data.{Platform, Project}
Expand All @@ -17,7 +18,13 @@ import bloop.logging.{BspServerLogger, DebugFilter}
import bloop.reporter.{BspProjectReporter, Reporter, ReporterConfig}
import bloop.testing.{BspLoggingEventHandler, TestInternals}
import monix.eval.Task
import ch.epfl.scala.bsp.{BuildTargetIdentifier, endpoints}
import ch.epfl.scala.bsp.{
BuildTargetIdentifier,
MessageType,
ShowMessageParams,
WorkspaceBuildTargets,
endpoints
}

import scala.meta.jsonrpc.{JsonRpcClient, Response => JsonRpcResponse, Services => JsonRpcServices}
import xsbti.Problem
Expand Down Expand Up @@ -447,36 +454,49 @@ final class BloopBspServices(
request: bsp.WorkspaceBuildTargetsRequest
): BspEndpointResponse[bsp.WorkspaceBuildTargets] = {
ifInitialized { (state: State) =>
val build = state.build
val targets = bsp.WorkspaceBuildTargets(
build.projects.map { p =>
val id = toBuildTargetId(p)
val tag = {
if (p.name.endsWith("-test") && build.getProjectFor(s"${p.name}-test").isEmpty)
bsp.BuildTargetTag.Test
else bsp.BuildTargetTag.Library
}
val deps = p.dependencies.iterator.flatMap(build.getProjectFor(_).toList)
val extra = p.scalaInstance.map(i => encodeScalaBuildTarget(toScalaBuildTarget(p, i)))
val capabilities = bsp.BuildTargetCapabilities(
canCompile = true,
canTest = true,
canRun = true
)
bsp.BuildTarget(
id = id,
displayName = Some(p.name),
baseDirectory = Some(bsp.Uri(p.baseDirectory.toBspUri)),
tags = List(tag),
languageIds = BloopBspServices.DefaultLanguages,
dependencies = deps.map(toBuildTargetId).toList,
capabilities = capabilities,
data = extra
def reportBuildError(msg: String): Unit = {
endpoints.Build.showMessage.notify(
ShowMessageParams(MessageType.Error, None, None, msg)
)(client)
()
}

Validate.validateBuildForCLICommands(state, reportBuildError(_)).flatMap { state =>
if (state.status == ExitStatus.BuildDefinitionError)
Task.now((state, Right(WorkspaceBuildTargets(Nil))))
else {
val build = state.build
val targets = bsp.WorkspaceBuildTargets(
build.projects.map { p =>
val id = toBuildTargetId(p)
val tag = {
if (p.name.endsWith("-test") && build.getProjectFor(s"${p.name}-test").isEmpty)
bsp.BuildTargetTag.Test
else bsp.BuildTargetTag.Library
}
val deps = p.dependencies.iterator.flatMap(build.getProjectFor(_).toList)
val extra = p.scalaInstance.map(i => encodeScalaBuildTarget(toScalaBuildTarget(p, i)))
val capabilities = bsp.BuildTargetCapabilities(
canCompile = true,
canTest = true,
canRun = true
)
bsp.BuildTarget(
id = id,
displayName = Some(p.name),
baseDirectory = Some(bsp.Uri(p.baseDirectory.toBspUri)),
tags = List(tag),
languageIds = BloopBspServices.DefaultLanguages,
dependencies = deps.map(toBuildTargetId).toList,
capabilities = capabilities,
data = extra
)
}
)
}
)

Task.now((state, Right(targets)))
Task.now((state, Right(targets)))
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main/scala/bloop/cli/ExitStatus.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ object ExitStatus {
}

// FORMAT: OFF
val Ok, UnexpectedError, ParseError, InvalidCommandLineOption, CompilationError, LinkingError, TestExecutionError, RunError: ExitStatus = generateExitStatus
val Ok, UnexpectedError, ParseError, InvalidCommandLineOption, CompilationError, LinkingError, TestExecutionError, RunError, BuildDefinitionError: ExitStatus = generateExitStatus
// FORMAT: ON

// The initialization of all must come after the invocations to `generateExitStatus`
Expand Down
Loading

0 comments on commit 8f8cc9d

Please sign in to comment.