diff --git a/README.md b/README.md index b535b28..9f5b50f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ This plugin: - configures [MiMa] to check for binary or source incompatibilities, - ensures that none of your dependencies are bumped or removed in an incompatible way, -- reports incompatibilities with previous releases. +- reports incompatibilities with previous releases, +- sets the [`versionScheme`](https://www.scala-sbt.org/1.x/docs/Publishing.html#Version+scheme) of the project to `"early-semver"`. ## Install @@ -266,6 +267,36 @@ In this mode, you can use sbt-version-policy to assess the incompatibilities int } ~~~ +### How to generate compatibility reports? + +You can export the compatibility reports in JSON format with the task `versionPolicyExportCompatibilityReport`. + +1. It does not matter whether `versionPolicyIntention` is set or not. If it is set, the report will list the incompatibilities that violate the intended compatibility level. If it is not set, all the incompatibilities will be reported. +2. Invoke the task `versionPolicyExportCompatibilityReport` on the module you want to generate a report for. For example, for the default root module: + ~~~ shell + sbt versionPolicyExportCompatibilityReport + ~~~ + The task automatically aggregates the compatibility reports of all its aggregated submodules. +3. Read the file `target/scala-2.13/compatibility-report.json` (or `target/scala-3/compatibility-report.json`). + You can see an example of compatibility report [here](./sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/expected-compatibility-report.json). + + Here are examples of how to read some specific fields of the compatibility report with `jq`: + ~~~ shell + # Get the highest compatibility level satisfied by all the aggregated modules. + # Returns either 'incompatible', 'binary-compatible', or 'binary-and-source-compatible'. + cat compatibility-report.json | jq '.aggregated.compatibility.value' + + # Get a human-readable description of the highest compatibility level sastisfied + # by all the aggregated modules. + cat compatibility-report.json | jq '.aggregated.compatibility.label' + + # Get the version of the project against which the compatibility level + # was assessed. + cat compatibility-report.json | jq '.aggregated.modules[0]."previous-version"' + # Or, in the case of a single module report (no aggregated submodules): + cat compatibility-report.json | jq '."previous-version"' + ~~~ + ## How does `versionPolicyCheck` work? The `versionPolicyCheck` task: diff --git a/build.sbt b/build.sbt index 4a3ec66..1631587 100644 --- a/build.sbt +++ b/build.sbt @@ -32,6 +32,7 @@ lazy val `sbt-version-policy` = project libraryDependencies ++= Seq( "io.get-coursier" % "interface" % "1.0.18", "io.get-coursier" %% "versions" % "0.3.1", + "com.lihaoyi" %% "ujson" % "3.1.3", // TODO shade "com.eed3si9n.verify" %% "verify" % "2.0.1" % Test, ), testFrameworks += new TestFramework("verify.runner.Framework"), diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala index 6d9caf2..0f62f57 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/Compatibility.scala @@ -1,6 +1,7 @@ package sbtversionpolicy -import coursier.version.{ Version, VersionCompatibility } +import com.typesafe.tools.mima.core.Problem +import coursier.version.{Version, VersionCompatibility} import sbt.VersionNumber /** Compatibility level between two version values. @@ -60,6 +61,22 @@ object Compatibility { } } + def fromIssues(dependencyIssues: DependencyCheckReport, apiIssues: Seq[(IncompatibilityType, Problem)]): Compatibility = { + if ( + dependencyIssues.validated(IncompatibilityType.SourceIncompatibility) && + apiIssues.isEmpty + ) { + Compatibility.BinaryAndSourceCompatible + } else if ( + dependencyIssues.validated(IncompatibilityType.BinaryIncompatibility) && + !apiIssues.exists(_._1 == IncompatibilityType.BinaryIncompatibility) + ) { + Compatibility.BinaryCompatible + } else { + Compatibility.None + } + } + /** * Validates that the given new `version` matches the claimed `compatibility` level. * @return Some validation error, or None if the version is valid. diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/CompatibilityReport.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/CompatibilityReport.scala new file mode 100644 index 0000000..9e875eb --- /dev/null +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/CompatibilityReport.scala @@ -0,0 +1,79 @@ +package sbtversionpolicy + +import sbt.* +import com.typesafe.tools.mima.core.Problem +import upickle.core.LinkedHashMap + +/** + * @param moduleReport Compatibility report for one module + * @param submoduleReports Compatibility reports for the aggregated submodules + */ +case class CompatibilityReport( + moduleReport: Option[CompatibilityModuleReport], + submoduleReports: Option[(Compatibility, Seq[CompatibilityReport])] +) + +/** + * @param previousRelease Module ID of the previous release of this module, against which the compatibility was assessed + * @param compatibility Assessed compatibility level based on both dependency issues and API issues + * @param dependencyIssues Dependency issues found for this module + * @param apiIssues API issues (ie, Mima issue) found for this module + */ +case class CompatibilityModuleReport( + previousRelease: ModuleID, + compatibility: Compatibility, + dependencyIssues: DependencyCheckReport, + apiIssues: Seq[(IncompatibilityType, Problem)] +) + +object CompatibilityReport { + + def write( + targetFile: File, + compatibilityReport: CompatibilityReport, + log: Logger, + compatibilityLabel: Compatibility => String = defaultCompatibilityLabels + ): Unit = { + IO.createDirectory(targetFile.getParentFile) + IO.write(targetFile, ujson.write(toJson(compatibilityReport, compatibilityLabel), indent = 2)) + log.info(s"Wrote compatibility report in ${targetFile.absolutePath}") + } + + // Human readable description of the compatibility levels + val defaultCompatibilityLabels: Compatibility => String = { + case Compatibility.None => "Incompatible" + case Compatibility.BinaryCompatible => "Binary compatible" + case Compatibility.BinaryAndSourceCompatible => "Binary and source compatible" + } + + private def toJson(report: CompatibilityReport, compatibilityLabel: Compatibility => String): ujson.Value = { + val fields = LinkedHashMap[String, ujson.Value]() + report.moduleReport.foreach { moduleReport => + fields ++= Seq( + "module-name" -> moduleReport.previousRelease.name, + "previous-version" -> moduleReport.previousRelease.revision, + "compatibility" -> toJson(moduleReport.compatibility, compatibilityLabel), + // TODO add issue details + // "issues" -> ujson.Obj("dependencies" -> ujson.Arr(), "api" -> ujson.Obj()) + ) + } + report.submoduleReports.foreach { case (aggregatedCompatibility, submoduleReports) => + fields += "aggregated" -> ujson.Obj( + "compatibility" -> toJson(aggregatedCompatibility, compatibilityLabel), + "modules" -> ujson.Arr(submoduleReports.map(toJson(_, compatibilityLabel))*) + ) + } + ujson.Obj(fields) + } + + private def toJson(compatibility: Compatibility, compatibilityLabel: Compatibility => String): ujson.Value = + ujson.Obj( + "value" -> (compatibility match { + case Compatibility.None => ujson.Str("incompatible") + case Compatibility.BinaryCompatible => ujson.Str("binary-compatible") + case Compatibility.BinaryAndSourceCompatible => ujson.Str("binary-and-source-compatible") + }), + "label" -> ujson.Str(compatibilityLabel(compatibility)) + ) + +} diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyInternalKeys.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyInternalKeys.scala index a9b4bb1..60e0633 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyInternalKeys.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyInternalKeys.scala @@ -22,4 +22,6 @@ trait SbtVersionPolicyInternalKeys { final val versionPolicyVersionCompatibility = settingKey[VersionCompatibility]("VersionCompatibility used to determine compatibility.") final val versionPolicyVersionCompatResult = taskKey[Compatibility]("Calculated level of compatibility required according to the current project version and the versioning scheme.") + + final def versionPolicyCollectCompatibilityReports = TaskKey[CompatibilityReport]("versionPolicyCollectCompatibilityReports", "Collect compatibility reports for the export task.") } diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala index e3944eb..b0a8de1 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyKeys.scala @@ -17,6 +17,8 @@ trait SbtVersionPolicyKeys { final val versionPolicyFindMimaIssues = taskKey[Seq[(ModuleID, Seq[(IncompatibilityType, Problem)])]]("Binary or source compatibility issues over the previously released artifacts.") final val versionPolicyFindIssues = taskKey[Seq[(ModuleID, (DependencyCheckReport, Seq[(IncompatibilityType, Problem)]))]]("Find both dependency issues and Mima issues.") final val versionPolicyAssessCompatibility = taskKey[Seq[(ModuleID, Compatibility)]]("Assess the compatibility level of the project compared to its previous releases.") + final def versionPolicyExportCompatibilityReport = TaskKey[Unit]("versionPolicyExportCompatibilityReport", "Export the compatibility report into a JSON file.") + final def versionPolicyCompatibilityReportPath = SettingKey[File]("versionPolicyCompatibilityReportPath", s"Path of the compatibility report (used by ${versionPolicyExportCompatibilityReport.key.label}).") final val versionCheck = taskKey[Unit]("Checks that the version is consistent with the intended compatibility level defined via versionPolicyIntention") final val versionPolicyIgnored = settingKey[Seq[OrganizationArtifactName]]("Exclude these dependencies from versionPolicyReportDependencyIssues.") diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala index 25f4392..1023e8d 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicyPlugin.scala @@ -26,7 +26,8 @@ object SbtVersionPolicyPlugin extends AutoPlugin { override def globalSettings = SbtVersionPolicySettings.reconciliationGlobalSettings ++ - SbtVersionPolicySettings.schemesGlobalSettings + SbtVersionPolicySettings.schemesGlobalSettings ++ + SbtVersionPolicySettings.exportGlobalSettings override def projectSettings = SbtVersionPolicySettings.updateSettings ++ @@ -50,25 +51,49 @@ object SbtVersionPolicyPlugin extends AutoPlugin { // Take all the projects aggregated by this project val aggregatedProjects = Keys.thisProject.value.aggregate - // Compute the highest compatibility level that is satisfied by all the aggregated projects - val maxCompatibility: Compatibility = Compatibility.BinaryAndSourceCompatible - aggregatedProjects.foldLeft(Def.task { maxCompatibility }) { (highestCompatibilityTask, project) => + aggregatedCompatibility(aggregatedProjects, log) { submodule => Def.task { - val highestCompatibility = highestCompatibilityTask.value - val compatibilities = (project / versionPolicyAssessCompatibility).value - // The most common case is to assess the compatibility with the latest release, - // so we look at the first element only and discard the others - compatibilities.headOption match { - case Some((_, compatibility)) => - log.debug(s"Compatibility of aggregated project ${project.project} is ${compatibility}") + (submodule / versionPolicyAssessCompatibility).value + } + } { compatibilities => + // The most common case is to assess the compatibility with the latest release, + // so we look at the first element only and discard the others + compatibilities.headOption.map(_._2) + }.map(_._1) // Discard submodules details + } + + // Compute the highest compatibility level that is satisfied by all the aggregated projects + private[sbtversionpolicy] def aggregatedCompatibility[A]( + submodules: Seq[ProjectRef], + log: Logger + )( + f: ProjectRef => Def.Initialize[Task[A]] + )( + compatibility: A => Option[Compatibility] + ): Def.Initialize[Task[(Compatibility, Seq[A])]] = + submodules.foldLeft( + Def.task { + (Compatibility.BinaryAndSourceCompatible: Compatibility, Seq.newBuilder[A]) + } + ) { case (highestCompatibilityAndResults, module) => + Def.task { + val (highestCompatibility, results) = highestCompatibilityAndResults.value + val result = f(module).value + compatibility(result) match { + case Some(compatibility) => + log.debug(s"Compatibility of aggregated project ${module.project} is ${compatibility}") + ( // Take the lowest of both - Compatibility.ordering.min(highestCompatibility, compatibility) - case None => - log.debug(s"Unable to assess the compatibility level of the aggregated project ${project.project}") - highestCompatibility - } + Compatibility.ordering.min(highestCompatibility, compatibility), + results += result + ) + case None => + log.debug(s"Unable to assess the compatibility level of the aggregated project ${module.project}") + (highestCompatibility, results) } } + }.map { case (compatibility, builder) => + (compatibility, builder.result()) } } diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala index c4f575f..074164c 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/SbtVersionPolicySettings.scala @@ -2,7 +2,7 @@ package sbtversionpolicy import com.typesafe.tools.mima.core.Problem import com.typesafe.tools.mima.plugin.MimaPlugin -import coursier.version.{ModuleMatchers, Version, VersionCompatibility} +import coursier.version.{ModuleMatchers, VersionCompatibility} import sbt.* import sbt.Keys.* import sbt.librarymanagement.CrossVersion @@ -168,6 +168,7 @@ object SbtVersionPolicySettings { } } }, + versionPolicyReportDependencyIssues := { val log = streams.value.log val sv = scalaVersion.value @@ -213,6 +214,7 @@ object SbtVersionPolicySettings { } } }, + versionCheck := Def.ifS((versionCheck / skip).toTask)(Def.task { () })(Def.task { @@ -240,12 +242,14 @@ object SbtVersionPolicySettings { throw new MessageOnlyException(s"Module ${moduleName} has a declared version number ${versionValue} that does not conform to its declared versionPolicyIntention of ${intention}. $detail") } }).value, + versionPolicyCheck := Def.ifS((versionPolicyCheck / skip).toTask)(Def.task { () })(Def.task { val ignored1 = versionPolicyMimaCheck.value val ignored2 = versionPolicyReportDependencyIssues.value }).value, + // For every previous module, returns a list of problems paired with the type of incompatibility versionPolicyFindMimaIssues := Def.taskDyn[Seq[(ModuleID, Seq[(IncompatibilityType, Problem)])]] { val compatibility = @@ -266,6 +270,7 @@ object SbtVersionPolicySettings { } } }.value, + versionPolicyMimaCheck := Def.taskDyn { import Compatibility.* val compatibility = @@ -313,6 +318,7 @@ object SbtVersionPolicySettings { } } }.value, + versionPolicyFindIssues := Def.ifS((versionPolicyFindIssues / skip).toTask)(Def.task { streams.value.log.debug("Not finding incompatibilities with previous releases because 'versionPolicyFindIssues / skip' is 'true'") Seq.empty[(ModuleID, (DependencyCheckReport, Seq[(IncompatibilityType, Problem)]))] @@ -338,6 +344,7 @@ object SbtVersionPolicySettings { } }) ).value, + versionPolicyAssessCompatibility := Def.ifS((versionPolicyAssessCompatibility / skip).toTask)(Def.task { streams.value.log.debug("Not assessing the compatibility with previous releases because 'versionPolicyAssessCompatibility / skip' is 'true'") Seq.empty[(ModuleID, Compatibility)] @@ -349,21 +356,60 @@ object SbtVersionPolicySettings { } val issues = versionPolicyFindIssues.value issues.map { case (previousRelease, (dependencyIssues, mimaIssues)) => - val compatibility = - if ( - dependencyIssues.validated(IncompatibilityType.SourceIncompatibility) && - mimaIssues.isEmpty - ) { - Compatibility.BinaryAndSourceCompatible - } else if ( - dependencyIssues.validated(IncompatibilityType.BinaryIncompatibility) && - !mimaIssues.exists(_._1 == IncompatibilityType.BinaryIncompatibility) - ) { - Compatibility.BinaryCompatible - } else { - Compatibility.None + previousRelease -> Compatibility.fromIssues(dependencyIssues, mimaIssues) + } + }).value, + + versionPolicyExportCompatibilityReport := { + val log = streams.value.log + val compatibilityReport = versionPolicyCollectCompatibilityReports.value + val targetFile = + versionPolicyCompatibilityReportPath.?.value + .getOrElse(crossTarget.value / "compatibility-report.json") + CompatibilityReport.write(targetFile, compatibilityReport, log) + }, + + versionPolicyCollectCompatibilityReports := Def.ifS(Def.task { + (versionPolicyCollectCompatibilityReports / skip).value + })(Def.task { + CompatibilityReport(None, None) + })(Def.taskDyn { + val module = thisProjectRef.value + val submodules = thisProject.value.aggregate + val log = streams.value.log + + // Compatibility report of the current module + val maybeModuleReport = + Def.task { + val issues = (module / versionPolicyFindIssues).value + if (issues.size > 1) { + log.warn(s"Ignoring compatibility reports with versions ${issues.drop(1).map(_._1.revision).mkString(", ")} for module ${issues.head._1.name}. Remove this warning by setting 'versionPolicyPreviousVersions' to a single previous version.") + } + issues.headOption.map { + case (previousRelease, (dependencyIssues, apiIssues)) => + val compatibility = Compatibility.fromIssues(dependencyIssues, apiIssues) + CompatibilityModuleReport(previousRelease, compatibility, dependencyIssues, apiIssues) + } + } + + // Compatibility reports of the aggregated modules (recursively computed) + val maybeAggregatedReports = Def.ifS[Option[(Compatibility, Seq[CompatibilityReport])]]({ + Def.task { submodules.isEmpty } + })(Def.task { + None + })(SbtVersionPolicyPlugin.aggregatedCompatibility(submodules, log) { submodule => + Def.task { + (submodule / versionPolicyCollectCompatibilityReports).value } - previousRelease -> compatibility + } { compatibilityReport => + compatibilityReport.moduleReport.map(_.compatibility) + }.map(Some(_))) + + Def.task { + CompatibilityReport( + maybeModuleReport.value, + maybeAggregatedReports.value + ) } }).value ) @@ -379,6 +425,13 @@ object SbtVersionPolicySettings { versionScheme := Some("early-semver") ) + val exportGlobalSettings: Seq[Def.Setting[?]] = Seq( + // Default [aggregation behavior](https://www.scala-sbt.org/1.x/docs/Multi-Project.html#Aggregation) + // is disabled for the “export” tasks because they handle + // their aggregated projects by themselves + versionPolicyExportCompatibilityReport / aggregate := false, + ) + /** All the modules (as pairs of organization name and artifact name) defined * by the current build definition, and whose version number matches the regex * defined by the key `versionPolicyIgnoredInternalDependencyVersions`. diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/build.sbt b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/build.sbt new file mode 100644 index 0000000..149530e --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/build.sbt @@ -0,0 +1,61 @@ +ThisBuild / organization := "com.example" +ThisBuild / scalaVersion := "2.13.2" + +val v1_a = + project + .settings( + name := "export-compatibility-report-test-a", + version := "1.0.0" + ) + +val v1_b = + project + .settings( + name := "export-compatibility-report-test-b", + version := "1.0.0" + ) + +val v1_c = + project + .settings( + name := "export-compatibility-report-test-c", + publish / skip := true + ) + +val v1_root = + project + .settings( + name := "export-compatibility-report-test-root", + publish / skip := true + ) + .aggregate(v1_a, v1_b, v1_c) + + +val v2_a = + project + .settings( + name := "export-compatibility-report-test-a", + version := "1.0.0+n" + ) + +val v2_b = + project + .settings( + name := "export-compatibility-report-test-b", + version := "1.0.0+n" + ) + +val v2_c = + project + .settings( + name := "export-compatibility-report-test-c", + publish / skip := true + ) + +val v2_root = + project + .settings( + name := "export-compatibility-report-test-root", + publish / skip := true + ) + .aggregate(v2_a, v2_b, v2_c) diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/expected-compatibility-report.json b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/expected-compatibility-report.json new file mode 100644 index 0000000..6825062 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/expected-compatibility-report.json @@ -0,0 +1,26 @@ +{ + "aggregated": { + "compatibility": { + "value": "incompatible", + "label": "Incompatible" + }, + "modules": [ + { + "module-name": "export-compatibility-report-test-a", + "previous-version": "1.0.0", + "compatibility": { + "value": "binary-compatible", + "label": "Binary compatible" + } + }, + { + "module-name": "export-compatibility-report-test-b", + "previous-version": "1.0.0", + "compatibility": { + "value": "incompatible", + "label": "Incompatible" + } + } + ] + } +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/project/plugins.sbt b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/project/plugins.sbt new file mode 100644 index 0000000..2843375 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % sys.props("plugin.version")) diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/test b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/test new file mode 100644 index 0000000..2383216 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/test @@ -0,0 +1,5 @@ +> v1_root/publishLocal + +> v2_root/versionPolicyExportCompatibilityReport + +$ must-mirror v2_root/target/scala-2.13/compatibility-report.json expected-compatibility-report.json diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_a/src/main/scala/pkg/A.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_a/src/main/scala/pkg/A.scala new file mode 100644 index 0000000..e6c552f --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_a/src/main/scala/pkg/A.scala @@ -0,0 +1,7 @@ +package pkg + +object A { + + val x: Int = 42 + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_b/src/main/scala/pkg/B.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_b/src/main/scala/pkg/B.scala new file mode 100644 index 0000000..b20f43c --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_b/src/main/scala/pkg/B.scala @@ -0,0 +1,5 @@ +package pkg + +object B { + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_c/src/main/scala/pkg/C.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_c/src/main/scala/pkg/C.scala new file mode 100644 index 0000000..396ece0 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v1_c/src/main/scala/pkg/C.scala @@ -0,0 +1,5 @@ +package pkg + +class C { + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_a/src/main/scala/pkg/A.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_a/src/main/scala/pkg/A.scala new file mode 100644 index 0000000..6bcfa92 --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_a/src/main/scala/pkg/A.scala @@ -0,0 +1,10 @@ +package pkg + +object A { + + val x: Int = 42 + + // Break source compatibility + val y: Int = 0 + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_b/src/main/scala/pkg/B.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_b/src/main/scala/pkg/B.scala new file mode 100644 index 0000000..1a02eff --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_b/src/main/scala/pkg/B.scala @@ -0,0 +1,6 @@ +package pkg + +// Break binary compatibility (changed object name) +object B2 { + +} diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_c/src/main/scala/pkg/C.scala b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_c/src/main/scala/pkg/C.scala new file mode 100644 index 0000000..54f073d --- /dev/null +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/export-compatibility-report/v2_c/src/main/scala/pkg/C.scala @@ -0,0 +1,6 @@ +package pkg + +// No changes +class C { + +}