[net8.0] Merge main into net8.0.
This commit is contained in:
Коммит
059aa7ad8f
|
@ -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()) > 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
|
||||
|
|
Загрузка…
Ссылка в новой задаче