Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Configuration.props
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@
<SqliteSourceDirectory Condition=" '$(SqliteSourceDirectory)' == '' ">$(MSBuildThisFileDirectory)external\sqlite</SqliteSourceDirectory>
<LibUnwindSourceDirectory Condition=" '$(LibUnwindSourceDirectory)' == '' ">$(MSBuildThisFileDirectory)external\libunwind</LibUnwindSourceDirectory>
<LibUnwindGeneratedHeadersDirectory Condition=" '$(LibUnwindGeneratedHeadersDirectory)' == '' ">$(BootstrapOutputDirectory)\libunwind</LibUnwindGeneratedHeadersDirectory>
<LZ4SourceDirectory Condition=" '$(LZ4SourceDirectory)' == '' ">$(MSBuildThisFileDirectory)external\lz4</LZ4SourceDirectory>
<XamarinAndroidSourcePath>$(MSBuildThisFileDirectory)</XamarinAndroidSourcePath>
<ThirdPartySourcePath>$(MSBuildThisFileDirectory)src-ThirdParty\</ThirdPartySourcePath>
<AllSupported32BitTargetAndroidAbis>armeabi-v7a;x86</AllSupported32BitTargetAndroidAbis>
Expand Down Expand Up @@ -165,7 +164,6 @@
<SqliteSourceFullPath>$([System.IO.Path]::GetFullPath ('$(SqliteSourceDirectory)'))</SqliteSourceFullPath>
<LibUnwindSourceFullPath>$([System.IO.Path]::GetFullPath ('$(LibUnwindSourceDirectory)'))</LibUnwindSourceFullPath>
<LibUnwindGeneratedHeadersFullPath>$([System.IO.Path]::GetFullPath ('$(LibUnwindGeneratedHeadersDirectory)'))</LibUnwindGeneratedHeadersFullPath>
<LZ4SourceFullPath>$([System.IO.Path]::GetFullPath ('$(LZ4SourceDirectory)'))</LZ4SourceFullPath>
<JavaInteropTargetFrameworkVersion>net10.0</JavaInteropTargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
Expand Down
6 changes: 6 additions & 0 deletions Xamarin.Android.Build.Tasks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29920.165
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Build.Tasks", "src\Xamarin.Android.Build.Tasks\Xamarin.Android.Build.Tasks.csproj", "{3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.Tasks", "src\Microsoft.Android.Build.Tasks\Microsoft.Android.Build.Tasks.csproj", "{5DB5BB32-F094-46C8-9C81-4FFEC0BAF5D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Build.Tests", "src\Xamarin.Android.Build.Tasks\Tests\Xamarin.Android.Build.Tests\Xamarin.Android.Build.Tests.csproj", "{53E4ABF0-1085-45F9-B964-DCAE4B819998}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.ProjectTools", "src\Xamarin.Android.Build.Tasks\Tests\Xamarin.ProjectTools\Xamarin.ProjectTools.csproj", "{2DD1EE75-6D8D-4653-A800-0A24367F7F38}"
Expand Down Expand Up @@ -44,6 +46,10 @@ Global
{3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|Any CPU.Build.0 = Release|Any CPU
{5DB5BB32-F094-46C8-9C81-4FFEC0BAF5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DB5BB32-F094-46C8-9C81-4FFEC0BAF5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DB5BB32-F094-46C8-9C81-4FFEC0BAF5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DB5BB32-F094-46C8-9C81-4FFEC0BAF5D2}.Release|Any CPU.Build.0 = Release|Any CPU
{53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
21 changes: 21 additions & 0 deletions Xamarin.Android.sln
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Build.Debug
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fastdevtools", "tools\fastdev\fastdevtools.csproj", "{4723786E-4BB5-4939-BBFD-3C2D0EAE236B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Build.Tasks", "src\Microsoft.Android.Build.Tasks\Microsoft.Android.Build.Tasks.csproj", "{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "xamarin-android-tools", "xamarin-android-tools", "{BB3C8E1B-31A2-1C4E-0B3F-36B3F475D6AA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{65925F91-7BB8-AC56-CADB-FB3DD28A5B5B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1021,6 +1027,18 @@ Global
{4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|x64.Build.0 = Release|Any CPU
{4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|x86.ActiveCfg = Release|Any CPU
{4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|x86.Build.0 = Release|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Debug|x64.Build.0 = Debug|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Debug|x86.ActiveCfg = Debug|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Debug|x86.Build.0 = Debug|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Release|Any CPU.Build.0 = Release|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Release|x64.ActiveCfg = Release|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Release|x64.Build.0 = Release|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Release|x86.ActiveCfg = Release|Any CPU
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1095,6 +1113,9 @@ Global
{79BBD075-BC73-4D35-8DE6-BE70C9AFC708} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723}
{624C58EB-57CF-4754-95EF-E9893E584296} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723}
{4723786E-4BB5-4939-BBFD-3C2D0EAE236B} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723}
{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723}
{BB3C8E1B-31A2-1C4E-0B3F-36B3F475D6AA} = {05C3B1D6-A4CE-4534-A9E4-E9117591ADF7}
{65925F91-7BB8-AC56-CADB-FB3DD28A5B5B} = {BB3C8E1B-31A2-1C4E-0B3F-36B3F475D6AA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53A1F287-EFB2-4D97-A4BB-4A5E145613F6}
Expand Down
2 changes: 2 additions & 0 deletions build-tools/installers/create-installers.targets
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Sdk.Bindings.Gradle.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.dll" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.pdb" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Build.Tasks.dll" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Build.Tasks.pdb" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Sdk.TrimmableTypeMap.dll" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Sdk.TrimmableTypeMap.pdb" />
<_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\Microsoft.Android.Build.BaseTasks.resources.dll')" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Configuration.props" />

<!--
This assembly hosts MSBuild tasks that require .NET (net11.0) APIs that are not available in
netstandard2.0, so they cannot live in Xamarin.Android.Build.Tasks.dll (which multi-targets
netstandard2.0 to run on both .NET Framework and .NET MSBuild). Tasks here are imported with
<UsingTask ... Runtime="NET" TaskFactory="TaskHostFactory" /> so they run out-of-process on .NET.
Keep this assembly small and self-contained: link only the minimal helpers it needs.
-->
<PropertyGroup>
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
<RootNamespace>Microsoft.Android.Tasks</RootNamespace>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<OutputPath>$(MicrosoftAndroidSdkOutDir)</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<!-- Microsoft.Build.* references flow transitively from Microsoft.Android.Build.BaseTasks. -->
<ItemGroup>
<ProjectReference Include="..\..\external\xamarin-android-tools\src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj" />
</ItemGroup>

<!--
Link the shared Resources so the XA#### messages stay localized. The linked Resources.Designer.cs
resolves strings via ResourceManager ("Xamarin.Android.Tasks.Properties.Resources"), so pin the
embedded resource's name to match even though RootNamespace differs.
-->
<ItemGroup>
<Compile Include="..\Xamarin.Android.Build.Tasks\Properties\Resources.Designer.cs" Link="Properties\Resources.Designer.cs" />
<EmbeddedResource Include="..\Xamarin.Android.Build.Tasks\Properties\Resources.resx" Link="Properties\Resources.resx">
<ManifestResourceName>Xamarin.Android.Tasks.Properties.Resources</ManifestResourceName>
</EmbeddedResource>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
#nullable enable
using System;
using System.Collections.Generic;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Properties = Xamarin.Android.Tasks.Properties;

namespace Xamarin.Android.Tasks;
namespace Microsoft.Android.Tasks;

/// <summary>
/// Compresses assemblies using LZ4 compression before placing them in the APK.
/// Compresses assemblies using Zstandard compression before placing them in the APK.
/// Note this is independent of whether they are stored compressed with ZIP in the APK.
/// Our runtime bits will LZ4 decompress them at assembly load time.
/// Our runtime bits will Zstd decompress them at assembly load time.
///
/// This task lives in Microsoft.Android.Build.Tasks.dll (net11.0) because it uses
/// System.IO.Compression.ZstandardEncoder, which is not available in netstandard2.0.
/// It is imported with &lt;UsingTask ... Runtime="NET" TaskFactory="TaskHostFactory" /&gt;.
/// </summary>
public class CompressAssemblies : AndroidTask
{
Expand All @@ -26,7 +30,7 @@ public override bool RunTask ()
var failed_assemblies = new List<ITaskItem> ();

foreach (var assembly in AssembliesToCompress) {
MonoAndroidHelper.LogIfReferenceAssembly (assembly, Log);
ReferenceAssemblyChecker.LogIfReferenceAssembly (assembly, Log);

if (!assembly.TryGetRequiredMetadata ("AssembliesToCompress", "DestinationPath", Log, out var destination_path))
break;
Expand All @@ -38,8 +42,8 @@ public override bool RunTask ()
Log.LogCodedError ("XA5303", Properties.Resources.XA5303, descriptor_index_string, assembly.ItemSpec);
break;
}
if (!AssemblyCompression.TryCompress (Log, assembly.ItemSpec, destination_path, descriptor_index)) {

if (!AssemblyCompressor.TryCompress (Log, assembly.ItemSpec, destination_path, descriptor_index)) {
failed_assemblies.Add (assembly);
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#nullable enable
using System;
using System.Buffers;
using System.IO;
using System.IO.Compression;

using Microsoft.Build.Utilities;

namespace Microsoft.Android.Tasks;

/// <summary>
/// Compresses assemblies with Zstandard before they are placed in the AssemblyStore.
/// The native runtime decompresses them at assembly load time. The 12-byte header
/// (magic / descriptor index / uncompressed length) is read back by the runtime and by
/// the diagnostic tools; the reader-side helpers live in <c>AssemblyCompression</c> in
/// Xamarin.Android.Build.Tasks.
/// </summary>
static class AssemblyCompressor
{
const uint CompressedDataMagic = 0x535A4158; // 'XAZS', little-endian

static readonly ArrayPool<byte> bytePool = ArrayPool<byte>.Shared;

enum CompressionResult
{
Success,
EncodingFailed,
}

public static bool TryCompress (TaskLoggingHelper log, string sourceAssembly, string destinationAssembly, uint descriptorIndex)
{
CompressionResult result = Compress (sourceAssembly, destinationAssembly, descriptorIndex);

if (result != CompressionResult.Success) {
log.LogMessage ($"Failed to compress {sourceAssembly}");
return false;
}

return true;
}

static CompressionResult Compress (string sourcePath, string outputFilePath, uint descriptorIndex)
{
var outputDirectory = Path.GetDirectoryName (outputFilePath);
if (string.IsNullOrEmpty (outputDirectory))
throw new ArgumentException ("must not be null or empty", nameof (outputFilePath));

Directory.CreateDirectory (outputDirectory);

var fi = new FileInfo (sourcePath);
if (!fi.Exists)
throw new InvalidOperationException ($"File '{sourcePath}' does not exist");

int fileSize = checked ((int) fi.Length);

byte[]? sourceBytes = null;
byte[]? destBytes = null;
try {
sourceBytes = bytePool.Rent (fileSize);
int bytesRead = 0;
using (var fs = File.Open (sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
while (bytesRead < fileSize) {
int read = fs.Read (sourceBytes, bytesRead, fileSize - bytesRead);
if (read == 0)
break;

bytesRead += read;
}
}

if (bytesRead != fileSize)
return CompressionResult.EncodingFailed;

long maxOutputSize = ZstandardEncoder.GetMaxCompressedLength (bytesRead);
if (maxOutputSize <= 0 || maxOutputSize > int.MaxValue)
return CompressionResult.EncodingFailed;

destBytes = bytePool.Rent ((int) maxOutputSize);
if (!ZstandardEncoder.TryCompress (sourceBytes.AsSpan (0, bytesRead), destBytes, out int encodedLength))
return CompressionResult.EncodingFailed;

using (var fs = File.Open (outputFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
using (var bw = new BinaryWriter (fs)) {
bw.Write (CompressedDataMagic); // magic
bw.Write (descriptorIndex); // index into runtime array of descriptors
bw.Write (checked ((uint) fi.Length)); // file size before compression
bw.Write (destBytes, 0, encodedLength);
bw.Flush ();
}
} finally {
if (sourceBytes != null)
bytePool.Return (sourceBytes);
if (destBytes != null)
bytePool.Return (destBytes);
}

return CompressionResult.Success;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#nullable enable
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;

using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Properties = Xamarin.Android.Tasks.Properties;

namespace Microsoft.Android.Tasks;

/// <summary>
/// Minimal reference-assembly detection for <see cref="CompressAssemblies"/>. This mirrors
/// <c>MonoAndroidHelper.LogIfReferenceAssembly</c> in Xamarin.Android.Build.Tasks, but is
/// duplicated here to keep this net11.0 assembly self-contained.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Code organizationReferenceAssemblyChecker (mirrors MonoAndroidHelper.IsReferenceAssembly/LogIfReferenceAssembly), TaskItemExtensions.TryGetRequiredMetadata (mirrors ITaskItemExtensions), and the CompressedDataMagic constant are all forked from Xamarin.Android.Build.Tasks. The duplication is honestly documented, but the copies will silently drift if the XA0107/XA4234 logic changes upstream. This csproj already source-links Resources.Designer.cs via <Compile Include="..\Xamarin.Android.Build.Tasks\...\Resources.Designer.cs" Link=... />, so the same <Compile Link> trick could share these small helpers instead of copying them (the magic constant especially is a one-liner worth centralizing). Reasonable to keep as-is given the netstandard2.0 → net11.0 split, but a good follow-up.

{Rule: Centralize duplicate algorithms — duplication is a bug farm (Postmortem #54)}

/// </summary>
static class ReferenceAssemblyChecker
{
public static bool LogIfReferenceAssembly (ITaskItem assembly, TaskLoggingHelper log)
{
if (IsReferenceAssembly (assembly.ItemSpec, log)) {
log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec);
return true;
}

return false;
}

static bool IsReferenceAssembly (string assembly, TaskLoggingHelper log)
{
using var stream = File.OpenRead (assembly);
using var pe = new PEReader (stream);
if (!pe.HasMetadata) {
log.LogDebugMessage ($"Skipping non-.NET assembly: {assembly}");
return false;
}

var reader = pe.GetMetadataReader ();
var assemblyDefinition = reader.GetAssemblyDefinition ();
foreach (var handle in assemblyDefinition.GetCustomAttributes ()) {
var attribute = reader.GetCustomAttribute (handle);
var attributeName = GetCustomAttributeFullName (reader, attribute);
if (attributeName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute")
return true;
}

return false;
}

static string? GetCustomAttributeFullName (MetadataReader reader, CustomAttribute attribute)
{
switch (attribute.Constructor.Kind) {
case HandleKind.MemberReference: {
var ctor = reader.GetMemberReference ((MemberReferenceHandle) attribute.Constructor);
if (ctor.Parent.Kind != HandleKind.TypeReference)
return null;
var type = reader.GetTypeReference ((TypeReferenceHandle) ctor.Parent);
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name);
}
case HandleKind.MethodDefinition: {
var ctor = reader.GetMethodDefinition ((MethodDefinitionHandle) attribute.Constructor);
var type = reader.GetTypeDefinition (ctor.GetDeclaringType ());
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name);
}
default:
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#nullable enable
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Properties = Xamarin.Android.Tasks.Properties;

namespace Microsoft.Android.Tasks;

/// <summary>
/// Minimal <see cref="ITaskItem"/> helpers for <see cref="CompressAssemblies"/>, duplicated
/// here to keep this net11.0 assembly self-contained (the full versions live in
/// <c>ITaskItemExtensions</c> in Xamarin.Android.Build.Tasks).
/// </summary>
static class TaskItemExtensions
{
public static bool TryGetRequiredMetadata (this ITaskItem item, string itemName, string name, TaskLoggingHelper log, out string value)
{
value = item.GetMetadata (name);

if (string.IsNullOrWhiteSpace (value)) {
log.LogCodedError ("XA4234", Properties.Resources.XA4234, itemName, item.ItemSpec, name);
return false;
}

return true;
}
}
Loading
Loading