[net8.0] Merge main into net8.0.

This commit is contained in:
Rolf Bjarne Kvinge 2023-04-10 12:44:17 +02:00
Родитель d4332e9173 8c9ee8289f
Коммит 059aa7ad8f
12 изменённых файлов: 715 добавлений и 318 удалений

Просмотреть файл

@ -641,10 +641,7 @@
<PropertyGroup>
<_ComputeFrameworkFilesToPublishDependsOn>
$(_ComputeFrameworkFilesToPublishDependsOn);
_DecompressAppleFrameworks;
_CollectDecompressedAppleFrameworks;
_DecompressAppleBindingResourcePackages;
_CollectDecompressedAppleBindingResourcePackages;
_ComputePublishLocation;
_ExpandNativeReferences;
_ComputeVariables;
_LoadLinkerOutput;
@ -655,7 +652,7 @@
<Target Name="_ComputeFrameworkFilesToPublish" DependsOnTargets="$(_ComputeFrameworkFilesToPublishDependsOn)">
<ItemGroup>
<!-- Collect the list of frameworks to publish from _FrameworkNativeReference. The ExtractBindingLibrariesStep in the linker and
the _DecompressAppleFrameworks and _DecompressAppleBindingResourcePackages targets might also add frameworks to _FrameworkToPublish -->
the _ComputePublishLocation targets might also add frameworks to _FrameworkToPublish -->
<_FrameworkToPublish Include="@(_FrameworkNativeReference)" Condition="'%(_FrameworkNativeReference.Kind)' == 'Framework' And '%(_FrameworkNativeReference.CopyToAppBundle)' != 'false'" />
<!-- Set TargetDirectory and SourceDirectory for all frameworks we have to publish -->
@ -1601,13 +1598,13 @@
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
Architectures="$(TargetArchitectures)"
BindingResourcePackages="@(_AppleBindingResourcePackage)"
IntermediateOutputPath="$(DeviceSpecificIntermediateOutputPath)"
NativeReferences="@(_UnresolvedXCFrameworks)"
NativeReferences="@(_UnresolvedXCFrameworks);@(_AppleBindingResourcePackage);@(_CompressedAppleFrameworks);@(_CompressedAppleBindingResourcePackage)"
SdkIsSimulator="$(_SdkIsSimulator)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
>
<Output TaskParameter="NativeFrameworks" ItemName="_FrameworkNativeReference" />
<Output TaskParameter="TouchedFiles" ItemName="FileWrites" />
</ResolveNativeReferences>
</Target>
@ -1648,54 +1645,6 @@
</Target>
<Target Name="_DecompressAppleBindingResourcePackages"
Inputs="@(_CompressedAppleBindingResourcePackage)"
DependsOnTargets="_ComputePublishLocation;_ComputeVariables"
Outputs="@(_CompressedAppleBindingResourcePackage -> '$(_IntermediateDecompressionDir)%(Filename)%(Extension).stamp')"
>
<Ditto
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AdditionalArguments="-x -k"
CopyFromWindows="true"
Source="%(_CompressedAppleBindingResourcePackage.Identity)"
Destination="$(_IntermediateDecompressionDir)%(Filename)"
>
</Ditto>
</Target>
<Target Name="_CollectDecompressedAppleBindingResourcePackages"
DependsOnTargets="_DecompressAppleBindingResourcePackages"
>
<ItemGroup>
<_DecompressedAppleBindingResourcePackage Include="@(_CompressedAppleBindingResourcePackage -> '$(_IntermediateDecompressionDir)%(Filename)')" />
</ItemGroup>
<!-- resolve any .xcframeworks and binding resource packages -->
<ResolveNativeReferences
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
Architectures="$(TargetArchitectures)"
BindingResourcePackages="@(_DecompressedAppleBindingResourcePackage)"
IntermediateOutputPath="$(DeviceSpecificIntermediateOutputPath)"
SdkIsSimulator="$(_SdkIsSimulator)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
>
<Output TaskParameter="NativeFrameworks" ItemName="_FrameworkNativeReference" />
</ResolveNativeReferences>
<Touch
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AlwaysCreate="true"
Files="@(_CompressedAppleBindingResourcePackage -> '$(_IntermediateDecompressionDir)%(Filename)%(Extension).stamp')"
>
<Output TaskParameter="TouchedFiles" ItemName="FileWrites" />
</Touch>
</Target>
<Target Name="_DecompressPlugIns"
Inputs="@(_CompressedPlugIns)"
DependsOnTargets="_ComputePublishLocation;_ComputeVariables"
@ -1742,81 +1691,6 @@
</ItemGroup>
</Target>
<Target Name="_DecompressAppleFrameworks"
Inputs="@(_CompressedAppleFrameworks)"
Outputs="@(_CompressedAppleFrameworks -> '$(_IntermediateFrameworksDir)/%(Filename)%(Extension).stamp')"
DependsOnTargets="_ComputeVariables"
BeforeTargets="_ExpandNativeReferences"
>
<Ditto
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AdditionalArguments="-x -k"
CopyFromWindows="true"
Source="%(_CompressedAppleFrameworks.Identity)"
Destination="$(_IntermediateFrameworksDir)/%(Filename)%(Extension)"
>
</Ditto>
<Touch
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AlwaysCreate="true"
Files="@(_CompressedAppleFrameworks -> '$(_IntermediateFrameworksDir)/%(Filename)%(Extension).stamp')"
>
<Output TaskParameter="TouchedFiles" ItemName="FileWrites" />
</Touch>
</Target>
<Target Name="_CollectDecompressedAppleFrameworks"
DependsOnTargets="_DecompressAppleFrameworks"
Condition="@(_CompressedAppleFrameworks->Count()) &gt; 0"
>
<GetFileSystemEntries
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
DirectoryPath="$(_IntermediateFrameworksDir)/%(_CompressedAppleFrameworks.Filename)%(_CompressedAppleFrameworks.Extension)"
Pattern="*.framework"
Recursive="false"
IncludeDirectories="true"
>
<Output TaskParameter="Entries" ItemName="_DecompressedFramework" />
</GetFileSystemEntries>
<GetFileSystemEntries
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
DirectoryPath="$(_IntermediateFrameworksDir)/%(_CompressedAppleFrameworks.Filename)%(_CompressedAppleFrameworks.Extension)"
Pattern="*.xcframework"
Recursive="false"
IncludeDirectories="true"
>
<Output TaskParameter="Entries" ItemName="_DecompressedXCFramework" />
</GetFileSystemEntries>
<ItemGroup>
<!-- include any normal frameworks directly in _FrameworkNativeReference. -->
<!-- .xcframeworks need to be resolved to a platform-specific .framework below -->
<_DecompressedFramework Include="$([System.IO.Directory]::GetDirectories('$(_IntermediateFrameworksDir)/%(_CompressedAppleFrameworks.Filename)%(_CompressedAppleFrameworks.Extension)','*.framework'))" />
<_FrameworkNativeReference Include="@(_DecompressedFramework -> '%(Identity)/%(Filename)')" Kind="Framework" />
</ItemGroup>
<!-- resolve the .xcframeworks -->
<ResolveNativeReferences
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
Architectures="$(TargetArchitectures)"
IntermediateOutputPath="$(DeviceSpecificIntermediateOutputPath)"
NativeReferences="@(_DecompressedXCFramework)"
SdkIsSimulator="$(_SdkIsSimulator)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
>
<Output TaskParameter="NativeFrameworks" ItemName="_FrameworkNativeReference" />
</ResolveNativeReferences>
</Target>
<Target Name="_VerifyValidOutputType">
<Error Text="WinExe is not a valid output type for $(_PlatformName)" Condition="'$(OutputType)' == 'WinExe'"/>
</Target>

Просмотреть файл

@ -1,6 +1,6 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MessagingVersion>1.9.99</MessagingVersion>
<HotRestartVersion>1.0.93</HotRestartVersion>
<MessagingVersion>1.9.102</MessagingVersion>
<HotRestartVersion>1.0.114</HotRestartVersion>
</PropertyGroup>
</Project>

Просмотреть файл

@ -830,13 +830,14 @@
</data>
<data name="E0174" xml:space="preserve">
<value>{0} has an incorrect or unknown format and cannot be processed.
</value>
<value>The xcframework {0} has an incorrect or unknown format and cannot be processed.</value>
</data>
<data name="E0175" xml:space="preserve">
<value>No matching framework found inside '{0}'.
</value>
<value>No matching framework found inside '{0}'. SupportedPlatform: '{0}', SupportedPlatformVariant: '{1}', SupportedArchitectures: '{2}'.</value>
<comment>
Do not translate: 'SupportedPlatform', 'SupportedPlatformVariant', 'SupportedArchitectures' (they're keys inside a file with key/value pairs))
</comment>
</data>
<data name="W0176" xml:space="preserve">
@ -1452,11 +1453,43 @@
</data>
<data name="W7105" xml:space="preserve">
<value>Unexpected extension '{0}' for native reference '{1}' in manifest '{2}'.</value>
<value>Unexpected extension '{0}' for native reference '{1}' in binding resource package '{2}'.</value>
<comment>
{0}: file extension
{1}: path to a file
{2}: path to a file
</comment>
</data>
<data name="W7106" xml:space="preserve">
<value>Expected a file named '{1}' in the zip file {0}.</value>
</data>
<data name="W7107" xml:space="preserve">
<value>The zip file '{0}' does not exist.</value>
</data>
<data name="W7108" xml:space="preserve">
<value>The file '{0}' does not exist.</value>
</data>
<data name="W7109" xml:space="preserve">
<value>Unable to process the item '{0}' as a native reference: unknown type.</value>
</data>
<data name="E7110" xml:space="preserve">
<value>Could not load Info.plist '{0}' from the xcframework '{1}'.</value>
</data>
<data name="W7111" xml:space="preserve">
<value>The directory '{0}' does not exist.</value>
</data>
<data name="E7112" xml:space="preserve">
<value>Could not find the file or directory '{0}' in the zip file '{1}'.</value>
</data>
<data name="E7113" xml:space="preserve">
<value>Can't process the zip file '{0}' on this platform: the file '{1}' is a symlink.</value>
</data>
</root>

Просмотреть файл

@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using Microsoft.Build.Utilities;
using Xamarin.Bundler;
using Xamarin.Localization.MSBuild;
using Xamarin.MacDev.Tasks;
#nullable enable
namespace Xamarin.MacDev {
public static class CompressionHelper {
/// <summary>
/// Is the specified path a compressed file?
/// </summary>
/// <param name="path">The path to check</param>
/// <returns>True if the path represents a compressed file (by checking the extension)</returns>
public static bool IsCompressed (string path)
{
return path.EndsWith (".zip", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Finds a file from either a directory or a zip file.
/// </summary>
/// <param name="log">The log to log any errors and/or warnings.</param>
/// <param name="resources">Path to a directory or a zip archive where to look.</param>
/// <param name="relativeFilePath">The relative path to find, either in a directory or a zip archive.</param>
/// <returns>If successful, a stream to the read the file. Otherwise null.</returns>
public static Stream? TryGetPotentiallyCompressedFile (TaskLoggingHelper log, string resources, string relativeFilePath)
{
// Check if we have a zipped resources, and if so, extract the manifest from the zip file
if (IsCompressed (resources)) {
if (!File.Exists (resources)) {
log.LogWarning (MSBStrings.W7107 /* The zip file '{0}' does not exist */, resources);
return null;
}
using var zip = ZipFile.OpenRead (resources);
var contentEntry = zip.GetEntry (relativeFilePath.Replace ('\\', '/')); // directory separator character is '/' on all platforms in zip files.
if (contentEntry is null) {
log.LogWarning (MSBStrings.W7106 /* Expected a file named '{1}' in the zip file {0}. */, resources, relativeFilePath);
return null;
}
using var contentStream = contentEntry.Open ();
var memoryStream = new MemoryStream ();
contentStream.CopyTo (memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
if (!Directory.Exists (resources)) {
log.LogWarning (MSBStrings.W7111 /* The directory '{0}' does not exist. */, resources);
return null;
}
var contentPath = Path.Combine (resources, relativeFilePath);
if (!File.Exists (contentPath)) {
log.LogWarning (MSBStrings.W7108 /* The file '{0}' does not exist. */, contentPath);
return null;
}
return File.OpenRead (contentPath);
}
/// <summary>
/// Extracts the specified resource (may be either a file or a directory) from the given zip file.
/// A stamp file will be created to avoid re-extracting unnecessarily.
///
/// Fails if:
/// * The resource is or contains a symlink and we're executing on Windows.
/// * The resource isn't found inside the zip file.
/// </summary>
/// <param name="log"></param>
/// <param name="zip">The zip to search in</param>
/// <param name="resource">The relative path inside the zip to extract (may be a file or a directory).</param>
/// <param name="decompressionDir">The location on disk to store the extracted results</param>
/// <param name="decompressedResource">The location on disk to the extracted resource</param>
/// <returns></returns>
public static bool TryDecompress (TaskLoggingHelper log, string zip, string resource, string decompressionDir, List<string> createdFiles, [NotNullWhen (true)] out string? decompressedResource)
{
decompressedResource = Path.Combine (decompressionDir, resource);
var stampFile = decompressedResource + ".stamp";
if (FileCopier.IsUptodate (zip, stampFile, XamarinTask.GetFileCopierReportErrorCallback (log), XamarinTask.GetFileCopierLogCallback (log), check_stamp: false))
return true;
// We use 'unzip' to extract 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 symlinks won't
// be extracted).
bool rv;
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
rv = TryDecompressUsingSystemIOCompression (log, zip, resource, decompressionDir);
} else if (!string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("XAMARIN_USE_SYSTEM_IO_COMPRESSION"))) {
rv = TryDecompressUsingSystemIOCompression (log, zip, resource, decompressionDir);
} else {
rv = TryDecompressUsingUnzip (log, zip, resource, decompressionDir);
}
if (rv) {
Directory.CreateDirectory (Path.GetDirectoryName (stampFile));
using var touched = new FileStream (stampFile, FileMode.Create, FileAccess.Write);
createdFiles.Add (stampFile);
}
if (File.Exists (decompressedResource)) {
createdFiles.Add (decompressedResource);
} else if (Directory.Exists (decompressedResource)) {
createdFiles.AddRange (Directory.GetFiles (decompressedResource, "*", SearchOption.AllDirectories));
}
return rv;
}
// The dir separator character in zip files is always "/", even on Windows
const char zipDirectorySeparator = '/';
static bool TryDecompressUsingUnzip (TaskLoggingHelper log, string zip, string resource, string decompressionDir)
{
var archive = ZipFile.OpenRead (zip);
resource = resource.Replace ('\\', zipDirectorySeparator);
var entry = archive.GetEntry (resource);
if (entry is null) {
entry = archive.GetEntry (resource + zipDirectorySeparator);
if (entry is null) {
log.LogError (MSBStrings.E7112 /* Could not find the file or directory '{0}' in the zip file '{1}'. */, resource, zip);
return false;
}
}
var zipPattern = entry.FullName;
if (zipPattern.Length > 0 && zipPattern [zipPattern.Length - 1] == zipDirectorySeparator) {
zipPattern += "*";
}
var args = new string [] {
"-u", "-o",
"-d", decompressionDir,
zip,
zipPattern,
};
var rv = XamarinTask.ExecuteAsync (log, "unzip", args).Result;
return rv.ExitCode == 0;
}
static bool TryDecompressUsingSystemIOCompression (TaskLoggingHelper log, string zip, string resource, string decompressionDir)
{
var rv = true;
// canonicalize input
resource = resource.TrimEnd ('/', '\\');
resource = resource.Replace ('\\', zipDirectorySeparator);
var resourceAsDir = resource + zipDirectorySeparator;
var archive = ZipFile.OpenRead (zip);
foreach (var entry in archive.Entries) {
var entryPath = entry.FullName;
if (entryPath.Length == 0)
continue;
if (entryPath.StartsWith (resourceAsDir, StringComparison.Ordinal)) {
// yep, we want this entry
} else if (entryPath == resource) {
// we want this one too
} else {
// but otherwise nope
continue;
}
// Check if the file or directory is a symlink, and show an error if so. Symlinks are only supported
// on non-Windows platforms.
var entryAttributes = ((uint) GetExternalAttributes (entry)) >> 16;
const uint S_IFLNK = 0xa000; // #define S_IFLNK 0120000 /* symbolic link */
var isSymlink = (entryAttributes & S_IFLNK) == S_IFLNK;
if (isSymlink) {
log.LogError (MSBStrings.E7113 /* Can't process the zip file '{0}' on this platform: the file '{1}' is a symlink. */, zip, entryPath);
rv = false;
continue;
}
var isDir = entryPath [entryPath.Length - 1] == zipDirectorySeparator;
var targetPath = Path.Combine (decompressionDir, entryPath);
if (isDir) {
Directory.CreateDirectory (targetPath);
} else {
Directory.CreateDirectory (Path.GetDirectoryName (targetPath));
using var streamWrite = File.OpenWrite (targetPath);
using var streamRead = entry.Open ();
streamRead.CopyTo (streamWrite);
}
}
return rv;
}
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).
// Since the property will always be available at runtime, just call it using reflection.
#if NET
return self.ExternalAttributes;
#else
var property = typeof (ZipArchiveEntry).GetProperty ("ExternalAttributes", BindingFlags.Instance | BindingFlags.Public);
return (int) property.GetValue (self);
#endif
}
}
}

Просмотреть файл

@ -1,20 +1,43 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Xamarin;
using Xamarin.MacDev;
using Xamarin.Bundler;
using Xamarin.MacDev.Tasks;
using Xamarin.Localization.MSBuild;
using Xamarin.Utils;
#nullable enable
namespace Xamarin.MacDev.Tasks {
// We can get numerous types of native references:
//
// *.dylib
// *.a
// *.framework
// *.xcframework
//
// They can come from:
//
// - A NativeReference to the file/directory on disk (or even a file inside the directory).
// - A NativeReference to a zip of the above
// - A binding resource package next to an assembly
// - A zipped binding resource package
//
// Special considerations:
// - We can only extract the files we need from any zipped reference, because this task must work on Windows (without a connection to a Mac),
// and a zip may contain symlinks for a different platform (and thus won't be needed). Example: an xcframework
// with a framework for macOS will likely have symlinks, but that shouldn't prevent the xcframework from being
// consumed in a build for iOS.
public abstract class ResolveNativeReferencesBase : XamarinTask {
#region Inputs
@ -22,13 +45,11 @@ namespace Xamarin.MacDev.Tasks {
public string? Architectures { get; set; }
[Required]
public string? IntermediateOutputPath { get; set; }
public string IntermediateOutputPath { get; set; } = string.Empty;
public ITaskItem []? NativeReferences { get; set; }
public ITaskItem [] NativeReferences { get; set; } = Array.Empty<ITaskItem> ();
public ITaskItem []? References { get; set; }
public ITaskItem []? BindingResourcePackages { get; set; }
public ITaskItem [] References { get; set; } = Array.Empty<ITaskItem> ();
[Required]
public bool SdkIsSimulator { get; set; }
@ -40,11 +61,29 @@ namespace Xamarin.MacDev.Tasks {
[Output]
public ITaskItem []? NativeFrameworks { get; set; }
[Output]
public ITaskItem [] TouchedFiles { get; set; } = Array.Empty<ITaskItem> ();
#endregion
string GetIntermediateDecompressionDir (ITaskItem item)
{
return GetIntermediateDecompressionDir (item.ItemSpec);
}
string GetIntermediateDecompressionDir (string item)
{
return Path.Combine (IntermediateOutputPath, Path.GetFileName (item));
}
// returns the Mach-O file for the given path:
// * for frameworks, returns foo.framework/foo
// * for anything else, returns the input path
#if NET
[return: NotNullIfNotNull (nameof (path))]
#else
[return: NotNullIfNotNull ("path")]
#endif
static string? GetActualLibrary (string? path)
{
if (path is null)
@ -59,120 +98,215 @@ namespace Xamarin.MacDev.Tasks {
public override bool Execute ()
{
var native_frameworks = new List<ITaskItem> ();
var createdFiles = new List<string> ();
// there can be direct native references inside a project
if (NativeReferences != null) {
foreach (var nr in NativeReferences) {
var name = nr.ItemSpec;
switch (Path.GetExtension (name)) {
// '.' can be used to represent a file (instead of the directory)
case "":
name = Path.GetDirectoryName (name);
if (Path.GetExtension (name) == ".xcframework")
goto case ".xcframework";
break;
case ".xcframework":
var resolved = ResolveXCFramework (name);
if (resolved == null)
return false;
var t = new TaskItem (resolved);
// add metadata from the original item
nr.CopyMetadataTo (t);
t.SetMetadata ("Kind", "Framework");
t.SetMetadata ("Name", resolved);
native_frameworks.Add (t);
break;
case ".framework":
native_frameworks.Add (nr);
break;
}
}
foreach (var nr in NativeReferences) {
ProcessNativeReference (nr, native_frameworks, createdFiles);
}
// or (managed) reference to an assembly that bind a framework
if (References != null) {
foreach (var r in References) {
// look for sidecar's manifest
var resources = Path.ChangeExtension (r.ItemSpec, ".resources");
if (Directory.Exists (resources)) {
ProcessSidecar (r, resources, native_frameworks);
} else {
resources = resources + ".zip";
if (File.Exists (resources))
ProcessSidecar (r, resources, native_frameworks);
}
}
}
// or even just plain binding packages
if (BindingResourcePackages is not null) {
foreach (var bp in BindingResourcePackages) {
ProcessSidecar (bp, bp.ItemSpec, native_frameworks);
foreach (var r in References) {
// look for sidecar's manifest
var resources = Path.ChangeExtension (r.ItemSpec, ".resources");
if (Directory.Exists (resources)) {
ProcessNativeReference (r, resources, native_frameworks, createdFiles);
} else {
resources = resources + ".zip";
if (File.Exists (resources))
ProcessNativeReference (r, resources, native_frameworks, createdFiles);
}
}
NativeFrameworks = native_frameworks.ToArray ();
TouchedFiles = createdFiles.Select (v => new TaskItem (v)).ToArray ();
return !Log.HasLoggedErrors;
}
void ProcessSidecar (ITaskItem r, string resources, List<ITaskItem> native_frameworks)
void ProcessNativeReference (ITaskItem item, List<ITaskItem> native_frameworks, List<string> createdFiles)
{
// Check if we have a zipped sidecar, and if so, extract it before we keep processing
if (resources.EndsWith (".zip", StringComparison.OrdinalIgnoreCase)) {
var path = Path.Combine (IntermediateOutputPath, Path.GetFileName (resources));
var arguments = new [] {
"-u",
"-o",
"-d",
path,
resources,
};
ExecuteAsync ("/usr/bin/unzip", arguments).Wait ();
resources = path;
}
ProcessNativeReference (item, item.ItemSpec, native_frameworks, createdFiles);
}
if (!Directory.Exists (resources)) {
Log.LogWarning (MSBStrings.W7093 /* The binding resource package {0} does not exist. */, resources);
void ProcessNativeReference (ITaskItem item, string name, List<ITaskItem> native_frameworks, List<string> createdFiles)
{
// '.' can be used to represent a file (instead of the directory)
if (Path.GetFileName (name) == ".")
name = Path.GetDirectoryName (name);
var parentDirectory = Path.GetDirectoryName (name);
// framework
if (name.EndsWith (".framework", StringComparison.OrdinalIgnoreCase)) {
var nr = new TaskItem (item);
nr.ItemSpec = GetActualLibrary (name);
nr.SetMetadata ("Kind", "Framework");
native_frameworks.Add (nr);
return;
} else if (parentDirectory.EndsWith (".framework", StringComparison.OrdinalIgnoreCase) && Path.GetFileName (name) == Path.GetFileNameWithoutExtension (parentDirectory)) {
var nr = new TaskItem (item);
nr.ItemSpec = GetActualLibrary (name);
nr.SetMetadata ("Kind", "Framework");
native_frameworks.Add (nr);
return;
}
var manifest = Path.Combine (resources, "manifest");
if (!File.Exists (manifest)) {
Log.LogWarning (MSBStrings.W7087 /* Expected a 'manifest' file in the directory {0} */, resources);
// dynamic library
if (name.EndsWith (".dylib", StringComparison.OrdinalIgnoreCase)) {
var nr = new TaskItem (item);
nr.ItemSpec = name;
nr.SetMetadata ("Kind", "Dynamic");
native_frameworks.Add (nr);
return;
}
// static library
if (name.EndsWith (".a", StringComparison.OrdinalIgnoreCase)) {
var nr = new TaskItem (item);
nr.ItemSpec = name;
nr.SetMetadata ("Kind", "Static");
native_frameworks.Add (nr);
return;
}
// (compressed) xcframework
if (name.EndsWith (".xcframework", StringComparison.OrdinalIgnoreCase) || name.EndsWith (".xcframework.zip", StringComparison.OrdinalIgnoreCase)) {
if (!TryResolveXCFramework (Log, TargetFrameworkMoniker, SdkIsSimulator, Architectures, name, GetIntermediateDecompressionDir (item), createdFiles, out var frameworkPath))
return;
var nr = new TaskItem (item);
nr.ItemSpec = GetActualLibrary (frameworkPath);
nr.SetMetadata ("Kind", "Framework");
native_frameworks.Add (nr);
return;
}
// compressed framework
if (name.EndsWith (".framework.zip", StringComparison.OrdinalIgnoreCase)) {
if (!CompressionHelper.TryDecompress (Log, name, Path.GetFileNameWithoutExtension (name), GetIntermediateDecompressionDir (item), createdFiles, out var frameworkPath))
return;
var nr = new TaskItem (item);
nr.ItemSpec = GetActualLibrary (frameworkPath);
nr.SetMetadata ("Kind", "Framework");
native_frameworks.Add (nr);
return;
}
// sidecar / binding resource package
if (name.EndsWith (".resources", StringComparison.OrdinalIgnoreCase)) {
ProcessSidecar (item, name, native_frameworks, createdFiles);
return;
}
// compressed sidecar / binding resource package
if (name.EndsWith (".resources.zip", StringComparison.OrdinalIgnoreCase)) {
ProcessSidecar (item, name, native_frameworks, createdFiles);
return;
}
Log.LogWarning (MSBStrings.W7109 /* Unable to process the item '{0}' as a native reference: unknown type.* */, item.ItemSpec);
}
/// <summary>
/// Finds the 'manifest' file inside a (potentially compressed) binding resource package.
/// </summary>
/// <param name="log">The log to log any errors and/or warnings.</param>
/// <param name="resources">Path to the binding resource package (as a zip file or a folder).</param>
/// <param name="manifestContents">The contents of the 'manifest' file inside the binding resource package</param>
/// <returns>True if the manifest was found.</returns>
static bool TryGetSidecarManifest (TaskLoggingHelper log, string resources, [NotNullWhen (true)] out string? manifestContents)
{
using var stream = CompressionHelper.TryGetPotentiallyCompressedFile (log, resources, "manifest");
if (stream is null) {
manifestContents = null;
return false;
}
using var streamReader = new StreamReader (stream);
manifestContents = streamReader.ReadToEnd ();
return true;
}
/// <summary>
/// Finds the 'Info.plist' file inside a (potentially compressed) xcframework.
/// </summary>
/// <param name="log">The log to log any errors and/or warnings.</param>
/// <param name="resourcePath">Path to the location of the xcframework (as a zip file with an xcframework inside, or the container folder of an xcframework directory).</param>
/// <param name="xcframework">The name of the xcframework to look for.</param>
/// <param name="plist">The parsed Info.plist</param>
/// <returns>True if the Info.plist was found and successfully parsed.</returns>
static bool TryGetInfoPlist (TaskLoggingHelper log, string resourcePath, string xcframework, [NotNullWhen (true)] out PDictionary? plist)
{
var manifestPath = Path.Combine (xcframework, "Info.plist");
var stream = CompressionHelper.TryGetPotentiallyCompressedFile (log, resourcePath, manifestPath);
if (stream is null) {
plist = null;
return false;
}
plist = (PDictionary?) PDictionary.FromStream (stream);
if (plist is null) {
log.LogError (MSBStrings.E7110 /* Could not load Info.plist '{0}' from the xcframework '{1}'.. */, manifestPath, resourcePath);
return false;
}
return true;
}
void ProcessSidecar (ITaskItem r, string resources, List<ITaskItem> native_frameworks, List<string> createdFiles)
{
if (!TryGetSidecarManifest (Log, resources, out var manifestContents))
return;
var isCompressed = CompressionHelper.IsCompressed (resources);
XmlDocument document = new XmlDocument ();
document.LoadWithoutNetworkAccess (manifest);
document.LoadXmlWithoutNetworkAccess (manifestContents);
foreach (XmlNode referenceNode in document.GetElementsByTagName ("NativeReference")) {
ITaskItem t;
ITaskItem t = new TaskItem (r);
var name = referenceNode.Attributes ["Name"].Value;
switch (Path.GetExtension (name)) {
case ".xcframework":
var resolved = ResolveXCFramework (Path.Combine (resources, name));
if (resolved == null)
return;
t = new TaskItem (resolved);
t.SetMetadata ("Kind", "Framework");
t.SetMetadata ("Name", resolved);
break;
case ".framework":
t = new TaskItem (Path.Combine (resources, name, Path.GetFileNameWithoutExtension (name)));
case ".xcframework": {
if (!TryResolveXCFramework (Log, TargetFrameworkMoniker, SdkIsSimulator, Architectures, resources, name, GetIntermediateDecompressionDir (resources), createdFiles, out var frameworkPath))
continue;
t.ItemSpec = GetActualLibrary (frameworkPath);
t.SetMetadata ("Kind", "Framework");
break;
}
case ".framework": {
string? frameworkPath;
if (!isCompressed) {
frameworkPath = Path.Combine (resources, name);
} else if (!CompressionHelper.TryDecompress (Log, resources, name, GetIntermediateDecompressionDir (resources), createdFiles, out frameworkPath)) {
continue;
}
t.ItemSpec = GetActualLibrary (frameworkPath);
t.SetMetadata ("Kind", "Framework");
break;
}
case ".dylib": // macOS
t = new TaskItem (Path.Combine (resources, name));
string? dylibPath;
if (!isCompressed) {
dylibPath = Path.Combine (resources, name);
} else if (!CompressionHelper.TryDecompress (Log, resources, name, GetIntermediateDecompressionDir (resources), createdFiles, out dylibPath)) {
continue;
}
t.ItemSpec = dylibPath;
t.SetMetadata ("Kind", "Dynamic");
break;
case ".a": // static library
t = new TaskItem (Path.Combine (resources, name));
string? aPath;
if (!isCompressed) {
aPath = Path.Combine (resources, name);
} else if (!CompressionHelper.TryDecompress (Log, resources, name, GetIntermediateDecompressionDir (resources), createdFiles, out aPath)) {
continue;
}
t.ItemSpec = aPath;
t.SetMetadata ("Kind", "Static");
break;
default:
Log.LogWarning (MSBStrings.W7105 /* Unexpected extension '{0}' for native reference '{1}' in manifest '{2}'. */, Path.GetExtension (name), name, manifest);
Log.LogWarning (MSBStrings.W7105 /* Unexpected extension '{0}' for native reference '{1}' in binding resource package '{2}'. */, Path.GetExtension (name), name, r.ItemSpec);
t = r;
break;
}
@ -191,61 +325,130 @@ namespace Xamarin.MacDev.Tasks {
}
}
protected string? ResolveXCFramework (string xcframework)
/// <summary>
/// Resolve an xcframework into a framework for a given platform.
/// </summary>
/// <param name="log">The log to log any errors and/or warnings.</param>
/// <param name="isSimulator">If we're targeting the simulator</param>
/// <param name="targetFrameworkMoniker">The target framework moniker.</param>
/// <param name="architectures">The target architectures</param>
/// <param name="path">Either the path to a compressed xcframework (*.xcframework.zip), or an xcframework (*.xcframework).</param>
/// <param name="frameworkPath">A full path to the resolved framework within the xcframework. If 'resourcePath' is compressed, this will point to where the framework is decompressed on disk.</param>
/// <param name="intermediateDecompressionDir"></param>
/// <returns>True if a framework was succsesfully found. Otherwise false, and an error will have been printed to the log.</returns>
public static bool TryResolveXCFramework (TaskLoggingHelper log, string targetFrameworkMoniker, bool isSimulator, string? architectures, string path, string intermediateDecompressionDir, List<string> createdFiles, [NotNullWhen (true)] out string? frameworkPath)
{
string platformName;
string resourcePath;
string xcframework;
switch (Platform) {
case Utils.ApplePlatform.MacCatalyst:
if (path.EndsWith (".zip", StringComparison.OrdinalIgnoreCase)) {
resourcePath = path;
xcframework = Path.GetFileNameWithoutExtension (path); // Remove the .zip extension
} else {
resourcePath = Path.GetDirectoryName (path);
xcframework = Path.GetFileName (path);
}
return TryResolveXCFramework (log, targetFrameworkMoniker, isSimulator, architectures, resourcePath, xcframework, intermediateDecompressionDir, createdFiles, out frameworkPath);
}
/// <summary>
/// Resolve an xcframework into a framework for a given platform.
/// </summary>
/// <param name="log">The log to log any errors and/or warnings.</param>
/// <param name="isSimulator">If we're targeting the simulator</param>
/// <param name="targetFrameworkMoniker">The target framework moniker.</param>
/// <param name="architectures">The target architectures</param>
/// <param name="resourcePath">Either the path to a compressed xcframework, or the containing directory of an xcframework.</param>
/// <param name="xcframework">The name of the xcframework.</param>
/// <param name="frameworkPath">A full path to the resolved .framework within the xcframework. If 'resourcePath' is compressed, this will point to where the framework is decompressed on disk.</param>
/// <param name="intermediateDecompressionDir"></param>
/// <returns>True if a framework was succsesfully found. Otherwise false, and an error will have been printed to the log.</returns>
public static bool TryResolveXCFramework (TaskLoggingHelper log, string targetFrameworkMoniker, bool isSimulator, string? architectures, string resourcePath, string xcframework, string intermediateDecompressionDir, List<string> createdFiles, [NotNullWhen (true)] out string? frameworkPath)
{
frameworkPath = null;
try {
if (!TryGetInfoPlist (log, resourcePath, xcframework, out var plist))
return false;
var isCompressed = CompressionHelper.IsCompressed (resourcePath);
var xcframeworkPath = isCompressed ? resourcePath : Path.Combine (resourcePath, xcframework);
if (!TryResolveXCFramework (log, plist, xcframeworkPath, targetFrameworkMoniker, isSimulator, architectures!, out var frameworkRelativePath))
return false;
if (!isCompressed) {
frameworkPath = Path.Combine (resourcePath, xcframework, frameworkRelativePath);
return true;
}
var zipResource = Path.Combine (xcframework, Path.GetDirectoryName (frameworkRelativePath));
if (!CompressionHelper.TryDecompress (log, resourcePath, zipResource, intermediateDecompressionDir, createdFiles, out var decompressedPath))
return false;
frameworkPath = Path.Combine (intermediateDecompressionDir, zipResource);
return true;
} catch (Exception) {
log.LogError (MSBStrings.E0174, resourcePath);
}
return false;
}
/// <summary>
/// Resolve an xcframework into a framework for a given platform.
/// </summary>
/// <param name="log">The log to log any errors and/or warnings.</param>
/// <param name="plist">The plist inside the xcframework.</param>
/// <param name="xcframeworkPath">The path to the xcframework. This is only used for error messages, so it can also point to a compressed xcframework.</param>
/// <param name="isSimulator">If we're targeting the simulator</param>
/// <param name="targetFrameworkMoniker">The target framework moniker.</param>
/// <param name="architectures">The target architectures</param>
/// <param name="frameworkPath">A relative path to the resolved framework within the xcframework.</param>
/// <returns>True if a framework was succsesfully found. Otherwise false, and an error will have been printed to the log.</returns>
internal static bool TryResolveXCFramework (TaskLoggingHelper log, PDictionary plist, string xcframeworkPath, string targetFrameworkMoniker, bool isSimulator, string architectures, [NotNullWhen (true)] out string? frameworkPath)
{
frameworkPath = null;
var platform = PlatformFrameworkHelper.GetFramework (targetFrameworkMoniker);
string platformName;
switch (platform) {
case ApplePlatform.MacCatalyst:
platformName = "ios";
break;
case Utils.ApplePlatform.MacOSX:
case ApplePlatform.MacOSX:
// PlatformFrameworkHelper.GetOperatingSystem returns "osx" which does not work for xcframework
platformName = "macos";
break;
default:
platformName = PlatformFrameworkHelper.GetOperatingSystem (TargetFrameworkMoniker);
platformName = PlatformFrameworkHelper.GetOperatingSystem (targetFrameworkMoniker);
break;
}
string? variant = null;
if (Platform == Utils.ApplePlatform.MacCatalyst) {
string? variant;
if (platform == ApplePlatform.MacCatalyst) {
variant = "maccatalyst";
} else if (SdkIsSimulator) {
} else if (isSimulator) {
variant = "simulator";
} else {
variant = null;
}
try {
var plist = PDictionary.FromFile (Path.Combine (xcframework, "Info.plist"))!;
var path = ResolveXCFramework (plist, platformName, variant, Architectures!);
if (!String.IsNullOrEmpty (path))
return Path.Combine (xcframework, path);
// either the format was incorrect or we could not find a matching framework
// note: last part is not translated since it match the (non-translated) keys inside the `Info.plist`
var msg = (path == null) ? MSBStrings.E0174 : MSBStrings.E0175 + $" SupportedPlatform: '{platformName}', SupportedPlatformVariant: '{variant}', SupportedArchitectures: '{Architectures}'.";
Log.LogError (msg, xcframework);
} catch (Exception) {
Log.LogError (MSBStrings.E0174, xcframework);
}
return null;
}
internal static string? ResolveXCFramework (PDictionary plist, string platformName, string? variant, string architectures)
{
// plist structure https://github.com/spouliot/xcframework#infoplist
var bundle_package_type = (PString?) plist ["CFBundlePackageType"];
if (bundle_package_type?.Value != "XFWK")
return null;
if (bundle_package_type?.Value != "XFWK") {
log.LogError (MSBStrings.E0174 /* The xcframework {0} has an incorrect or unknown format and cannot be processed. */, xcframeworkPath);
return false;
}
var available_libraries = plist.GetArray ("AvailableLibraries");
if ((available_libraries == null) || (available_libraries.Count == 0))
return null;
if ((available_libraries == null) || (available_libraries.Count == 0)) {
log.LogError (MSBStrings.E0174 /* The xcframework {0} has an incorrect or unknown format and cannot be processed. */, xcframeworkPath);
return false;
}
var platform = platformName.ToLowerInvariant ();
var archs = architectures.Split (new char [] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (PDictionary item in available_libraries) {
var supported_platform = (PString?) item ["SupportedPlatform"];
if (supported_platform?.Value != platform)
if (!string.Equals (supported_platform?.Value, platformName, StringComparison.OrdinalIgnoreCase))
continue;
// optional key
var supported_platform_variant = (PString?) item ["SupportedPlatformVariant"];
@ -261,14 +464,19 @@ namespace Xamarin.MacDev.Tasks {
if (found)
break;
}
if (!found)
return String.Empty;
if (!found) {
log.LogError (MSBStrings.E0175 /* No matching framework found inside '{0}'. SupportedPlatform: '{0}', SupportedPlatformVariant: '{1}', SupportedArchitectures: '{2}'. */, xcframeworkPath, platformName, variant, architectures);
return false;
}
}
var library_path = (PString?) item ["LibraryPath"];
var library_identifier = (PString?) item ["LibraryIdentifier"];
return GetActualLibrary (Path.Combine (library_identifier!, library_path!));
frameworkPath = GetActualLibrary (Path.Combine (library_identifier!, library_path!));
return true;
}
return String.Empty;
log.LogError (MSBStrings.E0175 /* No matching framework found inside '{0}'. SupportedPlatform: '{0}', SupportedPlatformVariant: '{1}', SupportedArchitectures: '{2}'. */, xcframeworkPath, platformName, variant, architectures);
return false;
}
}
}

Просмотреть файл

@ -7,6 +7,7 @@ using Microsoft.Build.Utilities;
using Xamarin.Localization.MSBuild;
using Xamarin.Utils;
using static Xamarin.Bundler.FileCopier;
namespace Xamarin.MacDev.Tasks {
// This is the same as XamarinToolTask, except that it subclasses Task instead.
@ -109,16 +110,21 @@ namespace Xamarin.MacDev.Tasks {
return PlatformFrameworkHelper.GetSdkPlatform (Platform, isSimulator);
}
protected async System.Threading.Tasks.Task<Execution> ExecuteAsync (string fileName, IList<string> arguments, string sdkDevPath = null, Dictionary<string, string> environment = null, bool mergeOutput = true, bool showErrorIfFailure = true, string workingDirectory = null)
protected System.Threading.Tasks.Task<Execution> ExecuteAsync (string fileName, IList<string> arguments, string sdkDevPath = null, Dictionary<string, string> environment = null, bool mergeOutput = true, bool showErrorIfFailure = true, string workingDirectory = null)
{
return ExecuteAsync (Log, fileName, arguments, sdkDevPath, environment, mergeOutput, showErrorIfFailure, workingDirectory);
}
internal protected static async System.Threading.Tasks.Task<Execution> ExecuteAsync (TaskLoggingHelper log, string fileName, IList<string> arguments, string sdkDevPath = null, Dictionary<string, string> environment = null, bool mergeOutput = true, bool showErrorIfFailure = true, string workingDirectory = null)
{
// Create a new dictionary if we're given one, to make sure we don't change the caller's dictionary.
var launchEnvironment = environment == null ? new Dictionary<string, string> () : new Dictionary<string, string> (environment);
if (!string.IsNullOrEmpty (sdkDevPath))
launchEnvironment ["DEVELOPER_DIR"] = sdkDevPath;
Log.LogMessage (MessageImportance.Normal, MSBStrings.M0001, fileName, StringUtils.FormatArguments (arguments));
log.LogMessage (MessageImportance.Normal, MSBStrings.M0001, fileName, StringUtils.FormatArguments (arguments));
var rv = await Execution.RunAsync (fileName, arguments, environment: launchEnvironment, mergeOutput: mergeOutput, workingDirectory: workingDirectory);
Log.LogMessage (rv.ExitCode == 0 ? MessageImportance.Low : MessageImportance.High, MSBStrings.M0002, fileName, rv.ExitCode);
log.LogMessage (rv.ExitCode == 0 ? MessageImportance.Low : MessageImportance.High, MSBStrings.M0002, fileName, rv.ExitCode);
// Show the output
var output = rv.StandardOutput.ToString ();
@ -131,7 +137,7 @@ namespace Xamarin.MacDev.Tasks {
var importance = MessageImportance.Low;
if (rv.ExitCode != 0)
importance = showErrorIfFailure ? MessageImportance.High : MessageImportance.Normal;
Log.LogMessage (importance, output);
log.LogMessage (importance, output);
}
if (showErrorIfFailure && rv.ExitCode != 0) {
@ -139,9 +145,9 @@ namespace Xamarin.MacDev.Tasks {
if (stderr.Length > 1024)
stderr = stderr.Substring (0, 1024);
if (string.IsNullOrEmpty (stderr)) {
Log.LogError (MSBStrings.E0117, /* {0} exited with code {1} */ fileName == "xcrun" ? arguments [0] : fileName, rv.ExitCode);
log.LogError (MSBStrings.E0117, /* {0} exited with code {1} */ fileName == "xcrun" ? arguments [0] : fileName, rv.ExitCode);
} else {
Log.LogError (MSBStrings.E0118, /* {0} exited with code {1}:\n{2} */ fileName == "xcrun" ? arguments [0] : fileName, rv.ExitCode, stderr);
log.LogError (MSBStrings.E0118, /* {0} exited with code {1}:\n{2} */ fileName == "xcrun" ? arguments [0] : fileName, rv.ExitCode, stderr);
}
}
@ -150,12 +156,31 @@ namespace Xamarin.MacDev.Tasks {
public bool ShouldExecuteRemotely () => this.ShouldExecuteRemotely (SessionId);
protected void FileCopierReportErrorCallback (int code, string format, params object [] arguments)
internal protected static ReportErrorCallback GetFileCopierReportErrorCallback (TaskLoggingHelper log)
{
Log.LogError (format, arguments);
return new ReportErrorCallback ((int code, string format, object [] arguments) => {
FileCopierReportErrorCallback (log, code, format, arguments);
});
}
protected void FileCopierLogCallback (int min_verbosity, string format, params object [] arguments)
internal protected static void FileCopierReportErrorCallback (TaskLoggingHelper log, int code, string format, params object [] arguments)
{
log.LogError (format, arguments);
}
protected void FileCopierReportErrorCallback (int code, string format, params object [] arguments)
{
FileCopierReportErrorCallback (Log, code, format, arguments);
}
internal protected static LogCallback GetFileCopierLogCallback (TaskLoggingHelper log)
{
return new LogCallback ((int min_verbosity, string format, object [] arguments) => {
FileCopierLogCallback (log, min_verbosity, format, arguments);
});
}
protected static void FileCopierLogCallback (TaskLoggingHelper log, int min_verbosity, string format, params object [] arguments)
{
MessageImportance importance;
if (min_verbosity <= 0) {
@ -165,7 +190,12 @@ namespace Xamarin.MacDev.Tasks {
} else {
importance = MessageImportance.Low;
}
Log.LogMessage (importance, format, arguments);
log.LogMessage (importance, format, arguments);
}
protected void FileCopierLogCallback (int min_verbosity, string format, params object [] arguments)
{
FileCopierLogCallback (Log, min_verbosity, format, arguments);
}
protected string GetNonEmptyStringOrFallback (ITaskItem item, string metadataName, string fallbackValue, string fallbackName = null, bool required = false)

Просмотреть файл

@ -62,6 +62,9 @@
<Compile Include="..\..\tools\common\FileUtils.cs">
<Link>external\FileUtils.cs</Link>
</Compile>
<Compile Include="..\..\tools\common\NullableAttributes.cs">
<Link>external\NullableAttributes.cs</Link>
</Compile>
<Compile Include="..\Versions.dotnet.g.cs">
<Link>Versions.dotnet.g.cs</Link>
</Compile>

Просмотреть файл

@ -1,57 +1,69 @@
using System;
using System.IO;
using Microsoft.Build.Utilities;
using NUnit.Framework;
using Xamarin.Utils;
#nullable enable
namespace Xamarin.MacDev.Tasks.Tests {
[TestFixture]
public class ResolveNativeReferencesTaskTest {
TaskLoggingHelper log = new TaskLoggingHelper (new TestEngine (), "ResolveNativeReferences");
// single arch request (subset are fine)
[TestCase ("iOS", null, "arm64", "ios-arm64/Universal.framework/Universal")]
[TestCase ("iOS", "simulator", "x86_64", "ios-arm64_x86_64-simulator/Universal.framework/Universal")] // subset
[TestCase ("iOS", "maccatalyst", "x86_64", "ios-arm64_x86_64-maccatalyst/Universal.framework/Universal")] // subset
[TestCase ("tvOS", null, "arm64", "tvos-arm64/Universal.framework/Universal")]
[TestCase ("tvOS", "simulator", "x86_64", "tvos-arm64_x86_64-simulator/Universal.framework/Universal")] // subset
[TestCase ("watchOS", null, "arm64_32", "watchos-arm64_32_armv7k/Universal.framework/Universal")]
[TestCase ("watchOS", "simulator", "x86_64", "watchos-arm64_x86_64-simulator/Universal.framework/Universal")] // subset
[TestCase ("macOS", null, "x86_64", "macos-arm64_x86_64/Universal.framework/Universal")] // subset
// multiple arch request (all must be present)
[TestCase ("macOS", null, "x86_64, arm64", "macos-arm64_x86_64/Universal.framework/Universal")]
[TestCase (TargetFramework.DotNet_iOS_String, false, "arm64", "ios-arm64/Universal.framework/Universal")]
[TestCase (TargetFramework.DotNet_iOS_String, true, "x86_64", "ios-arm64_x86_64-simulator/Universal.framework/Universal")] // subset
[TestCase (TargetFramework.DotNet_MacCatalyst_String, false, "x86_64", "ios-arm64_x86_64-maccatalyst/Universal.framework/Universal")] // subset
[TestCase (TargetFramework.DotNet_tvOS_String, false, "arm64", "tvos-arm64/Universal.framework/Universal")]
[TestCase (TargetFramework.DotNet_tvOS_String, true, "x86_64", "tvos-arm64_x86_64-simulator/Universal.framework/Universal")] // subset
[TestCase (TargetFramework.Xamarin_WatchOS_1_0_String, false, "arm64_32", "watchos-arm64_32_armv7k/Universal.framework/Universal")]
[TestCase (TargetFramework.DotNet_watchOS_String, true, "x86_64", "watchos-arm64_x86_64-simulator/Universal.framework/Universal")] // subset
[TestCase (TargetFramework.DotNet_macOS_String, false, "x86_64", "macos-arm64_x86_64/Universal.framework/Universal")] // subset
// multiple arch request (all must be present)
[TestCase (TargetFramework.DotNet_macOS_String, false, "x86_64, arm64", "macos-arm64_x86_64/Universal.framework/Universal")]
// failure to resolve requested architecture
[TestCase ("iOS", "simulator", "i386, x86_64", "")] // i386 not available
// failure to resolve mismatched variant
[TestCase ("macOS", "maccatalyst", "x86_64", "")] // maccatalyst not available on macOS (it's on iOS)
public void Xcode12_x (string platform, string variant, string architecture, string expected)
[TestCase (TargetFramework.DotNet_iOS_String, true, "i386, x86_64", null)] // i386 not available
// failure to resolve mismatched variant
[TestCase (TargetFramework.DotNet_macOS_String, true, "x86_64", null)] // simulator not available on macOS
public void Xcode12_x (string targetFrameworkMoniker, bool isSimulator, string architecture, string expected)
{
// some architecture changes recently, e.g.
// in Xcode 12.1+ watchOS does not have an i386 architecture anymore
// on Xcode 12.2+ you get arm64 for all (iOS, tvOS and watchOS) simulators
var path = Path.Combine (Path.GetDirectoryName (GetType ().Assembly.Location), "Resources", "xcf-xcode12.2.plist");
var plist = PDictionary.FromFile (path);
var result = ResolveNativeReferencesBase.ResolveXCFramework (plist, platform, variant, architecture);
Assert.That (result, Is.EqualTo (expected), expected);
var result = ResolveNativeReferencesBase.TryResolveXCFramework (log, plist, "N/A", targetFrameworkMoniker, isSimulator, architecture, out var frameworkPath);
Assert.AreEqual (result, !string.IsNullOrEmpty (expected), "result");
Assert.That (frameworkPath, Is.EqualTo (expected), "frameworkPath");
}
[TestCase ("iOS", null, "ARMv7", "ios-arm64_armv7_armv7s/XTest.framework/XTest")]
[TestCase (TargetFramework.DotNet_iOS_String, false, "ARMv7", "ios-arm64_armv7_armv7s/XTest.framework/XTest")]
// there was no 64bits simulator for watchOS but a i386 one was available
[TestCase ("watchOS", "simulator", "x86_64", "")]
[TestCase ("watchOS", "simulator", "i386", "watchos-i386-simulator/XTest.framework/XTest")]
public void PreXcode12 (string platform, string variant, string architecture, string expected)
[TestCase (TargetFramework.Xamarin_WatchOS_1_0_String, true, "x86_64", null)]
[TestCase (TargetFramework.Xamarin_WatchOS_1_0_String, true, "i386", "watchos-i386-simulator/XTest.framework/XTest")]
public void PreXcode12 (string targetFrameworkMoniker, bool isSimulator, string architecture, string expected)
{
var path = Path.Combine (Path.GetDirectoryName (GetType ().Assembly.Location), "Resources", "xcf-prexcode12.plist");
var plist = PDictionary.FromFile (path);
var result = ResolveNativeReferencesBase.ResolveXCFramework (plist, platform, variant, architecture);
Assert.That (result, Is.EqualTo (expected), expected);
var result = ResolveNativeReferencesBase.TryResolveXCFramework (log, plist, "N/A", targetFrameworkMoniker, isSimulator, architecture, out var frameworkPath);
Assert.AreEqual (result, !string.IsNullOrEmpty (expected), "result");
Assert.That (frameworkPath, Is.EqualTo (expected), "frameworkPath");
}
[Test]
public void BadInfoPlist ()
{
var plist = new PDictionary ();
var result = ResolveNativeReferencesBase.ResolveXCFramework (plist, "iOS", null, "x86_64");
Assert.Null (result, "Invalid Info.plist");
var result = ResolveNativeReferencesBase.TryResolveXCFramework (log, plist, "N/A", TargetFramework.DotNet_iOS_String, false, "x86_64", out var frameworkPath);
Assert.IsFalse (result, "Invalid Info.plist");
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
#if !NET // the below attributes are no longer needed once we switch to .NET
// Ref: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
namespace System.Diagnostics.CodeAnalysis {
[AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
internal sealed class NotNullIfNotNullAttribute : Attribute {
public string ParameterName { get; }
public NotNullIfNotNullAttribute (string parameterName)
{
ParameterName = parameterName;
}
}
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage (AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute {
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute (bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
}
#endif // !NET

Просмотреть файл

@ -30,7 +30,8 @@ namespace Xamarin.Utils {
public static readonly TargetFramework Xamarin_Mac_2_0 = Parse ("Xamarin.Mac,v2.0");
public static readonly TargetFramework Xamarin_iOS_1_0 = Parse ("Xamarin.iOS,v1.0");
public static readonly TargetFramework Xamarin_WatchOS_1_0 = Parse ("Xamarin.WatchOS,v1.0");
public const string Xamarin_WatchOS_1_0_String = "Xamarin.WatchOS,v1.0";
public static readonly TargetFramework Xamarin_WatchOS_1_0 = Parse (Xamarin_WatchOS_1_0_String);
public static readonly TargetFramework Xamarin_TVOS_1_0 = Parse ("Xamarin.TVOS,v1.0");
public static readonly TargetFramework Xamarin_MacCatalyst_1_0 = Parse ("Xamarin.MacCatalyst,v1.0");

Просмотреть файл

@ -11,7 +11,7 @@ parameters:
type: boolean
default: true
- name: createLocPR
- name: manuallyCreateLocPR
type: boolean
default: false
@ -50,32 +50,18 @@ resources:
ref: refs/heads/only_codesign
endpoint: xamarin
trigger: none
trigger:
- main
schedules:
# Create the PR into main with the newest usable localization strings once a week on Sundays
- cron: "0 12 * * 0"
displayName: Weekly Translations build (Sunday @ noon)
displayName: Sunday Build
branches:
include:
- main
always: true
variables:
createLocPR: true
# Run the OneLocTask everyday without setting the createLocPR var to true.
# This will ensure that OneLoc gets our newly added error strings faster but we do not need
# to look for new usable translation PRs everyday.
# This will run around midnight in the US after each workday
- cron: "0 5 * * 2-6"
displayName: Weekly Translations build (Sunday @ noon)
branches:
include:
- main
always: true
variables:
createLocPR: false
stages:
@ -104,4 +90,4 @@ stages:
isPR: false
repositoryAlias: self
commit: HEAD
createLocPR: $(createLocPR)
createLocPR: ${{ or(eq(parameters.manuallyCreateLocPR, true), eq(variables['Build.Reason'], 'Schedule')) }}

Просмотреть файл

@ -83,7 +83,7 @@ steps:
inputs:
locProj: '$(Build.SourcesDirectory)\\Localize\\LocProject.json'
outDir: '$(Build.ArtifactStagingDirectory)'
${{ if or(eq(variables['Build.Reason'], 'Schedule'), variables.createLocPR) }}:
${{ if eq(parameters.createLocPR, true) }}:
isCreatePrSelected: true
${{ else }}:
isCreatePrSelected: false