From ab213dacbb104406cc51948a664c0821b58690be Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 May 2020 14:32:40 -0400 Subject: [PATCH] split to Jar Jar Abrams Jar Jar Abrams is an experimental Scala extension of Jar Jar Links. --- .scalafmt.conf | 22 ++ .travis.yml | 18 ++ README.markdown | 18 ++ build.sbt | 52 +++++ .../jarjarabrams/ModuleCoordinate.scala | 41 ++++ .../com/eed3si9n/jarjarabrams/ShadeRule.scala | 47 ++++ .../jarjarabrams/ShadeRuleDelegate.scala | 36 +++ .../eed3si9n/jarjarabrams/ShadeTarget.scala | 52 +++++ core/src/main/contraband/jarjar.contra | 46 ++++ .../com/eed3si9n/jarjarabrams/Shader.scala | 81 +++++++ .../scalasig/ByteArrayReader.scala | 5 +- .../jarjarabrams}/scalasig/EntryTable.scala | 21 +- .../eed3si9n/jarjarabrams}/scalasig/Nat.scala | 5 +- .../scalasig/ScalaSigAnnotationVisitor.scala | 26 ++- .../jarjarabrams/scalasig/TaggedEntry.scala | 65 ++++++ .../org/pantsbuild/jarjar/JJProcessor.scala | 64 ++++-- .../pantsbuild/jarjar/ScalaSigProcessor.scala | 6 +- .../test/scala/testpkg/EntryTableSpec.scala | 78 +++++++ project/Dependencies.scala | 10 + project/build.properties | 1 + project/plugins.sbt | 3 + .../sbtjarjarabrams/JarjarAbramsKeys.scala | 17 ++ .../sbtjarjarabrams/JarjarAbramsPlugin.scala | 213 ++++++++++++++++++ .../sbtjarjarabrams/ShadeRuleBuilder.scala | 12 + .../src/sbt-test/actions/simple/build.sbt | 13 ++ .../actions/simple/project/plugins.sbt | 5 + sbtplugin/src/sbt-test/actions/simple/test | 4 + .../sbt-test/actions/simple/use/Test.scala | 213 ++++++++++++++++++ sbtplugin/src/sbt-test/actions/two/build.sbt | 22 ++ .../sbt-test/actions/two/project/plugins.sbt | 5 + sbtplugin/src/sbt-test/actions/two/test | 4 + .../src/sbt-test/actions/two/use/Test.scala | 10 + src/main/scala/sbtassembly/Shader.scala | 109 --------- .../sbtassembly/scalasig/TaggedEntry.scala | 64 ------ .../sbtassembly/scalasig/EntryTableSpec.scala | 82 ------- 35 files changed, 1170 insertions(+), 300 deletions(-) create mode 100644 .scalafmt.conf create mode 100644 .travis.yml create mode 100644 README.markdown create mode 100644 build.sbt create mode 100644 core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ModuleCoordinate.scala create mode 100644 core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRule.scala create mode 100644 core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRuleDelegate.scala create mode 100644 core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeTarget.scala create mode 100644 core/src/main/contraband/jarjar.contra create mode 100644 core/src/main/scala/com/eed3si9n/jarjarabrams/Shader.scala rename {src/main/scala/sbtassembly => core/src/main/scala/com/eed3si9n/jarjarabrams}/scalasig/ByteArrayReader.scala (90%) rename {src/main/scala/sbtassembly => core/src/main/scala/com/eed3si9n/jarjarabrams}/scalasig/EntryTable.scala (88%) rename {src/main/scala/sbtassembly => core/src/main/scala/com/eed3si9n/jarjarabrams}/scalasig/Nat.scala (96%) rename {src/main/scala/sbtassembly => core/src/main/scala/com/eed3si9n/jarjarabrams}/scalasig/ScalaSigAnnotationVisitor.scala (87%) create mode 100644 core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/TaggedEntry.scala rename {src => core/src}/main/scala/org/pantsbuild/jarjar/JJProcessor.scala (61%) rename {src => core/src}/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala (74%) create mode 100644 core/src/test/scala/testpkg/EntryTableSpec.scala create mode 100644 project/Dependencies.scala create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100644 sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsKeys.scala create mode 100644 sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsPlugin.scala create mode 100644 sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/ShadeRuleBuilder.scala create mode 100644 sbtplugin/src/sbt-test/actions/simple/build.sbt create mode 100644 sbtplugin/src/sbt-test/actions/simple/project/plugins.sbt create mode 100644 sbtplugin/src/sbt-test/actions/simple/test create mode 100644 sbtplugin/src/sbt-test/actions/simple/use/Test.scala create mode 100644 sbtplugin/src/sbt-test/actions/two/build.sbt create mode 100644 sbtplugin/src/sbt-test/actions/two/project/plugins.sbt create mode 100644 sbtplugin/src/sbt-test/actions/two/test create mode 100644 sbtplugin/src/sbt-test/actions/two/use/Test.scala delete mode 100644 src/main/scala/sbtassembly/Shader.scala delete mode 100644 src/main/scala/sbtassembly/scalasig/TaggedEntry.scala delete mode 100644 src/test/scala/sbtassembly/scalasig/EntryTableSpec.scala diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..47e13ff4 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,22 @@ +version = 2.3.2 +edition = 2019-10 +maxColumn = 100 +project.git = true +project.excludeFilters = [ "\\Wsbt-test\\W", "\\Winput_sources\\W", "\\Wcontraband-scala\\W" ] + +# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style. +# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala +docstrings = JavaDoc + +# This also seems more idiomatic to include whitespace in import x.{ yyy } +spaces.inImportCurlyBraces = true + +# This is more idiomatic Scala. +# http://docs.scala-lang.org/style/indentation.html#methods-with-numerous-arguments +align.openParenCallSite = false +align.openParenDefnSite = false + +# For better code clarity +danglingParentheses = true + +trailingCommas = preserve diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..70e90956 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +dist: xenial + +language: scala + +script: + - sbt "+test" "scripted" + +before_cache: + - find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete + - find $HOME/.ivy2 -name "ivydata-*.properties" -delete + - find $HOME/.sbt -name "*.lock" -delete + +cache: + directories: + - $HOME/.cache/coursier/v1 + - $HOME/.ivy2/cache + - $HOME/.sbt/boot + - $HOME/.jabba diff --git a/README.markdown b/README.markdown new file mode 100644 index 00000000..55585265 --- /dev/null +++ b/README.markdown @@ -0,0 +1,18 @@ +Jar Jar Abrams +============== + +Jar Jar Abrams is an experimental Scala extension of [Jar Jar Links][links] a utility to shade Java libraries. + +## License + +Licensed under the Apache License, Version 2.0. + +## Credits + +- [Jar Jar Links][links] was created by herbyderby (Chris Nokleberg) in 2004. +- Pants build team has been maintaining a fork [pantsbuild/jarjar][pj] since 2015. +- In 2015, Wu Xiang added shading support in [sbt-assembly#162](https://github.com/sbt/sbt-assembly/pull/162). +- In 2020, Jeroen ter Voorde added Scala signature processor in [sbt-assembly#393](https://github.com/sbt/sbt-assembly/pull/393). + + [links]: https://code.google.com/archive/p/jarjar/ + [pj]: https://github.com/pantsbuild/jarjar diff --git a/build.sbt b/build.sbt new file mode 100644 index 00000000..4389699e --- /dev/null +++ b/build.sbt @@ -0,0 +1,52 @@ +import Dependencies._ + +ThisBuild / scalaVersion := scala212 +ThisBuild / organization := "com.eed3si9n.jarjarabrams" +ThisBuild / version := "0.1.0-SNAPSHOT" +ThisBuild / description := "utility to shade Scala libraries" +ThisBuild / licenses := Seq("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt")) + +lazy val core = project + .enablePlugins(ContrabandPlugin) + .settings(nocomma { + name := "jarjar-abrams-core" + + crossScalaVersions := Vector(scala212, scala213, scala211, scala210) + + libraryDependencies += jarjar + libraryDependencies ++= { + if (scalaVersion.value.startsWith("2.10.")) Nil + else Vector(verify % Test) + } + libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value + + Compile / managedSourceDirectories += (Compile / generateContrabands / sourceManaged).value + Compile / generateContrabands / sourceManaged := baseDirectory.value / "src" / "main" / "contraband-scala" + Test / sources := { + val orig = (Test / sources).value + if (scalaVersion.value.startsWith("2.10.")) Nil + else orig + } + + testFrameworks += new TestFramework("verify.runner.Framework") + + Compile / scalacOptions ++= { + if (scalaVersion.value.startsWith("2.13.")) Vector("-Xlint") + else Vector("-Xlint", "-Xfatal-warnings") + } + }) + +lazy val sbtplugin = project + .enablePlugins(SbtPlugin) + .dependsOn(core) + .settings(nocomma { + name := "sbt-jarjar-abrams" + + Compile / scalacOptions ++= Vector("-Xlint", "-Xfatal-warnings") + + scriptedLaunchOpts := { + scriptedLaunchOpts.value ++ + Vector("-Xmx1024M", "-Dplugin.version=" + version.value) + } + scriptedBufferLog := false + }) diff --git a/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ModuleCoordinate.scala b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ModuleCoordinate.scala new file mode 100644 index 00000000..64d0693b --- /dev/null +++ b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ModuleCoordinate.scala @@ -0,0 +1,41 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package com.eed3si9n.jarjarabrams +/** stand-in for sbt's ModuleID */ +final class ModuleCoordinate private ( + val organization: String, + val name: String, + val version: String) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ModuleCoordinate => (this.organization == x.organization) && (this.name == x.name) && (this.version == x.version) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (17 + "com.eed3si9n.jarjarabrams.ModuleCoordinate".##) + organization.##) + name.##) + version.##) + } + override def toString: String = { + "ModuleCoordinate(" + organization + ", " + name + ", " + version + ")" + } + private[this] def copy(organization: String = organization, name: String = name, version: String = version): ModuleCoordinate = { + new ModuleCoordinate(organization, name, version) + } + def withOrganization(organization: String): ModuleCoordinate = { + copy(organization = organization) + } + def withName(name: String): ModuleCoordinate = { + copy(name = name) + } + def withVersion(version: String): ModuleCoordinate = { + copy(version = version) + } +} +object ModuleCoordinate { + + def apply(organization: String, name: String, version: String): ModuleCoordinate = new ModuleCoordinate(organization, name, version) +} diff --git a/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRule.scala b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRule.scala new file mode 100644 index 00000000..bfd10722 --- /dev/null +++ b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRule.scala @@ -0,0 +1,47 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package com.eed3si9n.jarjarabrams +final class ShadeRule private ( + val shadePattern: com.eed3si9n.jarjarabrams.ShadePattern, + val targets: Vector[ShadeTarget]) extends Serializable { + def inAll: ShadeRule = this.withTargets(targets :+ ShadeTarget.inAll) + def inProject: ShadeRule = this.withTargets(targets :+ ShadeTarget.inProject) + def inModuleCoordinates(moduleId: ModuleCoordinate*): ShadeRule = + this.withTargets(targets ++ (moduleId.toSeq map ShadeTarget.inModuleCoordinate)) + def isApplicableToAll: Boolean = targets.exists(_.inAll) + def isApplicableToCompiling: Boolean = targets.exists(_.inAll) || targets.exists(_.inProject) + def isApplicableTo(mod: ModuleCoordinate): Boolean = + targets.exists(_.inAll) || targets.exists(_.isApplicableTo(mod)) + + + override def equals(o: Any): Boolean = o match { + case x: ShadeRule => (this.shadePattern == x.shadePattern) && (this.targets == x.targets) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "com.eed3si9n.jarjarabrams.ShadeRule".##) + shadePattern.##) + targets.##) + } + override def toString: String = { + "ShadeRule(" + shadePattern + ", " + targets + ")" + } + private[this] def copy(shadePattern: com.eed3si9n.jarjarabrams.ShadePattern = shadePattern, targets: Vector[ShadeTarget] = targets): ShadeRule = { + new ShadeRule(shadePattern, targets) + } + def withShadePattern(shadePattern: com.eed3si9n.jarjarabrams.ShadePattern): ShadeRule = { + copy(shadePattern = shadePattern) + } + def withTargets(targets: Vector[ShadeTarget]): ShadeRule = { + copy(targets = targets) + } +} +object ShadeRule { + import ShadePattern._ + def rename(patterns: (String, String)*): ShadePattern = Rename(patterns.toSeq.toList) + def moveUnder(from: String, to: String): ShadePattern = rename(s"$from.**" -> s"$to.$from.@1") + def zap(patterns: String*): ShadePattern = Zap(patterns.toSeq.toList) + def keep(patterns: String*): ShadePattern = Keep(patterns.toSeq.toList) + def apply(shadePattern: com.eed3si9n.jarjarabrams.ShadePattern, targets: Vector[ShadeTarget]): ShadeRule = new ShadeRule(shadePattern, targets) +} diff --git a/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRuleDelegate.scala b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRuleDelegate.scala new file mode 100644 index 00000000..8a081e51 --- /dev/null +++ b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeRuleDelegate.scala @@ -0,0 +1,36 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package com.eed3si9n.jarjarabrams +final class ShadeRule private ( + val shadePattern: com.eed3si9n.jarjarabrams.ShadePattern, + val targets: Vector[ShadeTarget]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ShadeRule => (this.shadePattern == x.shadePattern) && (this.targets == x.targets) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "com.eed3si9n.jarjarabrams.ShadeRule".##) + shadePattern.##) + targets.##) + } + override def toString: String = { + "ShadeRule(" + shadePattern + ", " + targets + ")" + } + private[this] def copy(shadePattern: com.eed3si9n.jarjarabrams.ShadePattern = shadePattern, targets: Vector[ShadeTarget] = targets): ShadeRule = { + new ShadeRule(shadePattern, targets) + } + def withShadePattern(shadePattern: com.eed3si9n.jarjarabrams.ShadePattern): ShadeRule = { + copy(shadePattern = shadePattern) + } + def withTargets(targets: Vector[ShadeTarget]): ShadeRule = { + copy(targets = targets) + } +} +object ShadeRule { + + def apply(shadePattern: com.eed3si9n.jarjarabrams.ShadePattern, targets: Vector[ShadeTarget]): ShadeRule = new ShadeRule(shadePattern, targets) +} diff --git a/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeTarget.scala b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeTarget.scala new file mode 100644 index 00000000..a26fac69 --- /dev/null +++ b/core/src/main/contraband-scala/com/eed3si9n/jarjarabrams/ShadeTarget.scala @@ -0,0 +1,52 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package com.eed3si9n.jarjarabrams +/** + * This is a categorization to denote which rules are applied to what. + * Used internally in sbt-assembly. There's nothing in Shader.shadeDirectory + * that would enforce these target categorization. + */ +final class ShadeTarget private ( + val inAll: Boolean, + val inProject: Boolean, + val moduleId: Option[ModuleCoordinate]) extends Serializable { + def isApplicableTo(mod: ModuleCoordinate): Boolean = inAll || (moduleId == Some(mod)) + + + override def equals(o: Any): Boolean = o match { + case x: ShadeTarget => (this.inAll == x.inAll) && (this.inProject == x.inProject) && (this.moduleId == x.moduleId) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (17 + "com.eed3si9n.jarjarabrams.ShadeTarget".##) + inAll.##) + inProject.##) + moduleId.##) + } + override def toString: String = { + "ShadeTarget(" + inAll + ", " + inProject + ", " + moduleId + ")" + } + private[this] def copy(inAll: Boolean = inAll, inProject: Boolean = inProject, moduleId: Option[ModuleCoordinate] = moduleId): ShadeTarget = { + new ShadeTarget(inAll, inProject, moduleId) + } + def withInAll(inAll: Boolean): ShadeTarget = { + copy(inAll = inAll) + } + def withInProject(inProject: Boolean): ShadeTarget = { + copy(inProject = inProject) + } + def withModuleId(moduleId: Option[ModuleCoordinate]): ShadeTarget = { + copy(moduleId = moduleId) + } + def withModuleId(moduleId: ModuleCoordinate): ShadeTarget = { + copy(moduleId = Option(moduleId)) + } +} +object ShadeTarget { + private[jarjarabrams] def inAll: ShadeTarget = ShadeTarget(inAll = true, inProject = false, None) + private[jarjarabrams] def inProject: ShadeTarget = ShadeTarget(inAll = false, inProject = true, None) + private[jarjarabrams] def inModuleCoordinate(moduleId: ModuleCoordinate): ShadeTarget = + ShadeTarget(inAll = false, inProject = false, moduleId = Some(moduleId)) + def apply(inAll: Boolean, inProject: Boolean, moduleId: Option[ModuleCoordinate]): ShadeTarget = new ShadeTarget(inAll, inProject, moduleId) + def apply(inAll: Boolean, inProject: Boolean, moduleId: ModuleCoordinate): ShadeTarget = new ShadeTarget(inAll, inProject, Option(moduleId)) +} diff --git a/core/src/main/contraband/jarjar.contra b/core/src/main/contraband/jarjar.contra new file mode 100644 index 00000000..489c94cf --- /dev/null +++ b/core/src/main/contraband/jarjar.contra @@ -0,0 +1,46 @@ +package com.eed3si9n.jarjarabrams +@target(Scala) + +type ShadeRule { + shadePattern: com.eed3si9n.jarjarabrams.ShadePattern! + targets: [ShadeTarget] + + #x def inAll: ShadeRule = this.withTargets(targets :+ ShadeTarget.inAll) + #x def inProject: ShadeRule = this.withTargets(targets :+ ShadeTarget.inProject) + #x def inModuleCoordinates(moduleId: ModuleCoordinate*): ShadeRule = + #x this.withTargets(targets ++ (moduleId.toSeq map ShadeTarget.inModuleCoordinate)) + + #x def isApplicableToAll: Boolean = targets.exists(_.inAll) + #x def isApplicableToCompiling: Boolean = targets.exists(_.inAll) || targets.exists(_.inProject) + #x def isApplicableTo(mod: ModuleCoordinate): Boolean = + #x targets.exists(_.inAll) || targets.exists(_.isApplicableTo(mod)) + + #xcompanion import ShadePattern._ + #xcompanion def rename(patterns: (String, String)*): ShadePattern = Rename(patterns.toSeq.toList) + #xcompanion def moveUnder(from: String, to: String): ShadePattern = rename(s"$from.**" -> s"$to.$from.@1") + #xcompanion def zap(patterns: String*): ShadePattern = Zap(patterns.toSeq.toList) + #xcompanion def keep(patterns: String*): ShadePattern = Keep(patterns.toSeq.toList) +} + +## This is a categorization to denote which rules are applied to what. +## Used internally in sbt-assembly. There's nothing in Shader.shadeDirectory +## that would enforce these target categorization. +type ShadeTarget { + inAll: Boolean! = false + inProject: Boolean! = false + moduleId: ModuleCoordinate + + #x def isApplicableTo(mod: ModuleCoordinate): Boolean = inAll || (moduleId == Some(mod)) + + #xcompanion private[jarjarabrams] def inAll: ShadeTarget = ShadeTarget(inAll = true, inProject = false, None) + #xcompanion private[jarjarabrams] def inProject: ShadeTarget = ShadeTarget(inAll = false, inProject = true, None) + #xcompanion private[jarjarabrams] def inModuleCoordinate(moduleId: ModuleCoordinate): ShadeTarget = + #xcompanion ShadeTarget(inAll = false, inProject = false, moduleId = Some(moduleId)) +} + +## stand-in for sbt's ModuleID +type ModuleCoordinate { + organization: String! + name: String! + version: String! +} diff --git a/core/src/main/scala/com/eed3si9n/jarjarabrams/Shader.scala b/core/src/main/scala/com/eed3si9n/jarjarabrams/Shader.scala new file mode 100644 index 00000000..34a115ee --- /dev/null +++ b/core/src/main/scala/com/eed3si9n/jarjarabrams/Shader.scala @@ -0,0 +1,81 @@ +package com.eed3si9n.jarjarabrams + +import java.nio.file.{ Files, Path, StandardOpenOption } +import org.pantsbuild.jarjar.{ JJProcessor, _ } +import org.pantsbuild.jarjar.util.EntryStruct + +object Shader { + def shadeDirectory( + rules: Seq[ShadeRule], + dir: Path, + mappings: Seq[(Path, String)], + verbose: Boolean + ): Unit = { + val jjrules = rules flatMap { r => + r.shadePattern match { + case ShadePattern.Rename(patterns) => + patterns.map { + case (from, to) => + val jrule = new Rule() + jrule.setPattern(from) + jrule.setResult(to) + jrule + } + case ShadePattern.Zap(patterns) => + patterns.map { + case pattern => + val jrule = new Zap() + jrule.setPattern(pattern) + jrule + } + case ShadePattern.Keep(patterns) => + patterns.map { + case pattern => + val jrule = new Keep() + jrule.setPattern(pattern) + jrule + } + case _ => Nil + } + } + + val proc = new JJProcessor(jjrules, verbose, true, null) + + /* + jarjar MisplacedClassProcessor class transforms byte[] to a class using org.objectweb.asm.ClassReader.getClassName + which always translates class names containing '.' into '/', regardless of OS platform. + We need to transform any windows file paths in order for jarjar to match them properly and not omit them. + */ + val files = mappings.map(f => if (f._2.contains('\\')) (f._1, f._2.replace('\\', '/')) else f) + val entry = new EntryStruct + files.filter(x => !Files.isDirectory(x._1)) foreach { f => + entry.data = Files.readAllBytes(f._1) + entry.name = f._2 + entry.time = -1 + entry.skipTransform = false + Files.delete(f._1) + if (proc.process(entry)) { + val out = dir.resolve(entry.name) + if (!Files.exists(out.getParent)) { + Files.createDirectories(out.getParent) + } + Files.write(out, entry.data, StandardOpenOption.CREATE) + } + } + val excludes = proc.getExcludes + excludes.foreach(exclude => Files.delete(dir.resolve(exclude))) + } +} + +sealed trait ShadePattern { + def inAll: ShadeRule = ShadeRule(this, Vector(ShadeTarget.inAll)) + def inProject: ShadeRule = ShadeRule(this, Vector(ShadeTarget.inProject)) + def inModuleCoordinates(moduleId: ModuleCoordinate*): ShadeRule = + ShadeRule(this, moduleId.toVector map ShadeTarget.inModuleCoordinate) +} + +object ShadePattern { + case class Rename(patterns: List[(String, String)]) extends ShadePattern + case class Zap(patterns: List[String]) extends ShadePattern + case class Keep(patterns: List[String]) extends ShadePattern +} diff --git a/src/main/scala/sbtassembly/scalasig/ByteArrayReader.scala b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/ByteArrayReader.scala similarity index 90% rename from src/main/scala/sbtassembly/scalasig/ByteArrayReader.scala rename to core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/ByteArrayReader.scala index f2d04fd2..b626b1f7 100644 --- a/src/main/scala/sbtassembly/scalasig/ByteArrayReader.scala +++ b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/ByteArrayReader.scala @@ -1,4 +1,5 @@ -package sbtassembly.scalasig +package com.eed3si9n.jarjarabrams +package scalasig // Utility class to read the content of a single table entry class ByteArrayReader(bytes: Array[Byte]) extends Nat.Reader { @@ -19,4 +20,4 @@ class ByteArrayReader(bytes: Array[Byte]) extends Nat.Reader { } def atEnd: Boolean = readIndex == bytes.length -} \ No newline at end of file +} diff --git a/src/main/scala/sbtassembly/scalasig/EntryTable.scala b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/EntryTable.scala similarity index 88% rename from src/main/scala/sbtassembly/scalasig/EntryTable.scala rename to core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/EntryTable.scala index 41bbd39f..5d5b6bca 100644 --- a/src/main/scala/sbtassembly/scalasig/EntryTable.scala +++ b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/EntryTable.scala @@ -1,4 +1,5 @@ -package sbtassembly.scalasig +package com.eed3si9n.jarjarabrams +package scalasig import java.io.ByteArrayOutputStream @@ -12,11 +13,13 @@ import scala.reflect.internal.pickling.PickleFormat * @param entries initial table entries */ class EntryTable(majorVersion: Int, minorVersion: Int, entries: mutable.Buffer[TaggedEntry]) { + import TaggedEntry._ + // Mapping of known TermName or TypeNames to their index in the table. private val nameIndices: mutable.Map[NameEntry, Int] = mutable.HashMap( - entries.zipWithIndex.collect { + (entries.zipWithIndex.collect { case (entry: NameEntry, index) => (entry, index) - }:_* + }).toSeq: _* ) /** @@ -50,7 +53,10 @@ class EntryTable(majorVersion: Int, minorVersion: Int, entries: mutable.Buffer[T Some(nextOwner) } - entries(index) = ref.copy(nameRef = getOrAppendNameEntry(nameEntry.copy(name = parts.last)), ownerRef = myOwner) + entries(index) = ref.copy( + nameRef = getOrAppendNameEntry(nameEntry.copy(name = parts.last)), + ownerRef = myOwner + ) } case other => @@ -82,7 +88,8 @@ class EntryTable(majorVersion: Int, minorVersion: Int, entries: mutable.Buffer[T val myName = entries(extMod.nameRef) match { case term: NameEntry => term.name - case raw: RawEntry => throw new RuntimeException(s"Unexpected raw type for nameref ${raw.tag}") + case raw: RawEntry => + throw new RuntimeException(s"Unexpected raw type for nameref ${raw.tag}") case other => throw new RuntimeException(s"Unexpected type for nameref $other") } extMod.ownerRef match { @@ -116,8 +123,8 @@ class EntryTable(majorVersion: Int, minorVersion: Int, entries: mutable.Buffer[T entries.foreach { entry => val payloadBytes = entry.toBytes - writer.writeNat(entry.tag) // Tag of entry - writer.writeNat(payloadBytes.length) // Size of payload + writer.writeNat(entry.tag) // Tag of entry + writer.writeNat(payloadBytes.length) // Size of payload os.write(payloadBytes) } diff --git a/src/main/scala/sbtassembly/scalasig/Nat.scala b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/Nat.scala similarity index 96% rename from src/main/scala/sbtassembly/scalasig/Nat.scala rename to core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/Nat.scala index fab4dc7a..d9307e04 100644 --- a/src/main/scala/sbtassembly/scalasig/Nat.scala +++ b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/Nat.scala @@ -1,4 +1,5 @@ -package sbtassembly.scalasig +package com.eed3si9n.jarjarabrams +package scalasig /** * Utility methods for reading and writing nat encoded numbers. @@ -6,6 +7,7 @@ package sbtassembly.scalasig object Nat { trait Reader { + /** Read a byte */ def readByte(): Int @@ -37,6 +39,7 @@ object Nat { } trait Writer { + /** Write a byte */ def writeByte(b: Int): Unit diff --git a/src/main/scala/sbtassembly/scalasig/ScalaSigAnnotationVisitor.scala b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/ScalaSigAnnotationVisitor.scala similarity index 87% rename from src/main/scala/sbtassembly/scalasig/ScalaSigAnnotationVisitor.scala rename to core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/ScalaSigAnnotationVisitor.scala index b133ea13..a9e21697 100644 --- a/src/main/scala/sbtassembly/scalasig/ScalaSigAnnotationVisitor.scala +++ b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/ScalaSigAnnotationVisitor.scala @@ -1,12 +1,14 @@ -package sbtassembly.scalasig +package com.eed3si9n.jarjarabrams +package scalasig import java.io.ByteArrayOutputStream -import org.objectweb.asm.{AnnotationVisitor, ClassVisitor, Opcodes} +import org.objectweb.asm.{ AnnotationVisitor, ClassVisitor, Opcodes } import scala.reflect.internal.pickling.ByteCodecs -class ScalaSigClassVisitor(cv: ClassVisitor, renamer: String => Option[String]) extends ClassVisitor(Opcodes.ASM7, cv) { +class ScalaSigClassVisitor(cv: ClassVisitor, renamer: String => Option[String]) + extends ClassVisitor(Opcodes.ASM7, cv) { override def visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor = { if (descriptor == "Lscala/reflect/ScalaSignature;" || descriptor == "Lscala/reflect/ScalaLongSignature;") { @@ -18,9 +20,9 @@ class ScalaSigClassVisitor(cv: ClassVisitor, renamer: String => Option[String]) } class ScalaSigAnnotationVisitor( - visible: Boolean, - cv: ClassVisitor, - renamer: String => Option[String] + visible: Boolean, + cv: ClassVisitor, + renamer: String => Option[String] ) extends AnnotationVisitor(Opcodes.ASM7) { private val MaxStringSizeInBytes = 65535 @@ -45,7 +47,9 @@ class ScalaSigAnnotationVisitor( table.renameEntries(renamer) val chars = ubytesToCharArray(mapToNextModSevenBits(ByteCodecs.encode8to7(table.toBytes))) - val utf8EncodedLength = chars.foldLeft(0) { (count, next) => if (next == 0) count + 2 else count + 1} + val utf8EncodedLength = chars.foldLeft(0) { (count, next) => + if (next == 0) count + 2 else count + 1 + } if (utf8EncodedLength > MaxStringSizeInBytes) { // Encode as ScalaLongSignature containing an array of strings @@ -58,7 +62,7 @@ class ScalaSigAnnotationVisitor( var size = 0 var index = 0 - while(size < MaxStringSizeInBytes && from + index < chars.length) { + while (size < MaxStringSizeInBytes && from + index < chars.length) { val c = chars(from + index) size += (if (c == 0) 2 else 1) index += 1 @@ -73,7 +77,7 @@ class ScalaSigAnnotationVisitor( var offset = 0 var chunk = nextChunk(offset) - while(chunk.nonEmpty) { + while (chunk.nonEmpty) { arrayVisitor.visit("bytes", new String(chunk)) offset += chunk.length chunk = nextChunk(offset) @@ -104,7 +108,7 @@ class ScalaSigAnnotationVisitor( def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { val ca = new Array[Char](bytes.length) var idx = 0 - while(idx < bytes.length) { + while (idx < bytes.length) { val b: Byte = bytes(idx) assert((b & ~0x7f) == 0) ca(idx) = b.asInstanceOf[Char] @@ -112,4 +116,4 @@ class ScalaSigAnnotationVisitor( } ca } -} \ No newline at end of file +} diff --git a/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/TaggedEntry.scala b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/TaggedEntry.scala new file mode 100644 index 00000000..a4059fd0 --- /dev/null +++ b/core/src/main/scala/com/eed3si9n/jarjarabrams/scalasig/TaggedEntry.scala @@ -0,0 +1,65 @@ +package com.eed3si9n.jarjarabrams +package scalasig + +import java.io.ByteArrayOutputStream + +import scala.reflect.internal.pickling.PickleFormat + +sealed trait TaggedEntry { + def tag: Int + def toBytes: Array[Byte] // Entry payload. Does not include the tag or length. +} + +object TaggedEntry { + + /** + * Unparsed entry + */ + case class RawEntry(tag: Int, bytes: Seq[Byte]) extends TaggedEntry { + override def toBytes: Array[Byte] = bytes.toArray + } + + /** + * Named entry. Either a term name or type name + */ + case class NameEntry(tag: Int, name: String) extends TaggedEntry { + override def toBytes: Array[Byte] = name.getBytes("UTF-8") + } + + /** + * Ref entry. Either a EXTMODCLASSref or EXTref + */ + case class RefEntry(tag: Int, nameRef: Int, ownerRef: Option[Int]) extends TaggedEntry { + + override def toBytes: Array[Byte] = { + val os = new ByteArrayOutputStream() + val writer = new Nat.Writer { + override def writeByte(b: Int): Unit = os.write(b) + } + + ownerRef match { + case Some(owner) => + writer.writeNat(nameRef) + writer.writeNat(owner) + case None => + writer.writeNat(nameRef) + } + + os.toByteArray + } + } + + def apply(tag: Int, bytes: Array[Byte]): TaggedEntry = { + tag match { + case tag @ (PickleFormat.TERMname | PickleFormat.TYPEname) => + NameEntry(tag, new String(bytes, "UTF-8")) + case tag @ (PickleFormat.EXTMODCLASSref | PickleFormat.EXTref) => + val r = new ByteArrayReader(bytes) + val nameRef = r.readNat() + val symbolRef = if (r.atEnd) None else Some(r.readNat()) + RefEntry(tag, nameRef, symbolRef) + case _ => + RawEntry(tag, bytes) + } + } +} diff --git a/src/main/scala/org/pantsbuild/jarjar/JJProcessor.scala b/core/src/main/scala/org/pantsbuild/jarjar/JJProcessor.scala similarity index 61% rename from src/main/scala/org/pantsbuild/jarjar/JJProcessor.scala rename to core/src/main/scala/org/pantsbuild/jarjar/JJProcessor.scala index dae5cf89..0b4343db 100644 --- a/src/main/scala/org/pantsbuild/jarjar/JJProcessor.scala +++ b/core/src/main/scala/org/pantsbuild/jarjar/JJProcessor.scala @@ -3,7 +3,14 @@ package org.pantsbuild.jarjar import java.io.IOException import org.pantsbuild.jarjar.misplaced.MisplacedClassProcessorFactory -import org.pantsbuild.jarjar.util.{EntryStruct, JarProcessor, JarProcessorChain, JarTransformerChain, RemappingClassTransformer, StandaloneJarProcessor} +import org.pantsbuild.jarjar.util.{ + EntryStruct, + JarProcessor, + JarProcessorChain, + JarTransformerChain, + RemappingClassTransformer, + StandaloneJarProcessor +} import scala.collection.JavaConverters._ import scala.collection.mutable @@ -18,9 +25,14 @@ import scala.collection.mutable * @param misplacedClassStrategy The strategy to use when processing class files that are in the * wrong package (see MisplacedClassProcessorFactory.STRATEGY_* constants). */ -class JJProcessor(val patterns: Seq[PatternElement], val verbose: Boolean, val skipManifest: Boolean, val misplacedClassStrategy: String) extends JarProcessor { - - val zapList: Seq[Zap] = patterns.collect { case zap: Zap => zap } +class JJProcessor( + val patterns: Seq[PatternElement], + val verbose: Boolean, + val skipManifest: Boolean, + val misplacedClassStrategy: String +) extends JarProcessor { + + val zapList: Seq[Zap] = patterns.collect { case zap: Zap => zap } val ruleList: Seq[Rule] = patterns.collect { case rule: Rule => rule } val keepList: Seq[Keep] = patterns.collect { case keep: Keep => keep } val renames: mutable.Map[String, String] = collection.mutable.HashMap[String, String]() @@ -35,21 +47,24 @@ class JJProcessor(val patterns: Seq[PatternElement], val verbose: Boolean, val s if (kp != null) processors += kp - val misplacedClassProcessor: JarProcessor = MisplacedClassProcessorFactory.getInstance.getProcessorForName(misplacedClassStrategy) + val misplacedClassProcessor: JarProcessor = + MisplacedClassProcessorFactory.getInstance.getProcessorForName(misplacedClassStrategy) processors += new ZapProcessor(zapList.asJava) processors += misplacedClassProcessor - processors += new JarTransformerChain(Array[RemappingClassTransformer](new RemappingClassTransformer(pr))) + processors += new JarTransformerChain( + Array[RemappingClassTransformer](new RemappingClassTransformer(pr)) + ) val renamer: String => Option[String] = { val wildcards = PatternElement.createWildcards(ruleList.asJava).asScala value: String => { - val result = wildcards.flatMap { - wc => - val slashed = value.replace('.', '/') // The jarjar wildcards expect slashes instead of dots - // Hack to replace the package object name. - val renamed = Option(wc.replace(slashed)).orElse(Option(wc.replace(slashed + "/")).map(_.dropRight(1))) - renamed.map(_.replace('/', '.')) // Unslash + val result = wildcards.flatMap { wc => + val slashed = value.replace('.', '/') // The jarjar wildcards expect slashes instead of dots + // Hack to replace the package object name. + val renamed = + Option(wc.replace(slashed)).orElse(Option(wc.replace(slashed + "/")).map(_.dropRight(1))) + renamed.map(_.replace('/', '.')) // Unslash }.headOption result @@ -65,7 +80,8 @@ class JJProcessor(val patterns: Seq[PatternElement], val verbose: Boolean, val s def strip(file: Nothing): Unit = { if (kp != null) { val excludes = getExcludes - if (excludes.nonEmpty) StandaloneJarProcessor.run(file, file, new ExcludeProcessor(excludes.asJava, verbose)) + if (excludes.nonEmpty) + StandaloneJarProcessor.run(file, file, new ExcludeProcessor(excludes.asJava, verbose)) } } @@ -75,25 +91,31 @@ class JJProcessor(val patterns: Seq[PatternElement], val verbose: Boolean, val s * * @return the paths of the files in the jar-archive, including the .class suffix */ - def getExcludes: Set[String] = if (kp != null) kp.getExcludes.asScala.map { exclude => + def getExcludes: Set[String] = + if (kp != null) kp.getExcludes.asScala.map { exclude => val name = exclude + ".class" renames.getOrElse(name, name) - }.toSet else Set.empty + }.toSet + else Set.empty /** - * * @param struct entry struct to process * @return true if the entry is to include in the output jar - * @throws IOException */ @throws[IOException] def process(struct: EntryStruct): Boolean = { val name = struct.name val keepIt = chain.process(struct) - if (keepIt) if (!name.equals(struct.name)) { - if (kp != null) renames.put(name, struct.name) - if (verbose) System.err.println("Renamed " + name + " -> " + struct.name) - } else if (verbose) System.err.println("Removed " + name) + if (keepIt) { + if (!name.equals(struct.name)) { + if (kp != null) renames.put(name, struct.name) + if (verbose) System.err.println("Renamed " + name + " -> " + struct.name) + } else { + if (verbose) { + System.err.println(s"Skipped $name without processing because $name == ${struct.name}") + } + } + } keepIt } } diff --git a/src/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala b/core/src/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala similarity index 74% rename from src/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala rename to core/src/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala index 2a8c9211..5657239a 100644 --- a/src/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala +++ b/core/src/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala @@ -1,8 +1,8 @@ package org.pantsbuild.jarjar -import org.objectweb.asm.{ClassReader, ClassWriter} -import org.pantsbuild.jarjar.util.{EntryStruct, JarProcessor} -import sbtassembly.scalasig.ScalaSigClassVisitor +import org.objectweb.asm.{ ClassReader, ClassWriter } +import org.pantsbuild.jarjar.util.{ EntryStruct, JarProcessor } +import com.eed3si9n.jarjarabrams.scalasig.ScalaSigClassVisitor class ScalaSigProcessor(renamer: String => Option[String]) extends JarProcessor { override def process(struct: EntryStruct): Boolean = { diff --git a/core/src/test/scala/testpkg/EntryTableSpec.scala b/core/src/test/scala/testpkg/EntryTableSpec.scala new file mode 100644 index 00000000..dc4d7f43 --- /dev/null +++ b/core/src/test/scala/testpkg/EntryTableSpec.scala @@ -0,0 +1,78 @@ +package testpkg + +import verify._ +import scala.reflect.ScalaSignature +import scala.reflect.internal.pickling.ByteCodecs +import com.eed3si9n.jarjarabrams.scalasig._ + +class Test + +abstract class Test2 { + val test: Test +} + +object EntryTableSpec extends BasicTestSuite { + test("entry table should parse annotation bytes") { + val encoded = classOf[Test2].getAnnotation(classOf[ScalaSignature]) + val bytes = encoded.bytes().getBytes("UTF-8") + + val len = ByteCodecs.decode(bytes) + val table = EntryTable.fromBytes(bytes.slice(0, len)) + assert(resolveName(table, "Test") == Some("testpkg.Test")) + } + + test("entry table should rename package") { + val encoded = classOf[Test2].getAnnotation(classOf[ScalaSignature]) + val bytes = encoded.bytes().getBytes("UTF-8") + + val len = ByteCodecs.decode(bytes) + val table = EntryTable.fromBytes(bytes.slice(0, len)) + + table.renameEntries { + case pckg if pckg.startsWith("testpkg") => Some("shaded.testpkg") + case _ => None + } + + assert(resolveName(table, "Test") == Some("shaded.testpkg.Test")) + + table.renameEntries { + case pckg if pckg.startsWith("shaded.testpkg") => + Some("testpkg.shadedtoo") + case _ => None + } + + assert(resolveName(table, "Test") == Some("testpkg.shadedtoo.Test")) + } + + test("entry table should return same serialized bytes") { + val encoded = classOf[Test2].getAnnotation(classOf[ScalaSignature]) + val bytes = encoded.bytes().getBytes("UTF-8") + + val len = ByteCodecs.decode(bytes) + val decoded = bytes.slice(0, len) + val table = EntryTable.fromBytes(decoded) + + val serialized = table.toBytes + + assert(scala.math.abs(serialized.length - decoded.length) <= 1) // Scala compiler (sometimes) adds an extra zero. We don't. + assert(EntryTable.fromBytes(serialized).toSeq == table.toSeq) + } + + private def resolveName(entryTable: EntryTable, name: String): Option[String] = { + import TaggedEntry._ + val entries = entryTable.toSeq.zipWithIndex + def findNameIndex: Option[Int] = entries.collectFirst { + case (e: NameEntry, index) if e.name == name => index + } + + def findRefEntry(nameIndex: Int): Option[RefEntry] = entries.collectFirst { + case (e: RefEntry, _) if e.nameRef == nameIndex => e + } + + for { + index <- findNameIndex + ref <- findRefEntry(index) + resolved <- entryTable.resolveRef(ref) + } yield resolved + } +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala new file mode 100644 index 00000000..068e64f3 --- /dev/null +++ b/project/Dependencies.scala @@ -0,0 +1,10 @@ +import sbt._ + +object Dependencies { + val scala210 = "2.10.7" + val scala211 = "2.11.12" + val scala212 = "2.12.11" + val scala213 = "2.13.2" + val jarjar = "org.pantsbuild" % "jarjar" % "1.7.2" + val verify = "com.eed3si9n.verify" %% "verify" % "0.2.0" +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 00000000..797e7ccf --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.10 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 00000000..2fe51360 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,3 @@ +addSbtPlugin("com.eed3si9n" % "sbt-nocomma" % "0.1.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.0") +addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.6") diff --git a/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsKeys.scala b/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsKeys.scala new file mode 100644 index 00000000..2b08ae36 --- /dev/null +++ b/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsKeys.scala @@ -0,0 +1,17 @@ +package com.eed3si9n.jarjarabrams +package sbtjarjarabrams + +import sbt._ + +trait JarjarAbramsKeys { + lazy val jarjarLibraryDependency = settingKey[ModuleID]("") + lazy val jarjarShadeRules = settingKey[Seq[ShadeRule]]("") +} +object JarjarAbramsKeys extends JarjarAbramsKeys + +trait JarjarAbramsInternalKeys { + lazy val jarjarPackageBin = taskKey[File]("") + lazy val jarjarPackageBinMappings = taskKey[Seq[(File, String)]]("") + lazy val jarjarInputJar = taskKey[File]("") +} +object JarjarAbramsInternalKeys extends JarjarAbramsInternalKeys diff --git a/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsPlugin.scala b/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsPlugin.scala new file mode 100644 index 00000000..09f0804c --- /dev/null +++ b/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/JarjarAbramsPlugin.scala @@ -0,0 +1,213 @@ +package com.eed3si9n.jarjarabrams +package sbtjarjarabrams + +import sbt._ +import Keys._ +import Defaults.{ packageTaskSettings, prefix } +import sbt.librarymanagement.ScalaModuleInfo +import Path.relativeTo +import scala.xml.{ Comment, Elem, Node => XmlNode } +import scala.xml.transform.{ RewriteRule, RuleTransformer } +import java.nio.file.Files + +object JarjarAbramsPlugin extends AutoPlugin { + object autoImport extends JarjarAbramsKeys { + val ShadeRuleBuilder = sbtjarjarabrams.ShadeRuleBuilder + } + import autoImport._ + + override def globalSettings: Seq[Setting[_]] = { + jarjarShadeRules := Vector() + } + + override def projectSettings: Seq[Setting[_]] = + Seq( + libraryDependencies += jarjarLibraryDependency.value, + exportJars := true, + // https://github.com/coursier/sbt-shading/blob/895e7f7d73a5e25bbd3aad8f3886303ccdf58a76/src/main/scala/coursier/ShadingPlugin.scala + pomPostProcess := { + val orig = pomPostProcess.value + val scalaModuleInfoOpt = scalaModuleInfo.value + val libDep = jarjarLibraryDependency.value + val shadedModules0: Set[(String, String)] = Set(libDep).map { modId => + (modId.organization, crossName(modId, scalaModuleInfoOpt)) + } + // Originally based on https://github.com/olafurpg/coursier-small/blob/408528d10cea1694c536f55ba1b023e55af3e0b2/build.sbt#L44-L56 + val transformer = new RuleTransformer(new RewriteRule { + override def transform(node: XmlNode) = node match { + case _: Elem if node.label == "dependency" => + val org = node.child.find(_.label == "groupId").fold("")(_.text.trim) + val name = node.child.find(_.label == "artifactId").fold("")(_.text.trim) + val isShaded = shadedModules0.contains((org, name)) + if (isShaded) + Comment( + s""" shaded dependency $org:$name + | $node + |""".stripMargin + ) + else + node + case _ => node + } + }) + + node => + val node0 = orig(node) + transformer.transform(node0).head + }, + publishLocal := { + reallyUpdateIvyXml.dependsOn(publishLocal).value + }, + deliverLocal := { + val scalaModuleInfoOpt = scalaModuleInfo.value + val libDep = jarjarLibraryDependency.value + val shadedModules0: Set[(String, String)] = Set(libDep).map { modId => + (modId.organization, crossName(modId, scalaModuleInfoOpt)) + } + val file = deliverLocal.value + updateIvyXml(file, shadedModules0) + file + }, + ) ++ inConfig(Compile)(baseSettings) + + import JarjarAbramsInternalKeys._ + def baseSettings: Seq[Setting[_]] = + Seq( + packageBin := jarjarPackageBin.value, + jarjarPackageBin / target := crossTarget.value / (prefix(configuration.value.name) + "shaded"), + jarjarPackageBin / logLevel := Level.Info, + jarjarPackageBinMappings := { + import sbt.util.CacheImplicits._ + val s = streams.value + val input = jarjarInputJar.value + val prev = jarjarPackageBinMappings.previous + val dir = (jarjarPackageBin / target).value + val rules = jarjarShadeRules.value + val verbose = (jarjarPackageBin / logLevel).value == sbt.Level.Debug + def doMapping: Seq[(File, String)] = { + IO.delete(dir) + IO.createDirectory(dir) + IO.unzip(input, dir) + val mappings = ((dir ** "*").get pair relativeTo(dir)) map { + case (k, v) => k.toPath -> v + } + Shader.shadeDirectory(rules, dir.toPath, mappings, verbose) + (dir ** "*").get pair relativeTo(dir) + } + val cachedMappings = + Tracked + .inputChanged[HashFileInfo, Seq[(File, String)]](s.cacheStoreFactory.make("input")) { + (changed: Boolean, in: HashFileInfo) => + prev match { + case None => doMapping + case Some(last) => + if (changed) doMapping + else last + } + } + cachedMappings(FileInfo.hash(input)) + }, + jarjarInputJar := { + val libDep = jarjarLibraryDependency.value + val ur = update.value + val cr = ur.configurations.find(_.configuration.name == configuration.value.name).head + val modules = cr.modules find { + case mr: ModuleReport => + val m = mr.module + (m.organization == libDep.organization) && + (m.revision == libDep.revision) && + ((m.name == libDep.name) || (m.name == crossName(libDep, scalaModuleInfo.value))) + } + val mr = modules match { + case Some(m) => m + case _ => sys.error(s"$libDep was not found") + } + mr.artifacts.head._2 + }, + externalDependencyClasspath := { + val input = jarjarInputJar.value + val cp = externalDependencyClasspath.value + cp filter { attr => + attr.data != input + } + }, + ) ++ packageTaskSettings(jarjarPackageBin, jarjarPackageBinMappings) ++ Seq( + jarjarPackageBin / artifactPath := { + val original = (jarjarPackageBin / artifactPath).value + original.getParentFile / s"shaded-${original.getName}" + }, + jarjarPackageBin := { + val config = (jarjarPackageBin / packageConfiguration).value + val s = streams.value + Package( + config, + s.cacheStoreFactory, + s.log, + // sys.env.get("SOURCE_DATE_EPOCH").map(_.toLong * 1000).orElse(Some(0L)) + ) + config.jar + } + ) + + def crossName( + modId: ModuleID, + scalaModuleInfoOpt: Option[ScalaModuleInfo] + ): String = { + val crossVer = modId.crossVersion + val transformName = scalaModuleInfoOpt + .flatMap( + scalaInfo => + CrossVersion(crossVer, scalaInfo.scalaFullVersion, scalaInfo.scalaBinaryVersion) + ) + .getOrElse(identity[String] _) + transformName(modId.name) + } + + def updateIvyXml(file: File, removeDeps: Set[(String, String)]): Unit = { + import java.nio.charset.StandardCharsets + val content = scala.xml.XML.loadFile(file) + + val updatedContent = content.copy(child = content.child.map { + case elem: Elem if elem.label == "dependencies" => + elem.copy(child = elem.child.map { + case elem: Elem if elem.label == "dependency" => + (elem.attributes.get("org"), elem.attributes.get("name")) match { + case (Some(Seq(orgNode)), Some(Seq(nameNode))) => + val org = orgNode.text + val name = nameNode.text + val remove = removeDeps.contains((org, name)) + if (remove) + Comment( + s""" shaded dependency $org:$name + | $elem + |""".stripMargin + ) + else + elem + case _ => elem + } + case n => n + }) + case n => n + }) + + val printer = new scala.xml.PrettyPrinter(Int.MaxValue, 2) + val updatedFileContent = """""" + '\n' + + printer.format(updatedContent) + Files.write(file.toPath, updatedFileContent.getBytes(StandardCharsets.UTF_8)) + } + + private val reallyUpdateIvyXml = Def.task { + val baseFile = deliverLocal.value + val log = streams.value.log + val resolverName = publishLocalConfiguration.value.resolverName.getOrElse(???) + ivyModule.value.withModule(log) { + case (ivy, md, _) => + val resolver = ivy.getSettings.getResolver(resolverName) + val artifact = + new org.apache.ivy.core.module.descriptor.MDArtifact(md, "ivy", "ivy", "xml", true) + log.info(s"Writing ivy.xml with shading at $baseFile") + resolver.publish(artifact, baseFile, true) + } + } +} diff --git a/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/ShadeRuleBuilder.scala b/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/ShadeRuleBuilder.scala new file mode 100644 index 00000000..3c70c532 --- /dev/null +++ b/sbtplugin/src/main/scala/com/eed3si9n/jarjarabrams/sbtjarjarabrams/ShadeRuleBuilder.scala @@ -0,0 +1,12 @@ +package com.eed3si9n.jarjarabrams +package sbtjarjarabrams + +/** + * Creates shade rule but hardcoded to all targets. + */ +object ShadeRuleBuilder { + def rename(patterns: (String, String)*): ShadeRule = ShadeRule.rename(patterns: _*).inAll + def moveUnder(from: String, to: String): ShadeRule = ShadeRule.moveUnder(from, to).inAll + def zap(patterns: String*): ShadeRule = ShadeRule.zap(patterns: _*).inAll + def keep(patterns: String*): ShadeRule = ShadeRule.keep(patterns: _*).inAll +} diff --git a/sbtplugin/src/sbt-test/actions/simple/build.sbt b/sbtplugin/src/sbt-test/actions/simple/build.sbt new file mode 100644 index 00000000..d4f7b29e --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/simple/build.sbt @@ -0,0 +1,13 @@ +ThisBuild / organization := "com.example" +ThisBuild / scalaVersion := "2.12.11" + +lazy val shadedJawn = project + .enablePlugins(JarjarAbramsPlugin) + .settings( + name := "shaded-jawn", + jarjarLibraryDependency := "org.typelevel" %% "jawn-parser" % "1.0.0", + jarjarShadeRules += ShadeRuleBuilder.moveUnder("org.typelevel", "shaded") + ) + +lazy val use = project + .dependsOn(shadedJawn) diff --git a/sbtplugin/src/sbt-test/actions/simple/project/plugins.sbt b/sbtplugin/src/sbt-test/actions/simple/project/plugins.sbt new file mode 100644 index 00000000..fc31f2be --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/simple/project/plugins.sbt @@ -0,0 +1,5 @@ +sys.props.get("plugin.version") match { + case Some(x) => addSbtPlugin("com.eed3si9n.jarjarabrams" % "sbt-jarjar-abrams" % x) + case _ => sys.error("""|The system property 'plugin.version' is not defined. + |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) +} diff --git a/sbtplugin/src/sbt-test/actions/simple/test b/sbtplugin/src/sbt-test/actions/simple/test new file mode 100644 index 00000000..e3607276 --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/simple/test @@ -0,0 +1,4 @@ +# > shadedJawn/publishLocal +> show shadedJawn/makePom + +> use/run diff --git a/sbtplugin/src/sbt-test/actions/simple/use/Test.scala b/sbtplugin/src/sbt-test/actions/simple/use/Test.scala new file mode 100644 index 00000000..26393033 --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/simple/use/Test.scala @@ -0,0 +1,213 @@ +package example + +import shaded.org.typelevel.jawn._ +import scala.collection.mutable.ArrayBuffer + +object ApplicationMain extends App { + val json = """{ "x": 5 }""" + val tree = Parser.parseUnsafe(json) + println(tree) +} + +object Parser extends SupportParser[JValue] { + implicit val facade: Facade[JValue] = + new Facade[JValue] { + def jnull(index: Int) = JNull + def jfalse(index: Int) = JFalse + def jtrue(index: Int) = JTrue + def jnum(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = JNumber(s.toString) + def jint(s: String) = JNumber(s) + def jstring(s: CharSequence, index: Int) = JString(s.toString) + def singleContext(index: Int) = new FContext[JValue] { + var value: JValue = _ + def add(s: CharSequence, index: Int) = value = jstring(s, index) + def add(v: JValue, index: Int) = value = v + def finish(index: Int): JValue = value + def isObj: Boolean = false + } + def arrayContext(index: Int) = new FContext[JValue] { + private val vs = ArrayBuffer.empty[JValue] + def add(s: CharSequence, index: Int) = vs += jstring(s, index) + def add(v: JValue, index: Int) = vs += v + def finish(index: Int): JValue = JArray(vs.toArray) + def isObj: Boolean = false + } + def objectContext(index: Int) = new FContext[JValue] { + private var key: String = _ + private var vs = ArrayBuffer.empty[JField] + private def andNullKey[A](t: => Unit): Unit = { t; key = null } + def add(s: CharSequence, index: Int) = { + if (key == null) key = s.toString + else andNullKey(vs += JField(key, jstring(s, index))) + } + def add(v: JValue, index: Int) = andNullKey(vs += JField(key, v)) + def finish(index: Int): JValue = JObject(vs.toArray) + def isObj: Boolean = true + } + } +} + +/** Represents a JSON Value which may be invalid. Internally uses mutable + * collections when its desirable to do so, for performance and other reasons + * (such as ordering and duplicate keys) + * + * @author Matthew de Detrich + * @see https://www.ietf.org/rfc/rfc4627.txt + */ +sealed abstract class JValue extends Serializable with Product { + +} + +/** Represents a JSON null value + * + * @author Matthew de Detrich + */ +final case object JNull extends JValue { + +} + +/** Represents a JSON string value + * + * @author Matthew de Detrich + */ +final case class JString(value: String) extends JValue { + +} + +object JNumber { + def apply(value: Int): JNumber = JNumber(value.toInt.toString) + + def apply(value: Long): JNumber = JNumber(value.toString) + + def apply(value: BigInt): JNumber = JNumber(value.toString) + + def apply(value: BigDecimal): JNumber = JNumber(value.toString) + + def apply(value: Float): JNumber = JNumber(value.toString) + + def apply(value: Double): JNumber = JNumber(value.toString) + + def apply(value: Integer): JNumber = JNumber(value.toString) + + def apply(value: Array[Char]): JNumber = JNumber(new String(value)) +} + +/** Represents a JSON number value. + * + * If you are passing in a NaN or Infinity as a [[scala.Double]], [[unsafe.JNumber]] + * will contain "NaN" or "Infinity" as a String which means it will cause + * issues for users when they use the value at runtime. It may be + * preferable to check values yourself when constructing [[unsafe.JValue]] + * to prevent this. This isn't checked by default for performance reasons. + * + * @author Matthew de Detrich + */ +// JNumber is internally represented as a string, to improve performance +final case class JNumber(value: String) extends JValue { +} + +/** Represents a JSON Boolean value, which can either be a + * [[JTrue]] or a [[JFalse]] + * + * @author Matthew de Detrich + */ +// Implements named extractors so we can avoid boxing +sealed abstract class JBoolean extends JValue { + def isEmpty: Boolean = false + def get: Boolean +} + +object JBoolean { + def apply(x: Boolean): JBoolean = if (x) JTrue else JFalse + + def unapply(x: JBoolean): Some[Boolean] = Some(x.get) +} + +/** Represents a JSON Boolean true value + * + * @author Matthew de Detrich + */ +final case object JTrue extends JBoolean { + override def get = true +} + +/** Represents a JSON Boolean false value + * + * @author Matthew de Detrich + */ +final case object JFalse extends JBoolean { + override def get = false +} + +final case class JField(field: String, value: JValue) + +object JObject { + def apply(value: JField, values: JField*): JObject = + JObject(Array(value) ++ values) +} + +/** Represents a JSON Object value. Duplicate keys + * are allowed and ordering is respected + * @author Matthew de Detrich + */ +// JObject is internally represented as a mutable Array, to improve sequential performance +final case class JObject(value: Array[JField] = Array.empty) extends JValue { + override def equals(obj: scala.Any): Boolean = { + obj match { + case jObject: JObject => + val length = value.length + if (length != jObject.value.length) + return false + var index = 0 + while (index < length) { + if (value(index) != jObject.value(index)) + return false + index += 1 + } + true + case _ => false + } + } + + override def hashCode: Int = + java.util.Arrays.deepHashCode(value.asInstanceOf[Array[AnyRef]]) + + override def toString = + "JObject(" + java.util.Arrays + .toString(value.asInstanceOf[Array[AnyRef]]) + ")" +} + +object JArray { + def apply(value: JValue, values: JValue*): JArray = + JArray(Array(value) ++ values.toArray[JValue]) +} + +/** Represents a JSON Array value + * @author Matthew de Detrich + */ +// JArray is internally represented as a mutable Array, to improve sequential performance +final case class JArray(value: Array[JValue] = Array.empty) extends JValue { + override def equals(obj: scala.Any): Boolean = { + obj match { + case jArray: JArray => + val length = value.length + if (length != jArray.value.length) + return false + var index = 0 + while (index < length) { + if (value(index) != jArray.value(index)) + return false + index += 1 + } + true + case _ => false + } + } + + override def hashCode: Int = + java.util.Arrays.deepHashCode(value.asInstanceOf[Array[AnyRef]]) + + override def toString = + "JArray(" + java.util.Arrays + .toString(value.asInstanceOf[Array[AnyRef]]) + ")" +} diff --git a/sbtplugin/src/sbt-test/actions/two/build.sbt b/sbtplugin/src/sbt-test/actions/two/build.sbt new file mode 100644 index 00000000..474f56f9 --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/two/build.sbt @@ -0,0 +1,22 @@ +ThisBuild / organization := "com.example" +ThisBuild / scalaVersion := "2.12.11" + +lazy val shadedJawn = project + .enablePlugins(JarjarAbramsPlugin) + .settings( + name := "shaded-jawn", + jarjarLibraryDependency := "org.typelevel" %% "jawn-parser" % "1.0.0", + jarjarShadeRules += ShadeRuleBuilder.moveUnder("org.typelevel", "shaded") + ) + +lazy val shadedJawnAst = project + .enablePlugins(JarjarAbramsPlugin) + .dependsOn(shadedJawn) + .settings( + name := "shaded-jawn-ast", + jarjarLibraryDependency := "org.typelevel" %% "jawn-ast" % "1.0.0", + jarjarShadeRules += ShadeRuleBuilder.moveUnder("org.typelevel", "shaded") + ) + +lazy val use = project + .dependsOn(shadedJawnAst) diff --git a/sbtplugin/src/sbt-test/actions/two/project/plugins.sbt b/sbtplugin/src/sbt-test/actions/two/project/plugins.sbt new file mode 100644 index 00000000..fc31f2be --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/two/project/plugins.sbt @@ -0,0 +1,5 @@ +sys.props.get("plugin.version") match { + case Some(x) => addSbtPlugin("com.eed3si9n.jarjarabrams" % "sbt-jarjar-abrams" % x) + case _ => sys.error("""|The system property 'plugin.version' is not defined. + |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) +} diff --git a/sbtplugin/src/sbt-test/actions/two/test b/sbtplugin/src/sbt-test/actions/two/test new file mode 100644 index 00000000..e3607276 --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/two/test @@ -0,0 +1,4 @@ +# > shadedJawn/publishLocal +> show shadedJawn/makePom + +> use/run diff --git a/sbtplugin/src/sbt-test/actions/two/use/Test.scala b/sbtplugin/src/sbt-test/actions/two/use/Test.scala new file mode 100644 index 00000000..ec4f1ca3 --- /dev/null +++ b/sbtplugin/src/sbt-test/actions/two/use/Test.scala @@ -0,0 +1,10 @@ +package example + +import shaded.org.typelevel.jawn._ +import scala.collection.mutable.ArrayBuffer + +object ApplicationMain extends App { + val json = """{ "x": 5 }""" + val tree = ast.JParser.parseUnsafe(json) + println(tree) +} diff --git a/src/main/scala/sbtassembly/Shader.scala b/src/main/scala/sbtassembly/Shader.scala deleted file mode 100644 index aaafd949..00000000 --- a/src/main/scala/sbtassembly/Shader.scala +++ /dev/null @@ -1,109 +0,0 @@ -package sbtassembly - -import java.io.File - -import org.pantsbuild.jarjar.{JJProcessor, _} -import org.pantsbuild.jarjar.util.EntryStruct -import sbt._ - -case class ShadeRule(shadePattern: ShadePattern, targets: Seq[ShadeTarget] = Seq()) { - def inLibrary(moduleId: ModuleID*): ShadeRule = - this.copy(targets = targets ++ (moduleId.toSeq map { x => ShadeTarget(moduleID = Some(x)) })) - def inAll: ShadeRule = - this.copy(targets = targets :+ ShadeTarget(inAll = true)) - def inProject: ShadeRule = - this.copy(targets = targets :+ ShadeTarget(inProject = true)) - - private[sbtassembly] def isApplicableTo(mod: ModuleID): Boolean = - targets.exists(_.isApplicableTo(mod)) - - private[sbtassembly] def isApplicableToAll: Boolean = - targets.exists(_.inAll) - - private[sbtassembly] def isApplicableToCompiling: Boolean = - targets.exists(_.inAll) || targets.exists(_.inProject) - -} - -sealed trait ShadePattern { - def inLibrary(moduleId: ModuleID*): ShadeRule = - ShadeRule(this, moduleId.toSeq map { x => ShadeTarget(moduleID = Some(x)) }) - def inAll: ShadeRule = - ShadeRule(this, Seq(ShadeTarget(inAll = true))) - def inProject: ShadeRule = - ShadeRule(this, Seq(ShadeTarget(inProject = true))) -} - -object ShadeRule { - def rename(patterns: (String, String)*): ShadePattern = Rename(patterns.toSeq.toList) - def zap(patterns: String*): ShadePattern = Zap(patterns.toSeq.toList) - def keep(patterns: String*): ShadePattern = Keep(patterns.toSeq.toList) - private[sbtassembly] case class Rename(patterns: List[(String, String)]) extends ShadePattern - private[sbtassembly] case class Zap(patterns: List[String]) extends ShadePattern - private[sbtassembly] case class Keep(patterns: List[String]) extends ShadePattern -} - -private[sbtassembly] case class ShadeTarget( - inAll: Boolean = false, - inProject: Boolean = false, - moduleID: Option[ModuleID] = None) { - - private[sbtassembly] def isApplicableTo(mod: ModuleID): Boolean = - inAll || (moduleID match { - case Some(m) if (m.organization == mod.organization) && (m.name == mod.name) && (m.revision == mod.revision) => - true - case _ => - false - }) -} - -private[sbtassembly] object Shader { - def shadeDirectory(rules: Seq[ShadeRule], dir: File, log: Logger, level: Level.Value): Unit = { - val jjrules = rules flatMap { r => r.shadePattern match { - case ShadeRule.Rename(patterns) => - patterns.map { case (from, to) => - val jrule = new Rule() - jrule.setPattern(from) - jrule.setResult(to) - jrule - } - case ShadeRule.Zap(patterns) => - patterns.map { case pattern => - val jrule = new Zap() - jrule.setPattern(pattern) - jrule - } - case ShadeRule.Keep(patterns) => - patterns.map { case pattern => - val jrule = new Keep() - jrule.setPattern(pattern) - jrule - } - case _ => Nil - }} - - val proc = new JJProcessor(jjrules, verbose = level == Level.Debug, true, null) - - /* - jarjar MisplacedClassProcessor class transforms byte[] to a class using org.objectweb.asm.ClassReader.getClassName - which always translates class names containing '.' into '/', regardless of OS platform. - We need to transform any windows file paths in order for jarjar to match them properly and not omit them. - */ - val files = AssemblyUtils.getMappings(dir, Set()).map(f => - if (f._2.contains('\\')) (f._1, f._2.replace('\\', '/')) else f) - - val entry = new EntryStruct - files filter (!_._1.isDirectory) foreach { f => - entry.data = IO.readBytes(f._1) - entry.name = f._2 - entry.time = -1 - entry.skipTransform = false - IO.delete(f._1) - if (proc.process(entry)) { - IO.write(dir / entry.name, entry.data) - } - } - val excludes = proc.getExcludes - excludes.foreach(exclude => IO.delete(dir / exclude)) - } -} diff --git a/src/main/scala/sbtassembly/scalasig/TaggedEntry.scala b/src/main/scala/sbtassembly/scalasig/TaggedEntry.scala deleted file mode 100644 index ae76f0a0..00000000 --- a/src/main/scala/sbtassembly/scalasig/TaggedEntry.scala +++ /dev/null @@ -1,64 +0,0 @@ -package sbtassembly.scalasig - -import java.io.ByteArrayOutputStream - -import scala.reflect.internal.pickling.PickleFormat - -trait TaggedEntry { - def tag: Int - def toBytes: Array[Byte] // Entry payload. Does not include the tag or length. -} - -/** - * Unparsed entry - */ -case class RawEntry(tag: Int, bytes: Seq[Byte]) extends TaggedEntry { - override def toBytes: Array[Byte] = bytes.toArray -} - -/** - * Named entry. Either a term name or type name - */ -case class NameEntry(tag: Int, name: String) extends TaggedEntry { - override def toBytes: Array[Byte] = name.getBytes("UTF-8") -} - -/** - * Ref entry. Either a EXTMODCLASSref or EXTref - */ -case class RefEntry(tag: Int, nameRef: Int, ownerRef: Option[Int]) extends TaggedEntry { - - override def toBytes: Array[Byte] = { - val os = new ByteArrayOutputStream() - val writer = new Nat.Writer { - override def writeByte(b: Int): Unit = os.write(b) - } - - ownerRef match { - case Some(owner) => - writer.writeNat(nameRef) - writer.writeNat(owner) - case None => - writer.writeNat(nameRef) - } - - os.toByteArray - } -} - - -object TaggedEntry { - def apply(tag: Int, bytes: Array[Byte]): TaggedEntry = { - tag match { - case tag @ (PickleFormat.TERMname | PickleFormat.TYPEname)=> - NameEntry(tag, new String(bytes, "UTF-8")) - case tag @ (PickleFormat.EXTMODCLASSref | PickleFormat.EXTref) => - val r = new ByteArrayReader(bytes) - val nameRef = r.readNat() - val symbolRef = if (r.atEnd) None else Some(r.readNat()) - RefEntry(tag, nameRef, symbolRef) - case _ => - RawEntry(tag, bytes) - } - } -} \ No newline at end of file diff --git a/src/test/scala/sbtassembly/scalasig/EntryTableSpec.scala b/src/test/scala/sbtassembly/scalasig/EntryTableSpec.scala deleted file mode 100644 index 11e3e7cd..00000000 --- a/src/test/scala/sbtassembly/scalasig/EntryTableSpec.scala +++ /dev/null @@ -1,82 +0,0 @@ -package sbtassembly.scalasig - -import org.scalatest.OptionValues -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpec - -import scala.reflect.ScalaSignature -import scala.reflect.internal.pickling.ByteCodecs - -class Test - -abstract class Test2 { - val test: Test -} - -class EntryTableSpec extends AnyWordSpec with Matchers with OptionValues { - - "entry table" should { - "parse annotation bytes" in { - val encoded = classOf[Test2].getAnnotation(classOf[ScalaSignature]) - val bytes = encoded.bytes().getBytes("UTF-8") - - val len = ByteCodecs.decode(bytes) - val table = EntryTable.fromBytes(bytes.slice(0, len)) - - resolveName(table, "Test") shouldBe Some("sbtassembly.scalasig.Test") - } - - "rename package" in { - val encoded = classOf[Test2].getAnnotation(classOf[ScalaSignature]) - val bytes = encoded.bytes().getBytes("UTF-8") - - val len = ByteCodecs.decode(bytes) - val table = EntryTable.fromBytes(bytes.slice(0, len)) - - table.renameEntries { - case pckg if pckg.startsWith("sbtassembly.scalasig") => Some("shaded.sbtassembly.scalasig") - case _ => None - } - - resolveName(table, "Test") shouldBe Some("shaded.sbtassembly.scalasig.Test") - - table.renameEntries { - case pckg if pckg.startsWith("shaded.sbtassembly.scalasig") => Some("sbtassembly.scalasig.shadedtoo") - case _ => None - } - - resolveName(table, "Test") shouldBe Some("sbtassembly.scalasig.shadedtoo.Test") - } - - "return same serialized bytes" in { - val encoded = classOf[Test2].getAnnotation(classOf[ScalaSignature]) - val bytes = encoded.bytes().getBytes("UTF-8") - - val len = ByteCodecs.decode(bytes) - val decoded = bytes.slice(0, len) - val table = EntryTable.fromBytes(decoded) - - val serialized = table.toBytes - - serialized.length shouldBe decoded.length +- 1 // Scala compiler (sometimes) adds an extra zero. We don't. - EntryTable.fromBytes(serialized).toSeq shouldBe table.toSeq - } - } - - private def resolveName(entryTable: EntryTable, name: String): Option[String] = { - val entries = entryTable.toSeq.zipWithIndex - def findNameIndex: Option[Int] = entries.collectFirst { - case (e: NameEntry, index) if e.name == name => index - } - - def findRefEntry(nameIndex: Int): Option[RefEntry] = entries.collectFirst { - case (e: RefEntry, _) if e.nameRef == nameIndex => e - } - - for { - index <- findNameIndex - ref <- findRefEntry(index) - resolved <- entryTable.resolveRef(ref) - } yield resolved - } -}