Skip to content
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

[msbuild/dotnet] Build binding projects on Windows instead of remotely. Fixes #16244. #21873

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c7c04de
[msbuild] Run the generator on Windows instead of remotely.
rolfbjarne Aug 23, 2023
a1e5a5a
[tests] Run the .NET tests involving binding projects on Windows.
rolfbjarne Aug 25, 2023
de34c5d
[tests] Add a few more generated files to package-test-libraries.zip
rolfbjarne Aug 28, 2023
f057124
[tests] Make code a bit more platform agnostic.
rolfbjarne Aug 29, 2023
d1ec867
[tests] Add debug spew to track down failures on Windows.
rolfbjarne Aug 28, 2023
ba232ea
[msbuild] Allow packing on Windows as well.
rolfbjarne Aug 28, 2023
964ba6e
[tools] Add Windows support to PathUtils.IsSymlink.
rolfbjarne Aug 29, 2023
f2583ff
[tools] Add Windows support to FileCopier.TryUpdateDirectory.
rolfbjarne Aug 29, 2023
7dc0187
[tests] Don't use symlinks, it doesn't work on Windows :/
rolfbjarne Aug 30, 2023
64d6c2a
[tools] Improve/fix Windows support in TryUpdateDirectory.
rolfbjarne Aug 30, 2023
2ecc7bc
[msbuild] Create binding resource packages on Windows.
rolfbjarne Aug 30, 2023
44fd754
[msbuild] Do more stuff from Windows.
rolfbjarne Aug 30, 2023
d2d857d
[dotnet] Compute SdkIsSimulator from the RuntimeIdentifier.
rolfbjarne Aug 31, 2023
d816ab5
[tests] Improve logic to find assemblies in binlogs.
rolfbjarne Aug 31, 2023
f97744a
[msbuild] Implement creating binding resource package using built-in …
rolfbjarne Sep 13, 2023
a2f61a2
[msbuild] Make the Zip task work on Windows by using our own compress…
rolfbjarne Sep 14, 2023
5cac984
[tests] Improve debug output.
rolfbjarne Sep 15, 2023
6b7953c
[tools] Fix managed update directory.
rolfbjarne Sep 15, 2023
13623b6
Don't need this.
rolfbjarne Dec 31, 2024
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
6 changes: 6 additions & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.props
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,10 @@
-->
<CustomNativeMain Condition="'$(OutputType)' == 'Exe'">true</CustomNativeMain>
</PropertyGroup>

<!-- Compute _SdkIsSimulator from the RuntimeIdentifier -->
<PropertyGroup>
<_SdkIsSimulator Condition="'$(RuntimeIdentifier)' != '' And '$(_SdkIsSimulator)' == ''">$(RuntimeIdentifier.Contains('simulator'))</_SdkIsSimulator>
<_SdkIsSimulator Condition="'$(RuntimeIdentifiers)' != '' And '$(_SdkIsSimulator)' == ''">$(RuntimeIdentifiers.Contains('simulator'))</_SdkIsSimulator>
</PropertyGroup>
</Project>
4 changes: 4 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1662,4 +1662,8 @@
<data name="E7136" xml:space="preserve">
<value>Unknown resource type: {1}.</value>
</data>

<data name="E7120_TEMP" xml:space="preserve">
<value>Can't process the native reference '{0}' on this platform because it is or contains a symlink.</value>
</data>
</root>
130 changes: 130 additions & 0 deletions msbuild/Xamarin.MacDev.Tasks/Decompress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Threading;

Expand All @@ -12,6 +13,7 @@
using Xamarin.Bundler;
using Xamarin.Localization.MSBuild;
using Xamarin.MacDev.Tasks;
using Xamarin.Utils;

#nullable enable

Expand Down Expand Up @@ -222,6 +224,134 @@ static bool TryDecompressUsingSystemIOCompression (TaskLoggingHelper log, string
return rv;
}

/// <summary>
/// Compresses the specified resources (may be either files or directories) into a zip file.
///
/// Fails if:
/// * The resources is or contains a symlink and we're executing on Windows.
/// * The resources isn't found inside the zip file.
/// </summary>
/// <param name="log"></param>
/// <param name="zip">The zip to create</param>
/// <param name="resources">The files or directories to compress.</param>
/// <returns></returns>
public static bool TryCompress (TaskLoggingHelper log, string zip, IEnumerable<string> resources, bool overwrite, string workingDirectory, bool maxCompression = false)
{
// We use 'zip' to compress on !Windows, and System.IO.Compression to extract on Windows.
// This is because System.IO.Compression doesn't handle symlinks correctly, so we can only use
// it on Windows. It's also possible to set the XAMARIN_USE_SYSTEM_IO_COMPRESSION=1 environment
// variable to force using System.IO.Compression on !Windows, which is particularly useful when
// testing the System.IO.Compression implementation locally (with the caveat that if the resources
// to compress has symlinks, it may not work).

if (overwrite) {
if (File.Exists (zip)) {
log.LogMessage (MessageImportance.Low, "Replacing zip file {0} with {1}", zip, string.Join (", ", resources));
File.Delete (zip);
} else {
log.LogMessage (MessageImportance.Low, "Creating zip file {0} with {1}", zip, string.Join (", ", resources));
}
} else {
if (File.Exists (zip)) {
log.LogMessage (MessageImportance.Low, "Updating zip file {0} with {1}", zip, string.Join (", ", resources));
} else {
log.LogMessage (MessageImportance.Low, "Creating new zip file {0} with {1}", zip, string.Join (", ", resources));
}
}

var zipdir = Path.GetDirectoryName (zip);
if (!string.IsNullOrEmpty (zipdir))
Directory.CreateDirectory (zipdir);

bool rv;
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
rv = TryCompressUsingSystemIOCompression (log, zip, resources, workingDirectory, maxCompression);
} else if (!string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("XAMARIN_USE_SYSTEM_IO_COMPRESSION"))) {
rv = TryCompressUsingSystemIOCompression (log, zip, resources, workingDirectory, maxCompression);
} else {
rv = TryCompressUsingZip (log, zip, resources, workingDirectory, maxCompression);
}

return rv;
}

// Will add to an existing zip file (not replace)
static bool TryCompressUsingZip (TaskLoggingHelper log, string zip, IEnumerable<string> resources, string workingDirectory, bool maxCompression)
{
var zipArguments = new List<string> ();
if (maxCompression)
zipArguments.Add ("-9");
zipArguments.Add ("-r");
zipArguments.Add ("-y");
zipArguments.Add (zip);

foreach (var resource in resources) {
var fullPath = Path.GetFullPath (resource);
var relativePath = PathUtils.AbsoluteToRelative (workingDirectory, fullPath);
zipArguments.Add (relativePath);
}
var rv = XamarinTask.ExecuteAsync (log, "zip", zipArguments, workingDirectory: workingDirectory).Result;
log.LogMessage (MessageImportance.Low, "Updated {0} with {1}: {2}", zip, string.Join (", ", resources), rv.ExitCode == 0);
return rv.ExitCode == 0;
}

#if NET
const CompressionLevel SmallestCompressionLevel = CompressionLevel.SmallestSize;
#else
const CompressionLevel SmallestCompressionLevel = CompressionLevel.Optimal;
#endif

// Will add to an existing zip file (not replace)
static bool TryCompressUsingSystemIOCompression (TaskLoggingHelper log, string zip, IEnumerable<string> resources, string workingDirectory, bool maxCompression)
{
var rv = true;

workingDirectory = Path.GetFullPath (workingDirectory);

var resourcePaths = resources.Select ((v) => Path.Combine (workingDirectory, v)).ToList ();
foreach (var resource in resourcePaths) {
if (!resource.StartsWith (workingDirectory, StringComparison.Ordinal))
throw new InvalidOperationException ($"The resource to compress '{resource}' must be inside the working directory '{workingDirectory}'");
}

using var archive = ZipFile.Open (zip, File.Exists (zip) ? ZipArchiveMode.Update : ZipArchiveMode.Create);

var rootDirLength = workingDirectory.Length;
foreach (var resource in resourcePaths) {
if (Directory.Exists (resource)) {
var entries = Directory.GetFileSystemEntries (resource, "*", SearchOption.AllDirectories);
var entriesWithZipName = entries.Select (v => new { Path = v, ZipName = v.Substring (rootDirLength) });
foreach (var entry in entriesWithZipName) {
if (Directory.Exists (entry.Path)) {
if (entries.Where (v => v.StartsWith (entry.Path, StringComparison.Ordinal)).Count () == 1) {
// this is a directory with no files inside, we need to create an entry with a trailing directory separator.
archive.CreateEntry (entry.ZipName + zipDirectorySeparator);
}
} else {
WriteFileToZip (log, archive, entry.Path, entry.ZipName, maxCompression);
}
}
} else if (File.Exists (resource)) {
var zipName = resource.Substring (rootDirLength);
WriteFileToZip (log, archive, resource, zipName, maxCompression);
} else {
throw new FileNotFoundException (resource);
}
log.LogMessage (MessageImportance.Low, "Updated {0} with {1}", zip, resource);
}

return rv;
}

static void WriteFileToZip (TaskLoggingHelper log, ZipArchive archive, string path, string zipName, bool maxCompression)
{
var zipEntry = archive.CreateEntry (zipName, maxCompression ? SmallestCompressionLevel : CompressionLevel.Optimal);
using var fs = File.OpenRead (path);
using var zipStream = zipEntry.Open ();
fs.CopyTo (zipStream);
log.LogMessage (MessageImportance.Low, $"Compressed {path} into the zip file as {zipName}");
}

static int GetExternalAttributes (ZipArchiveEntry self)
{
// The ZipArchiveEntry.ExternalAttributes property is available in .NET 4.7.2 (which we need to target for builds on Windows) and .NET 5+, but not netstandard2.0 (which is the latest netstandard .NET 4.7.2 supports).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,10 @@ public override bool Execute ()
filesToZip.Add (manifestPath);

foreach (var nativeRef in filesToZip) {
var zipArguments = new List<string> ();
zipArguments.Add ("-9");
zipArguments.Add ("-r");
zipArguments.Add ("-y");
zipArguments.Add (zipFile);

var fullPath = Path.GetFullPath (nativeRef);
var workingDirectory = Path.GetDirectoryName (fullPath);
zipArguments.Add (Path.GetFileName (fullPath));
ExecuteAsync ("zip", zipArguments, workingDirectory: workingDirectory).Wait ();

packagedFiles.Add (zipFile);
var workingDirectory = Path.GetDirectoryName (nativeRef);
CompressionHelper.TryCompress (Log, zipFile, new string [] { nativeRef }, false, workingDirectory, true);
}
packagedFiles.Add (zipFile);
} else {
var bindingResourcePath = BindingResourcePath;
Log.LogMessage (MSBStrings.M0121, bindingResourcePath);
Expand All @@ -127,11 +118,14 @@ public override bool Execute ()
return !Log.HasLoggedErrors;
}

static bool ContainsSymlinks (ITaskItem [] items)
bool ContainsSymlinks (ITaskItem [] items)
{
foreach (var item in items) {
if (PathUtils.IsSymlinkOrContainsSymlinks (item.ItemSpec))
if (PathUtils.IsSymlinkOrContainsSymlinks (item.ItemSpec)) {
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
Log.LogError (MSBStrings.E7120_TEMP /* Can't process the native reference '{0}' on this platform because it is or contains a symlink. */, item?.ItemSpec);
return true;
}
}

return false;
Expand Down
53 changes: 11 additions & 42 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/Zip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,63 +15,27 @@

namespace Xamarin.MacDev.Tasks {
public class Zip : XamarinTask, ICancelableTask {
CancellationTokenSource? cancellationTokenSource;

#region Inputs

[Output]
[Required]
public ITaskItem? OutputFile { get; set; }

public bool Recursive { get; set; }

[Required]
public ITaskItem [] Sources { get; set; } = Array.Empty<ITaskItem> ();

public bool Symlinks { get; set; }

[Required]
public ITaskItem? WorkingDirectory { get; set; }

public string ZipPath { get; set; } = string.Empty;

#endregion

static string GetExecutable (List<string> arguments, string toolName, string toolPathOverride)
{
if (string.IsNullOrEmpty (toolPathOverride)) {
arguments.Insert (0, toolName);
return "xcrun";
}
return toolPathOverride;
}

string GetWorkingDirectory ()
{
return WorkingDirectory!.GetMetadata ("FullPath");
}

List<string> GenerateCommandLineCommands ()
{
var args = new List<string> ();

if (Recursive)
args.Add ("-r");

if (Symlinks)
args.Add ("-y");

args.Add (OutputFile!.GetMetadata ("FullPath"));

var root = GetWorkingDirectory ();
for (int i = 0; i < Sources.Length; i++) {
var relative = PathUtils.AbsoluteToRelative (root, Sources [i].GetMetadata ("FullPath"));
args.Add (relative);
}

return args;
}

public override bool Execute ()
{
if (ShouldExecuteRemotely ()) {
Expand All @@ -85,19 +49,24 @@ public override bool Execute ()
return rv;
}

var args = GenerateCommandLineCommands ();
var executable = GetExecutable (args, "zip", ZipPath);
cancellationTokenSource = new CancellationTokenSource ();
ExecuteAsync (Log, executable, args, workingDirectory: GetWorkingDirectory (), cancellationToken: cancellationTokenSource.Token).Wait ();
var zip = OutputFile!.GetMetadata ("FullPath");
var workingDirectory = GetWorkingDirectory ();
var sources = new List<string> ();
for (int i = 0; i < Sources.Length; i++) {
var relative = PathUtils.AbsoluteToRelative (workingDirectory, Sources [i].GetMetadata ("FullPath"));
sources.Add (relative);
}

if (!CompressionHelper.TryCompress (this.Log, zip, sources, false, workingDirectory, false))
return false;

return !Log.HasLoggedErrors;
}

public void Cancel ()
{
if (ShouldExecuteRemotely ()) {
BuildConnection.CancelAsync (BuildEngine4).Wait ();
} else {
cancellationTokenSource?.Cancel ();
}
}

Expand Down
9 changes: 1 addition & 8 deletions msbuild/Xamarin.Shared/Xamarin.Shared.ObjCBinding.targets
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,9 @@ Copyright (C) 2020 Microsoft. All rights reserved.
</Target>

<Target Name="_CompressNativeFrameworkResources" Inputs="@(_NativeFrameworkResource)" Outputs="%(_NativeFrameworkResource.ZipFile)" DependsOnTargets="_CollectNativeFrameworkResources">
<Delete SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" Files="%(_NativeFrameworkResource.ZipFile)" />
<Delete Files="%(_NativeFrameworkResource.ZipFile)" />

<Zip
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
ZipPath="$(ZipPath)"
Recursive="true"
Symlinks="true"
Sources="%(_NativeFrameworkResource.FrameworkPath)"
OutputFile="%(_NativeFrameworkResource.ZipFile)"
WorkingDirectory="%(_NativeFrameworkResource.FrameworkPath)" >
Expand All @@ -116,8 +111,6 @@ Copyright (C) 2020 Microsoft. All rights reserved.

<Target Name="_PrepareNativeReferences" Condition="'$(DesignTimeBuild)' != 'true'" DependsOnTargets="_SanitizeNativeReferences">
<PrepareNativeReferences
Condition="'$(IsMacEnabled)' == 'true'"
SessionId="$(BuildSessionId)"
IntermediateOutputPath="$(IntermediateOutputPath)"
NativeReferences="@(NativeReference)"
>
Expand Down
Loading
Loading