Skip to content

Commit a1f5558

Browse files
committed
Moar stepz
1 parent aeaab2b commit a1f5558

File tree

5 files changed

+189
-60
lines changed

5 files changed

+189
-60
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
5+
using Microsoft.Android.Build.Tasks;
6+
using Microsoft.Build.Framework;
7+
8+
namespace Xamarin.Android.Tasks;
9+
10+
public abstract class AssemblyNativeSourceGenerationTask : AndroidTask
11+
{
12+
protected sealed class CompressionResult
13+
{
14+
public bool Compressed;
15+
public uint CompressedSize;
16+
public string OutputFile;
17+
public FileInfo InputFileInfo;
18+
}
19+
20+
[Required]
21+
public string SourcesOutputDirectory { get; set; }
22+
23+
[Required]
24+
public bool EnableCompression { get; set; }
25+
26+
[Required]
27+
public string CompressedAssembliesOutputDirectory { get; set; }
28+
29+
AssemblyCompression? assemblyCompressor = null;
30+
31+
public override bool RunTask ()
32+
{
33+
if (EnableCompression) {
34+
assemblyCompressor = new AssemblyCompression (Log, CompressedAssembliesOutputDirectory);
35+
Log.LogDebugMessage ("Assembly compression ENABLED");
36+
} else {
37+
Log.LogDebugMessage ("Assembly compression DISABLED");
38+
}
39+
40+
Generate ();
41+
return !Log.HasLoggedErrors;
42+
}
43+
44+
protected CompressionResult Compress (ITaskItem assembly)
45+
{
46+
return Compress (assembly.ItemSpec, assembly.GetMetadata ("DestinationSubDirectory"));
47+
}
48+
49+
protected CompressionResult Compress (string assemblyPath, string? destinationSubdirectory = null)
50+
{
51+
FileInfo fi = new (assemblyPath);
52+
53+
bool compressed;
54+
string outputFile;
55+
uint compressedSize = 0;
56+
57+
if (assemblyCompressor != null) {
58+
(outputFile, compressed) = assemblyCompressor.CompressAssembly (assemblyPath, fi, destinationSubdirectory);
59+
60+
if (!compressed) {
61+
compressedSize = 0;
62+
} else {
63+
var cfi = new FileInfo (outputFile);
64+
compressedSize = (uint)cfi.Length;
65+
}
66+
} else {
67+
outputFile = assemblyPath;
68+
compressed = false;
69+
compressedSize = 0;
70+
}
71+
72+
Log.LogDebugMessage ($" will include from: {outputFile} (compressed? {compressed}; compressedSize == {compressedSize}");
73+
return new CompressionResult {
74+
Compressed = compressed,
75+
CompressedSize = compressedSize,
76+
OutputFile = outputFile,
77+
InputFileInfo = fi,
78+
};
79+
}
80+
81+
internal void GenerateSources (ICollection<string> supportedAbis, LLVMIR.LlvmIrComposer generator, LLVMIR.LlvmIrModule module, string baseFileName)
82+
{
83+
foreach (string abi in supportedAbis) {
84+
string targetAbi = abi.ToLowerInvariant ();
85+
string outputAsmFilePath = Path.Combine (SourcesOutputDirectory, $"{baseFileName}.{targetAbi}.ll");
86+
87+
using var sw = MemoryStreamPool.Shared.CreateStreamWriter ();
88+
try {
89+
generator.Generate (module, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, outputAsmFilePath);
90+
} catch {
91+
throw;
92+
} finally {
93+
sw.Flush ();
94+
}
95+
96+
if (Files.CopyIfStreamChanged (sw.BaseStream, outputAsmFilePath)) {
97+
Log.LogDebugMessage ($"File {outputAsmFilePath} was (re)generated");
98+
}
99+
}
100+
}
101+
102+
protected string GetAssemblyName (ITaskItem assembly)
103+
{
104+
if (!MonoAndroidHelper.IsSatelliteAssembly (assembly)) {
105+
return Path.GetFileName (assembly.ItemSpec);
106+
}
107+
108+
// It's a satellite assembly, %(DestinationSubDirectory) is the culture prefix
109+
string? destinationSubDir = assembly.GetMetadata ("DestinationSubDirectory");
110+
if (String.IsNullOrEmpty (destinationSubDir)) {
111+
throw new InvalidOperationException ($"Satellite assembly '{assembly.ItemSpec}' has no culture metadata item");
112+
}
113+
114+
string ret = $"{destinationSubDir}{Path.GetFileName (assembly.ItemSpec)}";
115+
if (!assembly.ItemSpec.EndsWith (ret, StringComparison.OrdinalIgnoreCase)) {
116+
throw new InvalidOperationException ($"Invalid metadata in satellite assembly '{assembly.ItemSpec}', culture metadata ('{destinationSubDir}') doesn't match file path");
117+
}
118+
119+
return ret;
120+
}
121+
122+
internal DSOAssemblyInfo MakeAssemblyInfo (ITaskItem assembly, string inputFile, long fileLength, uint compressedSize)
123+
{
124+
return new DSOAssemblyInfo (GetAssemblyName (assembly), inputFile, (uint)fileLength, compressedSize);
125+
}
126+
127+
protected abstract void Generate ();
128+
}

src/Xamarin.Android.Build.Tasks/Tasks/BuildAndLinkStandaloneAssemblyDSOs.cs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@ public class BuildAndLinkStandaloneAssemblyDSOs : AssemblyNativeSourceGeneration
1515
sealed class TargetDSO
1616
{
1717
public readonly string Abi;
18-
public readonly string InputAssemblyPath;
19-
public readonly string SourceFileName;
18+
public readonly string OriginalAssemblyPath;
19+
public readonly string SourceFilePath;
2020
public readonly string DSOPath;
2121
public readonly string? Culture;
22+
public readonly ITaskItem TaskItem;
2223

23-
public TargetDSO (ITaskItem dso)
24+
public TargetDSO (ITaskItem dso, string sourcesDir)
2425
{
26+
TaskItem = dso;
2527
DSOPath = dso.ItemSpec;
2628
Abi = EnsureValidMetadata ("Abi");
27-
InputAssemblyPath = EnsureValidMetadata ("InputAssemblyPath");
28-
SourceFileName = EnsureValidMetadata ("SourceFileName");
29+
OriginalAssemblyPath = EnsureValidMetadata ("InputAssemblyPath");
30+
SourceFilePath = Path.Combine (sourcesDir, EnsureValidMetadata ("SourceFileName"));
2931
Culture = dso.GetMetadata ("SatelliteAssemblyCulture");
3032

3133
string EnsureValidMetadata (string what)
@@ -53,15 +55,20 @@ string EnsureValidMetadata (string what)
5355

5456
protected override void Generate ()
5557
{
58+
var assemblies = new Dictionary<string, Dictionary<AndroidTargetArch, DSOAssemblyInfo>> (StringComparer.OrdinalIgnoreCase);
5659
var sharedLibraries = new List<ITaskItem> ();
60+
5761
foreach (ITaskItem item in TargetSharedLibraries) {
58-
var dso = new TargetDSO (item);
62+
var dso = new TargetDSO (item, SourcesOutputDirectory);
63+
string inputFilePath = AddAssembly (dso, assemblies);
64+
5965
var dsoItem = new TaskItem (dso.DSOPath);
6066

6167
dsoItem.SetMetadata ("DataSymbolOffset", "<TODO>");
6268
dsoItem.SetMetadata ("DataSize", "<TODO>");
6369
dsoItem.SetMetadata ("Compressed", "<TODO>");
64-
dsoItem.SetMetadata ("InputAssemblyPath", dso.InputAssemblyPath);
70+
dsoItem.SetMetadata ("OriginalAssemblyPath", dso.OriginalAssemblyPath);
71+
dsoItem.SetMetadata ("InputAssemblyPath", inputFilePath);
6572

6673
if (!String.IsNullOrEmpty (dso.Culture)) {
6774
dsoItem.SetMetadata ("SatelliteAssemblyCulture", dso.Culture);
@@ -72,4 +79,36 @@ protected override void Generate ()
7279

7380
SharedLibraries = sharedLibraries.ToArray ();
7481
}
82+
83+
string AddAssembly (TargetDSO dso, Dictionary<string, Dictionary<AndroidTargetArch, DSOAssemblyInfo>> assemblies)
84+
{
85+
string asmName = Path.GetFileNameWithoutExtension (dso.OriginalAssemblyPath);
86+
if (!String.IsNullOrEmpty (dso.Culture)) {
87+
asmName = $"{dso.Culture}/{asmName}";
88+
}
89+
90+
if (!assemblies.TryGetValue (asmName, out Dictionary<AndroidTargetArch, DSOAssemblyInfo> infos)) {
91+
infos = new Dictionary<AndroidTargetArch, DSOAssemblyInfo> ();
92+
assemblies.Add (asmName, infos);
93+
}
94+
95+
string destinationSubdirectory = dso.Abi;
96+
if (!String.IsNullOrEmpty (dso.Culture)) {
97+
destinationSubdirectory = Path.Combine (destinationSubdirectory, dso.Culture);
98+
}
99+
100+
CompressionResult cres = Compress (dso.OriginalAssemblyPath, destinationSubdirectory);
101+
string inputFile = cres.OutputFile;
102+
103+
AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (dso.Abi);
104+
DSOAssemblyInfo dsoInfo = MakeAssemblyInfo (dso.TaskItem, inputFile, cres.InputFileInfo.Length, cres.CompressedSize);
105+
106+
try {
107+
infos.Add (targetArch, dsoInfo);
108+
} catch (Exception ex) {
109+
throw new InvalidOperationException ($"Internal error: failed to add '{dso.OriginalAssemblyPath}' for target arch {targetArch}", ex);
110+
}
111+
112+
return dsoInfo.InputFile;
113+
}
75114
}

src/Xamarin.Android.Build.Tasks/Tasks/GenerateAppAssemblyDSONativeSourceFiles.cs

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -65,28 +65,7 @@ protected override void Generate ()
6565
}
6666

6767
var generator = new AssemblyDSOGenerator (FastPathAssemblyNames, dsoAssembliesInfo, inputAssemblyDataSize, uncompressedAssemblyDataSize);
68-
GenerateSources (generator, generator.Construct (), PrepareAbiItems.AssemblyDSOBase);
69-
70-
void GenerateSources (LLVMIR.LlvmIrComposer generator, LLVMIR.LlvmIrModule module, string baseFileName)
71-
{
72-
foreach (string abi in SupportedAbis) {
73-
string targetAbi = abi.ToLowerInvariant ();
74-
string outputAsmFilePath = Path.Combine (SourcesOutputDirectory, $"{baseFileName}.{targetAbi}.ll");
75-
76-
using var sw = MemoryStreamPool.Shared.CreateStreamWriter ();
77-
try {
78-
generator.Generate (module, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, outputAsmFilePath);
79-
} catch {
80-
throw;
81-
} finally {
82-
sw.Flush ();
83-
}
84-
85-
if (Files.CopyIfStreamChanged (sw.BaseStream, outputAsmFilePath)) {
86-
Log.LogDebugMessage ($"File {outputAsmFilePath} was (re)generated");
87-
}
88-
}
89-
}
68+
GenerateSources (SupportedAbis, generator, generator.Construct (), PrepareAbiItems.AssemblyDSOBase);
9069

9170
void StoreAssembly (AndroidTargetArch arch, ITaskItem assembly, string inputFile, long fileLength, uint compressedSize, DSOAssemblyInfo? info = null)
9271
{
@@ -102,30 +81,5 @@ void AddAssemblyToList (AndroidTargetArch arch, DSOAssemblyInfo info)
10281
assemblyList.Add (info);
10382
Log.LogDebugMessage ($" added to arch {arch} with name: {assemblyList[assemblyList.Count - 1].Name}");
10483
}
105-
106-
DSOAssemblyInfo MakeAssemblyInfo (ITaskItem assembly, string inputFile, long fileLength, uint compressedSize)
107-
{
108-
return new DSOAssemblyInfo (GetAssemblyName (assembly), inputFile, (uint)fileLength, compressedSize);
109-
}
110-
}
111-
112-
string GetAssemblyName (ITaskItem assembly)
113-
{
114-
if (!MonoAndroidHelper.IsSatelliteAssembly (assembly)) {
115-
return Path.GetFileName (assembly.ItemSpec);
116-
}
117-
118-
// It's a satellite assembly, %(DestinationSubDirectory) is the culture prefix
119-
string? destinationSubDir = assembly.GetMetadata ("DestinationSubDirectory");
120-
if (String.IsNullOrEmpty (destinationSubDir)) {
121-
throw new InvalidOperationException ($"Satellite assembly '{assembly.ItemSpec}' has no culture metadata item");
122-
}
123-
124-
string ret = $"{destinationSubDir}{Path.GetFileName (assembly.ItemSpec)}";
125-
if (!assembly.ItemSpec.EndsWith (ret, StringComparison.OrdinalIgnoreCase)) {
126-
throw new InvalidOperationException ($"Invalid metadata in satellite assembly '{assembly.ItemSpec}', culture metadata ('{destinationSubDir}') doesn't match file path");
127-
}
128-
129-
return ret;
13084
}
13185
}

src/Xamarin.Android.Build.Tasks/Tasks/PrepareAssemblyStandaloneDSOAbiItems.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ ITaskItem MakeTaskItem (ITaskItem assembly, string abi, string baseName)
8686
item.SetMetadata ("Abi", abi);
8787
item.SetMetadata ("InputAssemblyPath", assembly.ItemSpec);
8888
item.SetMetadata ("SourceFileName", MonoAndroidHelper.MakeNativeAssemblyFileName (baseName, abi));
89+
string skipCompression = assembly.GetMetadata ("AndroidSkipCompression");
90+
if (!String.IsNullOrEmpty (skipCompression)) {
91+
item.SetMetadata ("AndroidSkipCompression", skipCompression);
92+
}
8993

9094
return item;
9195
}

src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,34 +127,38 @@ CompressionResult Compress (AssemblyData data, string outputDirectory)
127127
return (assembly.ItemSpec, false);
128128
}
129129

130+
return CompressAssembly (assembly.ItemSpec, inputInfo, assembly.GetMetadata ("DestinationSubDirectory"));
131+
}
132+
133+
public (string outputPath, bool compressed) CompressAssembly (string assemblyPath, FileInfo inputInfo, string? subDirectory)
134+
{
130135
if (!inputInfo.Exists) {
131-
throw new InvalidOperationException ($"File '{assembly.ItemSpec}' does not exist");
136+
throw new InvalidOperationException ($"File '{assemblyPath}' does not exist");
132137
}
133138

134139
string assemblyOutputDir;
135-
string? subDirectory = assembly.GetMetadata ("DestinationSubDirectory");
136140
if (!String.IsNullOrEmpty (subDirectory)) {
137141
assemblyOutputDir = Path.Combine (compressedOutputDir, subDirectory);
138142
} else {
139143
assemblyOutputDir = compressedOutputDir;
140144
}
141-
string outputPath = Path.Combine (assemblyOutputDir, $"{Path.GetFileName (assembly.ItemSpec)}.lz4");
145+
string outputPath = Path.Combine (assemblyOutputDir, $"{Path.GetFileName (assemblyPath)}.lz4");
142146
Directory.CreateDirectory (assemblyOutputDir);
143147

144148
byte[]? sourceBytes = null;
145149
byte[]? destBytes = null;
146150
try {
147151
int inputLength = checked((int)inputInfo.Length);
148152
sourceBytes = bytePool.Rent (inputLength);
149-
using (var fs = File.Open (assembly.ItemSpec, FileMode.Open, FileAccess.Read, FileShare.Read)) {
153+
using (var fs = File.Open (assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
150154
fs.Read (sourceBytes, 0, inputLength);
151155
}
152156

153157
destBytes = bytePool.Rent (LZ4Codec.MaximumOutputSize (sourceBytes.Length));
154158
int encodedLength = LZ4Codec.Encode (sourceBytes, 0, inputLength, destBytes, 0, destBytes.Length, LZ4Level.L09_HC);
155159
if (encodedLength < 0) {
156-
log.LogMessage ($"Failed to compress {assembly.ItemSpec}");
157-
return (assembly.ItemSpec, false);
160+
log.LogMessage ($"Failed to compress {assemblyPath}");
161+
return (assemblyPath, false);
158162
}
159163

160164
using (var fs = File.Open (outputPath, FileMode.Create, FileAccess.Write, FileShare.Read)) {

0 commit comments

Comments
 (0)