From d6b9520e6411200dda4c1436b1346f5b722ce3fe Mon Sep 17 00:00:00 2001 From: Claire McGinty Date: Mon, 5 Dec 2022 10:30:31 -0500 Subject: [PATCH] Add target filters (#46) Support new targetSourcePackages/targetDestinationPackages settings in missinglink --- build.sbt | 2 +- .../sbtmissinglink/MissingLinkPlugin.scala | 90 ++++++++++++++++--- .../target-destination-package/build.sbt | 19 ++++ .../project/plugins.sbt | 8 ++ .../src/main/java/test/Foo.java | 5 ++ .../scala/test/ProblematicDependency.scala | 14 +++ .../target-destination-package/test | 3 + .../target-source-package/build.sbt | 18 ++++ .../target-source-package/project/plugins.sbt | 8 ++ .../src/main/java/test/Foo.java | 5 ++ .../scala/test/ProblematicDependency.scala | 14 +++ .../missinglink/target-source-package/test | 3 + 12 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 src/sbt-test/missinglink/target-destination-package/build.sbt create mode 100644 src/sbt-test/missinglink/target-destination-package/project/plugins.sbt create mode 100644 src/sbt-test/missinglink/target-destination-package/src/main/java/test/Foo.java create mode 100644 src/sbt-test/missinglink/target-destination-package/src/main/scala/test/ProblematicDependency.scala create mode 100644 src/sbt-test/missinglink/target-destination-package/test create mode 100644 src/sbt-test/missinglink/target-source-package/build.sbt create mode 100644 src/sbt-test/missinglink/target-source-package/project/plugins.sbt create mode 100644 src/sbt-test/missinglink/target-source-package/src/main/java/test/Foo.java create mode 100644 src/sbt-test/missinglink/target-source-package/src/main/scala/test/ProblematicDependency.scala create mode 100644 src/sbt-test/missinglink/target-source-package/test diff --git a/build.sbt b/build.sbt index a0f4991..96afc05 100644 --- a/build.sbt +++ b/build.sbt @@ -21,7 +21,7 @@ lazy val `sbt-missinglink` = project .enablePlugins(SbtPlugin) .settings( libraryDependencies ++= Seq( - "com.spotify" % "missinglink-core" % "0.2.5" + "com.spotify" % "missinglink-core" % "0.2.6" ), // configuration fro scripted scriptedLaunchOpts := { diff --git a/src/main/scala/ch/epfl/scala/sbtmissinglink/MissingLinkPlugin.scala b/src/main/scala/ch/epfl/scala/sbtmissinglink/MissingLinkPlugin.scala index 846ce41..0f804eb 100644 --- a/src/main/scala/ch/epfl/scala/sbtmissinglink/MissingLinkPlugin.scala +++ b/src/main/scala/ch/epfl/scala/sbtmissinglink/MissingLinkPlugin.scala @@ -25,6 +25,16 @@ object MissingLinkPlugin extends AutoPlugin { object autoImport { final case class IgnoredPackage(name: String, ignoreSubpackages: Boolean = true) + extends PackageFilter { + override def apply(packageName: String): Boolean = + packageName != name && (!ignoreSubpackages || !packageName.startsWith(s"$name.")) + } + + final case class TargetedPackage(name: String, targetSubpackages: Boolean = true) + extends PackageFilter { + override def apply(packageName: String): Boolean = + packageName == name || (targetSubpackages && packageName.startsWith(s"$name.")) + } val missinglinkCheck: TaskKey[Unit] = taskKey[Unit]("Run the missinglink checks") @@ -38,12 +48,24 @@ object MissingLinkPlugin extends AutoPlugin { "is in one of the specified packages." ) + val missinglinkTargetSourcePackages: SettingKey[Seq[TargetedPackage]] = + settingKey[Seq[TargetedPackage]]( + "Optional list of source packages to specifically target conflicts in. " + + "Cannot be used with missinglinkIgnoreSourcePackages." + ) + val missinglinkIgnoreDestinationPackages: SettingKey[Seq[IgnoredPackage]] = settingKey[Seq[IgnoredPackage]]( "Optional list of packages to ignore conflicts where the destination/called-side " + "of the conflict is in one of the specified packages." ) + val missinglinkTargetDestinationPackages: SettingKey[Seq[TargetedPackage]] = + settingKey[Seq[TargetedPackage]]( + "Optional list of source packages to specifically target conflicts in. " + + "Cannot be used with missinglinkIgnoreDestinationPackages." + ) + val missinglinkExcludedDependencies = settingKey[Seq[ModuleFilter]]("Dependencies that are excluded from analysis") } @@ -66,12 +88,30 @@ object MissingLinkPlugin extends AutoPlugin { val failOnConflicts = missinglinkFailOnConflicts.value val ignoreSourcePackages = missinglinkIgnoreSourcePackages.value val ignoreDestinationPackages = missinglinkIgnoreDestinationPackages.value + val targetSourcePackages = missinglinkTargetSourcePackages.value + val targetDestinationPackages = missinglinkTargetDestinationPackages.value + + assert( + ignoreSourcePackages.isEmpty || targetSourcePackages.isEmpty, + "ignoreSourcePackages and targetSourcePackages cannot be defined in the same project." + ) + + assert( + ignoreDestinationPackages.isEmpty || targetDestinationPackages.isEmpty, + "ignoreDestinationPackages and targetDestinationPackages cannot be defined in the same project." + ) + val filter = missinglinkExcludedDependencies.value.foldLeft[ModuleFilter](_ => true)((k, v) => k - v) val conflicts = loadArtifactsAndCheckConflicts(cp, classDir, filter, log) val filteredConflicts = - filterConflicts(conflicts, ignoreSourcePackages, ignoreDestinationPackages, log) + filterConflicts( + conflicts, + ignoreSourcePackages ++ targetSourcePackages, + ignoreDestinationPackages ++ targetDestinationPackages, + log + ) if (filteredConflicts.nonEmpty) { val initialTotal = conflicts.length @@ -100,7 +140,9 @@ object MissingLinkPlugin extends AutoPlugin { override def globalSettings: Seq[Def.Setting[_]] = Seq( missinglinkFailOnConflicts := true, missinglinkIgnoreSourcePackages := Nil, + missinglinkTargetSourcePackages := Nil, missinglinkIgnoreDestinationPackages := Nil, + missinglinkTargetDestinationPackages := Nil, missinglinkExcludedDependencies := Nil, ) @@ -212,32 +254,29 @@ object MissingLinkPlugin extends AutoPlugin { private def filterConflicts( conflicts: Seq[Conflict], - ignoreSourcePackages: Seq[IgnoredPackage], - ignoreDestinationPackages: Seq[IgnoredPackage], + sourceFilters: Seq[PackageFilter], + destinationFilters: Seq[PackageFilter], log: Logger ): Seq[Conflict] = { def filter( - ignoredPackages: Seq[IgnoredPackage], + packageFilters: Seq[PackageFilter], name: String, setting: String, field: Dependency => ClassTypeDescriptor ): Seq[Conflict] => Seq[Conflict] = { input => - if (ignoredPackages.nonEmpty) { - log.debug(s"Ignoring $name packages: ${ignoredPackages.mkString(", ")}") + if (packageFilters.nonEmpty) { + log.debug(s"Applying filters on $name packages: ${packageFilters.mkString(", ")}") - def isIgnored(conflict: Conflict): Boolean = { + def isFiltered(conflict: Conflict): Boolean = { val descriptor = field(conflict.dependency()) val className = descriptor.getClassName.replace('/', '.') val conflictPackageName = className.substring(0, className.lastIndexOf('.')) - ignoredPackages.exists { p => - conflictPackageName == p.name || - (p.ignoreSubpackages && conflictPackageName.startsWith(p.name + ".")) - } + packageFilters.exists(_.apply(conflictPackageName)) } - val filtered = input.filterNot(isIgnored) + val filtered = input.filter(isFiltered) val diff = input.length - filtered.length if (diff != 0) { @@ -256,8 +295,30 @@ object MissingLinkPlugin extends AutoPlugin { } val filters = List( - filter(ignoreSourcePackages, "source", "ignoreSourcePackages", _.fromClass), - filter(ignoreDestinationPackages, "destination", "ignoreDestinationPackages", _.targetClass) + filter( + sourceFilters.collect { case p: IgnoredPackage => p }, + "source", + "ignoreSourcePackages", + _.fromClass + ), + filter( + sourceFilters.collect { case p: TargetedPackage => p }, + "source", + "targetSourcePackages", + _.fromClass + ), + filter( + destinationFilters.collect { case p: IgnoredPackage => p }, + "destination", + "ignoreSourcePackages", + _.targetClass + ), + filter( + destinationFilters.collect { case p: TargetedPackage => p }, + "destination", + "targetDestinationPackages", + _.targetClass + ) ) Function.chain(filters).apply(conflicts) @@ -317,4 +378,5 @@ object MissingLinkPlugin extends AutoPlugin { private final case class ModuleArtifact(artifact: Artifact, module: Option[ModuleID]) + private[sbtmissinglink] sealed trait PackageFilter extends (String => Boolean) } diff --git a/src/sbt-test/missinglink/target-destination-package/build.sbt b/src/sbt-test/missinglink/target-destination-package/build.sbt new file mode 100644 index 0000000..6c4d715 --- /dev/null +++ b/src/sbt-test/missinglink/target-destination-package/build.sbt @@ -0,0 +1,19 @@ +inThisBuild(Def.settings( + version := "0.1.0", + scalaVersion := "2.12.8", +)) + +lazy val `target-destination-package` = project + .in(file(".")) + .settings( + libraryDependencies ++= Seq( + "com.google.guava" % "guava" % "14.0", + "com.google.guava" % "guava" % "18.0" % Runtime, + ), + + // Speed up compilation a bit. Our .java files do not need to see the .scala files. + compileOrder := CompileOrder.JavaThenScala, + + // Will ignore Guava conflict + missinglinkTargetDestinationPackages += TargetedPackage("test") + ) diff --git a/src/sbt-test/missinglink/target-destination-package/project/plugins.sbt b/src/sbt-test/missinglink/target-destination-package/project/plugins.sbt new file mode 100644 index 0000000..1f345f5 --- /dev/null +++ b/src/sbt-test/missinglink/target-destination-package/project/plugins.sbt @@ -0,0 +1,8 @@ +System.getProperty("plugin.version") match { + case null => + throw new MessageOnlyException( + "The system property 'plugin.version' is not defined. " + + "Specify this property using the scriptedLaunchOpts -D.") + case pluginVersion => + addSbtPlugin("ch.epfl.scala" % "sbt-missinglink" % pluginVersion) +} diff --git a/src/sbt-test/missinglink/target-destination-package/src/main/java/test/Foo.java b/src/sbt-test/missinglink/target-destination-package/src/main/java/test/Foo.java new file mode 100644 index 0000000..5626906 --- /dev/null +++ b/src/sbt-test/missinglink/target-destination-package/src/main/java/test/Foo.java @@ -0,0 +1,5 @@ +package test; + +public enum Foo { + BAR +} diff --git a/src/sbt-test/missinglink/target-destination-package/src/main/scala/test/ProblematicDependency.scala b/src/sbt-test/missinglink/target-destination-package/src/main/scala/test/ProblematicDependency.scala new file mode 100644 index 0000000..164c0fa --- /dev/null +++ b/src/sbt-test/missinglink/target-destination-package/src/main/scala/test/ProblematicDependency.scala @@ -0,0 +1,14 @@ +package test + +import com.google.common.base.Enums + +/** + * Calls a method in the Guava Enums class which was removed in guava 18. If a project calls this + * method while overriding Guava to >= 18, it will cause a NoSuchMethodError at runtime. + */ +object ProblematicDependency { + + def reliesOnRemovedMethod(): AnyRef = { + Enums.valueOfFunction(classOf[Foo]) + } +} diff --git a/src/sbt-test/missinglink/target-destination-package/test b/src/sbt-test/missinglink/target-destination-package/test new file mode 100644 index 0000000..2a927be --- /dev/null +++ b/src/sbt-test/missinglink/target-destination-package/test @@ -0,0 +1,3 @@ +> compile +> compile:missinglinkCheck +> runtime:missinglinkCheck diff --git a/src/sbt-test/missinglink/target-source-package/build.sbt b/src/sbt-test/missinglink/target-source-package/build.sbt new file mode 100644 index 0000000..3f9eb2e --- /dev/null +++ b/src/sbt-test/missinglink/target-source-package/build.sbt @@ -0,0 +1,18 @@ +inThisBuild(Def.settings( + version := "0.1.0", + scalaVersion := "2.12.8", +)) + +lazy val `target-source-package` = project + .in(file(".")) + .settings( + libraryDependencies ++= Seq( + "com.google.guava" % "guava" % "14.0", + "com.google.guava" % "guava" % "18.0" % Runtime, + ), + + // Speed up compilation a bit. Our .java files do not need to see the .scala files. + compileOrder := CompileOrder.JavaThenScala, + + missinglinkTargetSourcePackages += TargetedPackage("test") + ) diff --git a/src/sbt-test/missinglink/target-source-package/project/plugins.sbt b/src/sbt-test/missinglink/target-source-package/project/plugins.sbt new file mode 100644 index 0000000..1f345f5 --- /dev/null +++ b/src/sbt-test/missinglink/target-source-package/project/plugins.sbt @@ -0,0 +1,8 @@ +System.getProperty("plugin.version") match { + case null => + throw new MessageOnlyException( + "The system property 'plugin.version' is not defined. " + + "Specify this property using the scriptedLaunchOpts -D.") + case pluginVersion => + addSbtPlugin("ch.epfl.scala" % "sbt-missinglink" % pluginVersion) +} diff --git a/src/sbt-test/missinglink/target-source-package/src/main/java/test/Foo.java b/src/sbt-test/missinglink/target-source-package/src/main/java/test/Foo.java new file mode 100644 index 0000000..5626906 --- /dev/null +++ b/src/sbt-test/missinglink/target-source-package/src/main/java/test/Foo.java @@ -0,0 +1,5 @@ +package test; + +public enum Foo { + BAR +} diff --git a/src/sbt-test/missinglink/target-source-package/src/main/scala/test/ProblematicDependency.scala b/src/sbt-test/missinglink/target-source-package/src/main/scala/test/ProblematicDependency.scala new file mode 100644 index 0000000..164c0fa --- /dev/null +++ b/src/sbt-test/missinglink/target-source-package/src/main/scala/test/ProblematicDependency.scala @@ -0,0 +1,14 @@ +package test + +import com.google.common.base.Enums + +/** + * Calls a method in the Guava Enums class which was removed in guava 18. If a project calls this + * method while overriding Guava to >= 18, it will cause a NoSuchMethodError at runtime. + */ +object ProblematicDependency { + + def reliesOnRemovedMethod(): AnyRef = { + Enums.valueOfFunction(classOf[Foo]) + } +} diff --git a/src/sbt-test/missinglink/target-source-package/test b/src/sbt-test/missinglink/target-source-package/test new file mode 100644 index 0000000..ad0128e --- /dev/null +++ b/src/sbt-test/missinglink/target-source-package/test @@ -0,0 +1,3 @@ +> compile +> compile:missinglinkCheck +-> runtime:missinglinkCheck