Skip to content

Commit

Permalink
Merge pull request #90 from Duhemm/jar-url
Browse files Browse the repository at this point in the history
Support setting the URL of the JAR to resolve
  • Loading branch information
eed3si9n authored Jun 1, 2021
2 parents 4282989 + f0ed1be commit 7b45d77
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,19 @@ import moped.json.Result
import moped.json.ValueResult
import moped.progressbars.ProgressRenderer
import moped.reporters.Diagnostic
import moped.reporters.ErrorSeverity
import moped.reporters.Input
import moped.reporters.NoPosition
import moped.reporters.WarningSeverity
import multiversion.configs.DependencyConfig
import multiversion.configs.ModuleConfig
import multiversion.configs.ThirdpartyConfig
import multiversion.diagnostics.ConflictingTransitiveDependencyDiagnostic
import multiversion.diagnostics.EvictedDeclaredDependencyDiagnostic
import multiversion.diagnostics.ForbiddenUrlAttributeDiagnostic
import multiversion.diagnostics.IntraTargetConflictDiagnostic
import multiversion.diagnostics.MultidepsEnrichments._
import multiversion.diagnostics.TransitiveUrlDiagnostic
import multiversion.loggers._
import multiversion.outputs.ArtifactOutput
import multiversion.outputs.DependencyResolution
Expand Down Expand Up @@ -67,6 +71,8 @@ case class ExportCommand(
parallelDownload: Option[Int] = None,
@Description("Report an error if a declared dependency is evicted")
failOnEvictedDeclared: Boolean = true,
@Description("Whether to allow `url` attribute in dependency configurations")
allowUrl: Boolean = true,
) extends Command {
def app = lintCommand.app
def run(): Int = {
Expand Down Expand Up @@ -100,15 +106,16 @@ case class ExportCommand(
.withRetry(retryCount)

for {
initialResolutions <- runResolutions(thirdparty, thirdparty.coursierDeps, coursierCache)
initialIndex = ResolutionIndex.fromResolutions(thirdparty, initialResolutions)
withSelectedVersions = selectVersionsFromIndex(thirdparty, initialIndex)
withUrls <- lintUrls(thirdparty, allowUrl)
initialResolutions <- runResolutions(withUrls, withUrls.coursierDeps, coursierCache)
initialIndex = ResolutionIndex.fromResolutions(withUrls, initialResolutions)
withSelectedVersions = selectVersionsFromIndex(withUrls, initialIndex)
withOverriddenTargets = overrideTargets(withSelectedVersions, initialIndex)
resolutions <-
runResolutions(withOverriddenTargets, withOverriddenTargets.coursierDeps, coursierCache)
index = ResolutionIndex.fromResolutions(withOverriddenTargets, resolutions)
_ <- lintEvictedDeclaredDependencies(
thirdparty,
withUrls,
initialIndex,
index,
failOnEvictedDeclared
Expand Down Expand Up @@ -155,6 +162,40 @@ case class ExportCommand(
VersionCompatibility.Strict -> "strict"
)

/**
* Report errors for dependencies that set the URL of the JAR to resolve when
* `allowUrl` is not set, and report warnings for dependencies that set the URL of the JAR
* to resolve, but do not set the intransitive flag.
*
* @param thirdparty The third party configuration
* @param allowUrl Whether setting the URL of the JAR in a dependency is allowed
* @return The new third party configuration where dependencies that set the URl of the JAR
* to resolve are all marked `intransitive`.
*/
private def lintUrls(
thirdparty: ThirdpartyConfig,
allowUrl: Boolean
): Result[ThirdpartyConfig] = {
val diagnostics = thirdparty.dependencies.flatMap { dep =>
val forbiddenUrl =
if (dep.url.isDefined && !allowUrl)
List(new ForbiddenUrlAttributeDiagnostic(dep.targets, dep.organization.position))
else Nil
val transitiveUrl =
if (dep.url.isDefined && dep.transitive)
List(new TransitiveUrlDiagnostic(dep.targets, dep.organization.position))
else Nil
forbiddenUrl ++ transitiveUrl
}

val newDependencies = thirdparty.dependencies.map {
case dep if dep.url.isDefined && dep.transitive => dep.copy(transitive = false)
case dep => dep
}

app.reportOrElse(diagnostics, thirdparty.copy(dependencies = newDependencies))
}

/**
* Re-configure the dependencies whose resolution include overridden targets
* (artifacts that are published but that are available as source dependency),
Expand Down Expand Up @@ -320,6 +361,9 @@ case class ExportCommand(
* `failOnEvictedDeclared` is set, then an error will be returned when such
* dependencies are found.
*
* When a declared dependency is evicted and sets the URL of the JAR to resolve, then the
* message will always be considered an error, regardless of `failOnEvictedDeclared`.
*
* @param originalThirdParty The original third party configuration, as configured in the
* input file.
* @param originalIndex The resolution index built after the first resolution.
Expand All @@ -335,10 +379,6 @@ case class ExportCommand(
index: ResolutionIndex,
failOnEvictedDeclared: Boolean
): Result[Unit] = {
val severity =
if (failOnEvictedDeclared) moped.reporters.ErrorSeverity
else moped.reporters.WarningSeverity

def selectedDependencyOf(config: DependencyConfig): Dependency = {
val originalDependency = config.toCoursierDependency(originalThirdParty.scala)
// The original dependency could have been evicted after the first resolution already,
Expand Down Expand Up @@ -372,29 +412,19 @@ case class ExportCommand(
selectedDependency = selectedDependencyOf(dependency)
if declaredDependency.version != selectedDependency.version && dependency.force
breakingTargets = targetsDependingOn(selectedDependency) -- declaringTargets
severity =
if (dependency.url.isDefined || failOnEvictedDeclared) ErrorSeverity else WarningSeverity
} yield new EvictedDeclaredDependencyDiagnostic(
declaredDependency,
declaringTargets,
selectedDependency,
breakingTargets,
hasUrl = dependency.url.isDefined,
severity,
dependency.organization.position
)

if (failOnEvictedDeclared) {
Diagnostic.fromDiagnostics(diagnostics) match {
case Some(diagnostic) => ErrorResult(diagnostic)
case None => ValueResult(())
}
} else {
diagnostics.foreach(app.reporter.log)
if (diagnostics.length == 1) {
app.reporter.warning("1 declared dependency was evicted.")
} else if (diagnostics.length > 1) {
app.reporter.warning(s"${diagnostics.length} declared dependencies were evicted.")
}
ValueResult(())
}
app.reportOrElse(diagnostics, ())
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ final case class DependencyConfig(
organization: JsonString = JsonString(""),
name: String = "",
version: String = "",
url: Option[String] = None,
classifier: Option[String] = None,
exclusions: Set[ModuleConfig] = Set.empty,
crossVersions: List[CrossVersionsConfig] = Nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,25 @@ class EvictedDeclaredDependencyDiagnostic(
declaringTargets: Iterable[String],
selectedDependency: Dependency,
breakingTargets: Iterable[String],
hasUrl: Boolean,
severity: Severity,
pos: Position
) extends Diagnostic(severity, "", pos) {
private val urlInfos =
if (hasUrl)
List(
" This dependency defines the URL of the JAR to resolve, forcing this message to the ERROR level."
)
else Nil
private val infos = urlInfos ++ List(
s" '${declaredDependency.repr}' is declared in ${declaringTargets.toList.sorted.mkString(", ")}.",
s" '${selectedDependency.repr}' is a transitive dependency of ${breakingTargets.toList.sorted.mkString(", ")}."
)

override def message: String = {
s"""|Declared third party dependency '${declaredDependency.repr}' is evicted in favor of '${selectedDependency.repr}'.
|Update the third party declaration to use version '${selectedDependency.version}' instead of '${declaredDependency.version}' to reflect the effective dependency graph.
|Info:
| '${declaredDependency.repr}' is declared in ${declaringTargets.toList.sorted
.mkString(", ")}.
| '${selectedDependency.repr}' is a transitive dependency of ${breakingTargets.toList.sorted
.mkString(", ")}.""".stripMargin
|${infos.mkString(System.lineSeparator)}""".stripMargin
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package multiversion.diagnostics

import moped.reporters.Diagnostic
import moped.reporters.ErrorSeverity
import moped.reporters.Position
import multiversion.diagnostics.MultidepsEnrichments._

class ForbiddenUrlAttributeDiagnostic(
targets: List[String],
pos: Position
) extends Diagnostic(ErrorSeverity, "", pos) {
override def message: String = {
s"""|The third party dependency declaration in ${targets.commas} is defining the URL of the JAR
|to resolve, which is not permitted.
|""".stripMargin
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import moped.json.ErrorResult
import moped.json.Result
import moped.json.ValueResult
import moped.reporters.Diagnostic
import moped.reporters.ErrorSeverity
import moped.reporters.Reporter
import moped.reporters.WarningSeverity

object MultidepsEnrichments {
implicit class XtensionString(string: String) {
Expand Down Expand Up @@ -56,6 +58,23 @@ object MultidepsEnrichments {
app.reporter.log(error)
1
}

def reportOrElse[T](diagnostics: List[Diagnostic], result: => T): Result[T] = {
val hasError = diagnostics.exists(_.severity >= ErrorSeverity)
if (hasError) {
Diagnostic.fromDiagnostics(diagnostics) match {
case Some(diagnostic) => ErrorResult(diagnostic)
case None => ValueResult(result)
}
} else {
diagnostics.foreach(app.reporter.log)
val warnings = diagnostics.count(_.severity == WarningSeverity)
if (warnings > 0) {
app.reporter.warning(warnings + " warning(s) found.")
}
ValueResult(result)
}
}
}
private val isEmptyLikeConfiguration = Set(
Configuration.empty,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package multiversion.diagnostics

import moped.reporters.Diagnostic
import moped.reporters.Position
import moped.reporters.WarningSeverity
import multiversion.diagnostics.MultidepsEnrichments._

class TransitiveUrlDiagnostic(
targets: List[String],
pos: Position
) extends Diagnostic(WarningSeverity, "", pos) {
override def message: String = {
s"""|The third party dependency declaration in ${targets.commas} is defining the URL of the JAR
|to resolve, but isn't marked as intransitive. It will be considered intransitive.
|""".stripMargin
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ final case class ResolutionIndex(
// list of all artifacts including transitive JARs
val rawArtifacts: List[ResolvedDependency] = for {
r <- resolutions
resolutionModule = r.dep.coursierModule(thirdparty.scala)
(d, p, a) <- r.res.dependencyArtifacts() if a.url.endsWith(".jar")
dependency = ResolutionIndex.actualDependency(d, r.res.projectCache)
} yield ResolvedDependency(r.dep, dependency, p, a)
artifact = r.dep.url match {
case Some(url) if dependency.module == resolutionModule =>
a.withUrl(url).withChecksumUrls(Map.empty)
case _ => a
}
} yield ResolvedDependency(r.dep, dependency, p, artifact)

val resolvedArtifacts: List[ResolvedDependency] = (rawArtifacts
.groupBy(_.dependency.bazelLabel)
Expand Down
6 changes: 6 additions & 0 deletions tests/src/main/scala/tests/ConfigSyntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ trait ConfigSyntax {
org,
name,
version,
url = None,
classifier = None,
dependencies = Nil,
exclusions = Nil,
Expand Down Expand Up @@ -47,6 +48,7 @@ object ConfigNode {
organization: String,
name: String,
version: String,
url: Option[String],
classifier: Option[String],
dependencies: List[String],
exclusions: List[Exclusion],
Expand All @@ -56,6 +58,9 @@ object ConfigNode {
versionPattern: Option[String]
) extends ConfigNode {

def url(url: String): Dependency =
copy(url = Option(url))

def classifier(classifier: String): Dependency =
copy(classifier = Option(classifier))

Expand Down Expand Up @@ -105,6 +110,7 @@ object ConfigNode {
}

s"""| - dependency: $organization:$name:$version
| url: ${url.orNull}
| classifier: ${classifier.orNull}
| force: $force
| transitive: $transitive
Expand Down
Loading

0 comments on commit 7b45d77

Please sign in to comment.