forked from softprops/assembly-sbt
-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rewrite @ScalaSignature when shading #393
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
1157b1f
Copied and scalayified MainProcessor from jarjar in preparation to ad…
jeroentervoorde 3567fc0
WIP: Initial support for rewriting terms and types in the @ScalaSigna…
jeroentervoorde 291c9b6
Added a test for scalasig annotation processing.
jeroentervoorde bbe2669
Refactored entry table reading and writing.
jeroentervoorde 2eae8d9
Refactored TaggedEntries and simplified the rename loop.
jeroentervoorde 3dc8937
Factored jarjar rules out of the table entry code.
jeroentervoorde 660caae
Support reading and writing ScalaLongSignature
jeroentervoorde 1d35796
Removed plugin fallback version from scalasigannot test
jeroentervoorde dd673f6
Removed build.properties files from the tests
jeroentervoorde e7dae3d
Modified the build file to work with sbt 0.13 to fix the CI
jeroentervoorde 75da71d
Moved most of the code into sbtassembly.scalasig package.
jeroentervoorde 8879855
Updated README.md
jeroentervoorde File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,99 @@ | ||
package org.pantsbuild.jarjar | ||
|
||
import org.pantsbuild.jarjar.util.{EntryStruct, JarProcessor} | ||
import java.io.IOException | ||
|
||
import org.pantsbuild.jarjar.misplaced.MisplacedClassProcessorFactory | ||
import org.pantsbuild.jarjar.util.{EntryStruct, JarProcessor, JarProcessorChain, JarTransformerChain, RemappingClassTransformer, StandaloneJarProcessor} | ||
|
||
import scala.collection.JavaConverters._ | ||
import scala.collection.mutable | ||
|
||
/** | ||
* Creates a new JJProcessor, which automatically generates the standard zap, keep, remap, etc processors. | ||
* This is a copy of the MainProcessor in JarJar with an added ScalaSigProcessor | ||
* | ||
* @param patterns List of rules to parse. | ||
* @param verbose Whether to verbosely log information. | ||
* @param skipManifest If true, omits the manifest file from the processed jar. | ||
* @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 } | ||
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]() | ||
|
||
val kp: KeepProcessor = if (keepList.isEmpty) null else new KeepProcessor(keepList.asJava) | ||
|
||
val pr = new PackageRemapper(ruleList.asJava, verbose) | ||
|
||
class JJProcessor(val proc: JarProcessor) { | ||
val processors: mutable.ArrayBuffer[JarProcessor] = collection.mutable.ArrayBuffer[JarProcessor]() | ||
if (skipManifest) | ||
processors += ManifestProcessor.getInstance | ||
if (kp != null) | ||
processors += kp | ||
|
||
def process(entry: EntryStruct): Boolean = proc.process(entry) | ||
val misplacedClassProcessor: JarProcessor = MisplacedClassProcessorFactory.getInstance.getProcessorForName(misplacedClassStrategy) | ||
processors += new ZapProcessor(zapList.asJava) | ||
processors += misplacedClassProcessor | ||
processors += new JarTransformerChain(Array[RemappingClassTransformer](new RemappingClassTransformer(pr))) | ||
|
||
def getExcludes(): Set[String] = { | ||
val field = proc.getClass().getDeclaredField("kp") | ||
field.setAccessible(true) | ||
val keepProcessor = field.get(proc) | ||
val renamer: String => Option[String] = { | ||
val wildcards = PatternElement.createWildcards(ruleList.asJava).asScala | ||
|
||
if (keepProcessor == null) Set() | ||
else { | ||
val method = proc.getClass().getDeclaredMethod("getExcludes") | ||
method.setAccessible(true) | ||
method.invoke(proc).asInstanceOf[java.util.Set[String]].asScala.toSet | ||
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 | ||
}.headOption | ||
|
||
result | ||
} | ||
} | ||
|
||
} | ||
processors += new ScalaSigProcessor(renamer) | ||
processors += new MethodSignatureProcessor(pr) | ||
processors += new ResourceProcessor(pr) | ||
val chain = new JarProcessorChain(processors.toArray) | ||
|
||
object JJProcessor { | ||
@throws[IOException] | ||
def strip(file: Nothing): Unit = { | ||
if (kp != null) { | ||
val excludes = getExcludes | ||
if (excludes.nonEmpty) StandaloneJarProcessor.run(file, file, new ExcludeProcessor(excludes.asJava, verbose)) | ||
} | ||
} | ||
|
||
def apply(patterns: Seq[PatternElement], verbose: Boolean, skipManifest: Boolean): JJProcessor = | ||
new JJProcessor(new MainProcessor(patterns.asJava, verbose, skipManifest)) | ||
/** | ||
* Returns the <code>.class</code> files to delete. As well the root-parameter as the rename ones | ||
* are taken in consideration, so that the concerned files are not listed in the result. | ||
* | ||
* @return the paths of the files in the jar-archive, including the <code>.class</code> suffix | ||
*/ | ||
def getExcludes: Set[String] = if (kp != null) kp.getExcludes.asScala.map { exclude => | ||
val name = exclude + ".class" | ||
renames.getOrElse(name, name) | ||
}.toSet else Set.empty | ||
|
||
/** | ||
* | ||
* @param struct entry struct to process | ||
* @return <code>true</code> 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) | ||
keepIt | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
src/main/scala/org/pantsbuild/jarjar/ScalaSigProcessor.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.pantsbuild.jarjar | ||
|
||
import org.objectweb.asm.{ClassReader, ClassWriter} | ||
import org.pantsbuild.jarjar.util.{EntryStruct, JarProcessor} | ||
import sbtassembly.scalasig.ScalaSigClassVisitor | ||
|
||
class ScalaSigProcessor(renamer: String => Option[String]) extends JarProcessor { | ||
override def process(struct: EntryStruct): Boolean = { | ||
|
||
if (!struct.name.endsWith(".class") || struct.skipTransform) true | ||
else { | ||
val classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS) | ||
val reader = new ClassReader(struct.data) | ||
|
||
reader.accept(new ScalaSigClassVisitor(classWriter, renamer), ClassReader.EXPAND_FRAMES) | ||
struct.data = classWriter.toByteArray | ||
true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package sbtassembly.scalasig | ||
|
||
// Utility class to read the content of a single table entry | ||
class ByteArrayReader(bytes: Array[Byte]) extends Nat.Reader { | ||
private var readIndex = 0 | ||
|
||
/** Read a byte */ | ||
override def readByte(): Int = { | ||
val x = bytes(readIndex).toInt | ||
readIndex += 1 | ||
x | ||
} | ||
|
||
/** Reads a number of bytes into an array */ | ||
def readBytes(len: Int): Array[Byte] = { | ||
val result = bytes.slice(readIndex, readIndex + len) | ||
readIndex += len | ||
result | ||
} | ||
|
||
def atEnd: Boolean = readIndex == bytes.length | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package sbtassembly.scalasig | ||
|
||
import java.io.ByteArrayOutputStream | ||
|
||
import scala.collection.mutable | ||
import scala.reflect.internal.pickling.PickleFormat | ||
|
||
/** | ||
* Mutable table of tagged entries | ||
* @param majorVersion major table version | ||
* @param minorVersion minor table version | ||
* @param entries initial table entries | ||
*/ | ||
class EntryTable(majorVersion: Int, minorVersion: Int, entries: mutable.Buffer[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 { | ||
case (entry: NameEntry, index) => (entry, index) | ||
}:_* | ||
) | ||
|
||
/** | ||
* Return the current table entries as an immutable seq. | ||
* @return table entries | ||
*/ | ||
def toSeq: Seq[TaggedEntry] = entries.toVector | ||
|
||
/** | ||
* Rename term and type entries in this table according to the renamer function. | ||
* A name or type is referred to by a Ref entry. The existing ref entries are reused to references to them will remain intact. | ||
* Unused entries will not be removed from the table. | ||
* | ||
* @param renamer renames a fully qualified type or term name or return None if it does not match. | ||
*/ | ||
def renameEntries(renamer: String => Option[String]): Unit = { | ||
|
||
entries.zipWithIndex.collect { | ||
case (ref: RefEntry, index) => | ||
entries(ref.nameRef) match { | ||
case nameEntry: NameEntry => | ||
for { | ||
fqName <- resolveRef(ref) | ||
renamed <- renamer(fqName) | ||
} { | ||
val parts = renamed.split('.') | ||
|
||
val myOwner = parts.init.foldLeft(Option.empty[Int]) { (owner, part) => | ||
val nameIndex = getOrAppendNameEntry(NameEntry(PickleFormat.TERMname, part)) | ||
val nextOwner = appendEntry(RefEntry(PickleFormat.EXTMODCLASSref, nameIndex, owner)) | ||
Some(nextOwner) | ||
} | ||
|
||
entries(index) = ref.copy(nameRef = getOrAppendNameEntry(nameEntry.copy(name = parts.last)), ownerRef = myOwner) | ||
} | ||
|
||
case other => | ||
throw new RuntimeException(s"Ref entry does not point to a name but to a ${other.tag}") | ||
} | ||
} | ||
} | ||
|
||
// Return existing name entry or append a new one. | ||
private def getOrAppendNameEntry(name: NameEntry): Int = { | ||
nameIndices.getOrElse(name, appendEntry(name)) | ||
} | ||
|
||
private def appendEntry(entry: TaggedEntry): Int = { | ||
val index = entries.size | ||
entries += entry | ||
|
||
entry match { | ||
case name: NameEntry => | ||
nameIndices.put(name, index) | ||
case _ => // NoOp | ||
} | ||
|
||
index | ||
} | ||
|
||
// Resolves a ref into a fully qualified name | ||
def resolveRef(extMod: RefEntry): Option[String] = { | ||
|
||
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 other => throw new RuntimeException(s"Unexpected type for nameref $other") | ||
} | ||
extMod.ownerRef match { | ||
case None => Some(myName) | ||
case Some(owner) => | ||
entries(owner) match { | ||
case name: NameEntry => | ||
Some(s"$name/$myName") | ||
case mod: RefEntry => | ||
resolveRef(mod).map(p => s"$p.$myName") | ||
case raw: RawEntry if raw.tag == PickleFormat.NONEsym => | ||
None | ||
case raw: RawEntry => | ||
throw new RuntimeException(s"Not a known owner type tag for $myName : ${raw.tag}") | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Serializes this entry table into a byte array. | ||
*/ | ||
def toBytes: Array[Byte] = { | ||
val os = new ByteArrayOutputStream() | ||
val writer = new Nat.Writer { | ||
override def writeByte(b: Int): Unit = os.write(b) | ||
} | ||
|
||
writer.writeNat(majorVersion) | ||
writer.writeNat(minorVersion) | ||
writer.writeNat(entries.size) | ||
|
||
entries.foreach { entry => | ||
val payloadBytes = entry.toBytes | ||
writer.writeNat(entry.tag) // Tag of entry | ||
writer.writeNat(payloadBytes.length) // Size of payload | ||
os.write(payloadBytes) | ||
} | ||
|
||
os.toByteArray | ||
} | ||
} | ||
|
||
object EntryTable { | ||
|
||
/** | ||
* Parse bytes into a EntryTable | ||
*/ | ||
def fromBytes(bytes: Array[Byte]): EntryTable = { | ||
val reader = new ByteArrayReader(bytes) | ||
|
||
val majorVersion = reader.readNat() | ||
val minorVersion = reader.readNat() | ||
|
||
val result = new Array[TaggedEntry](reader.readNat()) | ||
|
||
result.indices foreach { index => | ||
val tag = reader.readNat() | ||
val len = reader.readNat() | ||
|
||
result(index) = TaggedEntry(tag, reader.readBytes(len)) | ||
} | ||
|
||
new EntryTable(majorVersion, minorVersion, result.toBuffer) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe allow
*.sig
files here? - scala/scala#7712There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add it to the list. I have some reading up to do here.