added PruneMerge
This commit is contained in:
parent
7bef4dd042
commit
a6fa3eef08
@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj
|
|||||||
{085399FC-1934-43A4-A9C8-EC3137BAEA8E} = {085399FC-1934-43A4-A9C8-EC3137BAEA8E}
|
{085399FC-1934-43A4-A9C8-EC3137BAEA8E} = {085399FC-1934-43A4-A9C8-EC3137BAEA8E}
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PruneMerge", "PruneMerge\PruneMerge.fsproj", "{2F6C6AAD-44DA-4993-BDDB-F6B3F3424916}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -53,6 +55,10 @@ Global
|
|||||||
{408EA5F6-2A53-4472-90DA-CC80D5ACEA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{408EA5F6-2A53-4472-90DA-CC80D5ACEA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{408EA5F6-2A53-4472-90DA-CC80D5ACEA13}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{408EA5F6-2A53-4472-90DA-CC80D5ACEA13}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{408EA5F6-2A53-4472-90DA-CC80D5ACEA13}.Release|Any CPU.Build.0 = Release|Any CPU
|
{408EA5F6-2A53-4472-90DA-CC80D5ACEA13}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2F6C6AAD-44DA-4993-BDDB-F6B3F3424916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2F6C6AAD-44DA-4993-BDDB-F6B3F3424916}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2F6C6AAD-44DA-4993-BDDB-F6B3F3424916}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2F6C6AAD-44DA-4993-BDDB-F6B3F3424916}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -96,6 +96,10 @@ namespace SpaceFlint.JavaBinary
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int Count => pool.Count;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public JavaConstant Get(int index)
|
public JavaConstant Get(int index)
|
||||||
{
|
{
|
||||||
return ((index > 0 && index < pool.Count) ? pool[index] : null);
|
return ((index > 0 && index < pool.Count) ? pool[index] : null);
|
||||||
|
@ -10,6 +10,13 @@ namespace SpaceFlint.JavaBinary
|
|||||||
public class JavaReader
|
public class JavaReader
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public class JavaClassEx
|
||||||
|
{
|
||||||
|
public JavaClass JavaClass;
|
||||||
|
public JavaConstantPool Constants;
|
||||||
|
public byte[] RawBytes;
|
||||||
|
}
|
||||||
|
|
||||||
internal JavaClass Class;
|
internal JavaClass Class;
|
||||||
internal JavaException.Where Where;
|
internal JavaException.Where Where;
|
||||||
|
|
||||||
@ -31,12 +38,11 @@ namespace SpaceFlint.JavaBinary
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static JavaClass ReadClass(System.IO.Compression.ZipArchiveEntry entry,
|
public static JavaClassEx ReadClassEx(System.IO.Compression.ZipArchiveEntry entry,
|
||||||
bool withCode = true)
|
bool withCode = true)
|
||||||
{
|
{
|
||||||
JavaClass jclass = null;
|
if (entry.Length > 4 &&
|
||||||
if ( (! string.IsNullOrEmpty(Path.GetFileName(entry.FullName)))
|
(! string.IsNullOrEmpty(Path.GetFileName(entry.FullName))))
|
||||||
&& entry.Length > 4)
|
|
||||||
{
|
{
|
||||||
using (var stream = entry.Open())
|
using (var stream = entry.Open())
|
||||||
{
|
{
|
||||||
@ -54,22 +60,35 @@ namespace SpaceFlint.JavaBinary
|
|||||||
stream2.Position = 0;
|
stream2.Position = 0;
|
||||||
|
|
||||||
var whereText = $"entry '{entry.FullName}' in archive";
|
var whereText = $"entry '{entry.FullName}' in archive";
|
||||||
jclass = (new JavaReader(stream2, whereText, withCode)).Class;
|
var rdr = new JavaReader(stream2, whereText, withCode);
|
||||||
|
|
||||||
if (jclass != null)
|
if (rdr.Class != null)
|
||||||
{
|
{
|
||||||
jclass.PackageNameLength =
|
rdr.Class.PackageNameLength =
|
||||||
(short) Path.GetDirectoryName(entry.FullName).Length;
|
(short) Path.GetDirectoryName(entry.FullName).Length;
|
||||||
|
|
||||||
|
return new JavaClassEx
|
||||||
|
{
|
||||||
|
JavaClass = rdr.Class,
|
||||||
|
Constants = rdr.constants,
|
||||||
|
RawBytes = stream2.ToArray()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jclass;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static JavaClass ReadClass(System.IO.Compression.ZipArchiveEntry entry,
|
||||||
|
bool withCode = true)
|
||||||
|
=> ReadClassEx(entry, withCode)?.JavaClass;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JavaReader(Stream _stream, string whereText, bool withCode)
|
JavaReader(Stream _stream, string whereText, bool withCode)
|
||||||
{
|
{
|
||||||
Where = new JavaException.Where();
|
Where = new JavaException.Where();
|
||||||
|
@ -562,7 +562,7 @@ namespace SpaceFlint.JavaBinary
|
|||||||
|
|
||||||
ushort lastOffset = 0xFFFF;
|
ushort lastOffset = 0xFFFF;
|
||||||
if (! frames.TryGetValue(0, out var lastFrame))
|
if (! frames.TryGetValue(0, out var lastFrame))
|
||||||
throw new Exception("stackmap does not include frame for offset 0");
|
throw wtr.Where.Exception("stackmap does not include frame for offset 0");
|
||||||
|
|
||||||
var frames3 = new List<JavaAttribute.StackMapTable.Item>();
|
var frames3 = new List<JavaAttribute.StackMapTable.Item>();
|
||||||
foreach (var kvp in frames2)
|
foreach (var kvp in frames2)
|
||||||
|
32
PruneMerge/Cmdline.fs
Normal file
32
PruneMerge/Cmdline.fs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
module Cmdline
|
||||||
|
|
||||||
|
type Cmdline = {
|
||||||
|
Inputs: string list
|
||||||
|
Output: string
|
||||||
|
Roots: string list
|
||||||
|
}
|
||||||
|
|
||||||
|
let private exeName = System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName
|
||||||
|
|
||||||
|
let private usage () =
|
||||||
|
printfn "usage: %s inputfile [inputfile2..] outputfile :keepclass [:keepclass2..]" exeName
|
||||||
|
printfn "Merges one or more input JARs into one output JAR, while pruning unreferenced classes."
|
||||||
|
None
|
||||||
|
|
||||||
|
let private parseArgsList (args: string list) =
|
||||||
|
let (classes, args) = args |> List.partition (fun arg -> arg.Length > 1 && arg.StartsWith ":")
|
||||||
|
let (files, args) = args |> List.partition (fun arg -> arg.Length > 0 && not (arg.StartsWith ":"))
|
||||||
|
if files.Length >= 2 && classes.Length >= 1 && args.Length = 0
|
||||||
|
then Some { Inputs = List.take (files.Length - 1) files
|
||||||
|
Output = List.last files
|
||||||
|
Roots = classes |> List.map (fun arg -> arg.Substring 1)
|
||||||
|
}
|
||||||
|
else usage()
|
||||||
|
|
||||||
|
let print format =
|
||||||
|
Printf.ksprintf (fun msg -> printfn "%s: %s" exeName msg) format
|
||||||
|
|
||||||
|
let parse args =
|
||||||
|
if isNull args then usage ()
|
||||||
|
else Array.toList args |> parseArgsList
|
93
PruneMerge/Program.fs
Normal file
93
PruneMerge/Program.fs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
|
||||||
|
module PruneMerge
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.IO
|
||||||
|
open System.IO.Compression
|
||||||
|
open System.Collections.Generic
|
||||||
|
open SpaceFlint.JavaBinary
|
||||||
|
|
||||||
|
let private SuccessExitCode: int = 0
|
||||||
|
let private ErrorExitCode: int = 1
|
||||||
|
exception IoError of string * Exception
|
||||||
|
|
||||||
|
let readJar path =
|
||||||
|
try
|
||||||
|
use file = File.OpenRead path
|
||||||
|
use jar = new ZipArchive (file, ZipArchiveMode.Read)
|
||||||
|
jar.Entries |> Seq.choose (fun entry -> JavaReader.ReadClassEx (entry, false) |> Option.ofObj)
|
||||||
|
|> Seq.toList
|
||||||
|
with
|
||||||
|
| ex -> raise (IoError ((sprintf "reading '%s'" path), ex))
|
||||||
|
|
||||||
|
let readJarsIntoMap inputs =
|
||||||
|
let known = HashSet()
|
||||||
|
inputs |> List.map readJar |> List.concat
|
||||||
|
|> List.map (fun clsex ->
|
||||||
|
let cls = clsex.JavaClass
|
||||||
|
if not (known.Add cls.Name)
|
||||||
|
then failwithf "duplicate class '%s'" cls.Name
|
||||||
|
else (cls.Name, clsex))
|
||||||
|
|> Map.ofList
|
||||||
|
|
||||||
|
let writeListIntoJar (allClasses: Map<string, JavaReader.JavaClassEx>) keepClasses path =
|
||||||
|
try
|
||||||
|
use file = File.Open (path, FileMode.CreateNew)
|
||||||
|
use jar = new ZipArchive (file, ZipArchiveMode.Update)
|
||||||
|
keepClasses |> Set.iter (fun name ->
|
||||||
|
let bytes = (allClasses |> Map.find name).RawBytes
|
||||||
|
let name = name.Replace('.', '/') + ".class"
|
||||||
|
let entry = jar.CreateEntry (name, CompressionLevel.Optimal)
|
||||||
|
use entryStream = entry.Open()
|
||||||
|
entryStream.Write(bytes, 0, bytes.Length)
|
||||||
|
)
|
||||||
|
with
|
||||||
|
| ex -> raise (IoError ((sprintf "writing '%s'" path), ex))
|
||||||
|
|
||||||
|
let shrink (allClasses: Map<string, JavaReader.JavaClassEx>) (filterNames: string list) =
|
||||||
|
|
||||||
|
let getClassRefName (constpool: JavaConstantPool) idx =
|
||||||
|
match constpool.Get (idx) with
|
||||||
|
| :? JavaConstant.Class as c ->
|
||||||
|
match constpool.Get (int (c.stringIndex)) with
|
||||||
|
| :? JavaConstant.Utf8 as u -> u.str
|
||||||
|
| _ -> ""
|
||||||
|
| _ -> ""
|
||||||
|
|
||||||
|
let extractClassRefs name (constpool: JavaConstantPool) =
|
||||||
|
let mutable refs = List.empty
|
||||||
|
for i = constpool.Count - 1 downto 0 do
|
||||||
|
let name2 = getClassRefName constpool i
|
||||||
|
if name2.Length > 0
|
||||||
|
then refs <- name2.Replace('/', '.') :: refs
|
||||||
|
refs
|
||||||
|
|
||||||
|
let rec addClass name keep important =
|
||||||
|
if Set.contains name keep then keep
|
||||||
|
else match allClasses |> Map.tryFind name with
|
||||||
|
| None -> if important
|
||||||
|
then failwithf "class '%s' not found in input" name
|
||||||
|
else keep
|
||||||
|
| Some clsex -> extractClassRefs name clsex.Constants
|
||||||
|
|> List.fold (fun keep name -> addClass name keep false)
|
||||||
|
(Set.add name keep)
|
||||||
|
|
||||||
|
filterNames |> List.fold (fun keep name -> addClass name keep true) Set.empty
|
||||||
|
|
||||||
|
[<EntryPoint>]
|
||||||
|
let main args =
|
||||||
|
Cmdline.parse args |> Option.exists (fun cmd ->
|
||||||
|
if File.Exists cmd.Output then
|
||||||
|
Cmdline.print "error: output file '%s' already exists" cmd.Output
|
||||||
|
false
|
||||||
|
else try
|
||||||
|
let allClasses = readJarsIntoMap cmd.Inputs
|
||||||
|
let keepClasses = shrink allClasses cmd.Roots
|
||||||
|
Cmdline.print "%d classes in input, %d classes in output" allClasses.Count keepClasses.Count
|
||||||
|
writeListIntoJar allClasses keepClasses cmd.Output
|
||||||
|
true
|
||||||
|
with
|
||||||
|
| Failure msg -> Cmdline.print "error: %s" msg; false
|
||||||
|
| IoError (path, ex) -> Cmdline.print "error %s: %s: %s" path (ex.GetType().Name) ex.Message ; false
|
||||||
|
| (* any *) ex -> Cmdline.print "%s: %s" (ex.GetType().Name) ex.Message ; false
|
||||||
|
) |> (fun ok -> if ok then 0 else 1) // program exit code
|
28
PruneMerge/PruneMerge.fsproj
Normal file
28
PruneMerge/PruneMerge.fsproj
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{2F6C6AAD-44DA-4993-BDDB-F6B3F3424916}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>SpaceFlint.PruneMerge</RootNamespace>
|
||||||
|
<AssemblyName>PruneMerge</AssemblyName>
|
||||||
|
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||||
|
<ProjectLanguage>FSharp</ProjectLanguage>
|
||||||
|
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="..\Solution.project" />
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Cmdline.fs" />
|
||||||
|
<Compile Include="Program.fs" />
|
||||||
|
<Content Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\JavaBinary\JavaBinary.csproj">
|
||||||
|
<Project>{e9ad82d2-f50f-47d0-af81-98fae604d910}</Project>
|
||||||
|
<Name>JavaBinary</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<Reference Include="FSharp.Core">
|
||||||
|
<HintPath>..\packages\FSharp.Core.4.7.2\lib\net45\FSharp.Core.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.IO.Compression" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
4
PruneMerge/packages.config
Normal file
4
PruneMerge/packages.config
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="FSharp.Core" version="4.7.2" targetFramework="net472" />
|
||||||
|
</packages>
|
@ -372,6 +372,7 @@ namespace Tests
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void TestNestedTry7()
|
void TestNestedTry7()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -386,6 +387,7 @@ namespace Tests
|
|||||||
{
|
{
|
||||||
Console.WriteLine("Try2.Finally");
|
Console.WriteLine("Try2.Finally");
|
||||||
}
|
}
|
||||||
|
#pragma warning disable 0162
|
||||||
Console.WriteLine("Try1.Stmt2");
|
Console.WriteLine("Try1.Stmt2");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
Loading…
x
Reference in New Issue
Block a user