[msbuild] Add a ComputeBundleLocation task
This commit is contained in:
Родитель
3dc0fe881a
Коммит
19dc9ce0aa
|
@ -1948,6 +1948,33 @@ namespace Xamarin.Localization.MSBuild {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The 'PublishFolderType' metadata value '{0}' on the item '{1}' is not recognized. The file will not be copied to the app bundle..
|
||||
/// </summary>
|
||||
public static string E7088 {
|
||||
get {
|
||||
return ResourceManager.GetString("E7088", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The file '{0}' does not specify a 'PublishFolderType' metadata, and a default value could not be calculated. The file will not be copied to the app bundle..
|
||||
/// </summary>
|
||||
public static string E7089 {
|
||||
get {
|
||||
return ResourceManager.GetString("E7089", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The 'PublishFolderType' metadata value '{0}' on the item '{1}' is not recognized. The file will not be copied to the app bundle. If the file is not supposed to be copied to the app bundle, remove the '{2}' metadata on the item..
|
||||
/// </summary>
|
||||
public static string E7090 {
|
||||
get {
|
||||
return ResourceManager.GetString("E7090", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to File '{0}' is not a valid framework: {1}.
|
||||
/// </summary>
|
||||
|
@ -1957,6 +1984,15 @@ namespace Xamarin.Localization.MSBuild {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The file or directory '{0}' is not a framework nor a file within a framework..
|
||||
/// </summary>
|
||||
public static string E7094 {
|
||||
get {
|
||||
return ResourceManager.GetString("E7094", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid framework: {0}.
|
||||
/// </summary>
|
||||
|
@ -2582,7 +2618,7 @@ namespace Xamarin.Localization.MSBuild {
|
|||
return ResourceManager.GetString("W7091", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The binding resource package {0} does not exist..
|
||||
/// </summary>
|
||||
|
|
|
@ -1348,6 +1348,33 @@
|
|||
<comment>The following are literal names and should not be translated: manifest</comment>
|
||||
</data>
|
||||
|
||||
<data name="E7088" xml:space="preserve">
|
||||
<value>The 'PublishFolderType' metadata value '{0}' on the item '{1}' is not recognized. The file will not be copied to the app bundle.</value>
|
||||
<comment>
|
||||
PublishFolderType: do not translate (name of metadata)
|
||||
{0}: metadata value (read from a user file)
|
||||
{1}: path to a file
|
||||
</comment>
|
||||
</data>
|
||||
|
||||
<data name="E7089" xml:space="preserve">
|
||||
<value>The file '{0}' does not specify a 'PublishFolderType' metadata, and a default value could not be calculated. The file will not be copied to the app bundle.</value>
|
||||
<comment>
|
||||
PublishFolderType: do not translate (name of metadata)
|
||||
{0}: path to a file
|
||||
</comment>
|
||||
</data>
|
||||
|
||||
<data name="E7090" xml:space="preserve">
|
||||
<value>The 'PublishFolderType' metadata value '{0}' on the item '{1}' is not recognized. The file will not be copied to the app bundle. If the file is not supposed to be copied to the app bundle, remove the '{2}' metadata on the item.</value>
|
||||
<comment>
|
||||
PublishFolderType: do not translate (name of metadata)
|
||||
{0}: metadata value (read from a user file)
|
||||
{1}: path to a file
|
||||
{2}: name of metadata (either 'CopyToOutputDirectory' or 'CopyToPublishDirectory')
|
||||
</comment>
|
||||
</data>
|
||||
|
||||
<data name="W7091" xml:space="preserve">
|
||||
<value>The framework {0} is a framework of static libraries, and will not be copied to the app.</value>
|
||||
</data>
|
||||
|
@ -1361,4 +1388,7 @@
|
|||
<value>The binding resource package {0} does not exist.</value>
|
||||
</data>
|
||||
|
||||
<data name="E7094" xml:space="preserve">
|
||||
<value>The file or directory '{0}' is not a framework nor a file within a framework.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
using Xamarin.MacDev;
|
||||
using Xamarin.Utils;
|
||||
using Xamarin.Localization.MSBuild;
|
||||
|
||||
namespace Xamarin.MacDev.Tasks {
|
||||
public abstract class ComputeBundleLocationTaskBase : XamarinTask {
|
||||
// not required because this can be the root directory (so an empty string)
|
||||
public string AssemblyDirectory { get; set; } = string.Empty;
|
||||
|
||||
public ITaskItem []? BundleResource { get; set; }
|
||||
public ITaskItem []? Content { get; set; }
|
||||
public ITaskItem []? EmbeddedResource { get; set; }
|
||||
|
||||
[Required]
|
||||
public string FrameworksDirectory { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string PlugInsDirectory { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string ProjectDir { get; set; } = string.Empty;
|
||||
|
||||
// not required because this can be the root directory (so an empty string)
|
||||
public string ResourceDirectory { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public ITaskItem []? ResolvedFileToPublish { get; set; }
|
||||
|
||||
[Output]
|
||||
public ITaskItem []? UpdatedResolvedFileToPublish { get; set; }
|
||||
|
||||
HashSet<string> resourceFilesSet = new HashSet<string> ();
|
||||
|
||||
public override bool Execute ()
|
||||
{
|
||||
if (ResolvedFileToPublish is null || ResolvedFileToPublish.Length == 0)
|
||||
return !Log.HasLoggedErrors;
|
||||
|
||||
// Make sure we use the correct path separator, these are relative paths, so it doesn't look
|
||||
// like MSBuild does the conversion automatically.
|
||||
FrameworksDirectory = FrameworksDirectory.Replace ('\\', Path.DirectorySeparatorChar);
|
||||
PlugInsDirectory = PlugInsDirectory.Replace ('\\', Path.DirectorySeparatorChar);
|
||||
ResourceDirectory = ResourceDirectory.Replace ('\\', Path.DirectorySeparatorChar);
|
||||
|
||||
// Collect all our BundleResource, Content and EmbeddedResource paths into one big dictionary for later lookup.
|
||||
if (BundleResource?.Length > 0)
|
||||
resourceFilesSet.UnionWith (BundleResource.Select (v => Path.GetFullPath (v.ItemSpec)));
|
||||
if (Content?.Length > 0)
|
||||
resourceFilesSet.UnionWith (Content.Select (v => Path.GetFullPath (v.ItemSpec)));
|
||||
if (EmbeddedResource?.Length > 0)
|
||||
resourceFilesSet.UnionWith (EmbeddedResource.Select (v => Path.GetFullPath (v.ItemSpec)));
|
||||
|
||||
var appleFrameworks = new Dictionary<string, List<ITaskItem>> ();
|
||||
var list = ResolvedFileToPublish.ToList ();
|
||||
foreach (var item in list.ToArray ()) { // iterate over a copy of the list, because we might modify the original list
|
||||
// Compute the publish folder type if it's not specified
|
||||
var publishFolderType = ParsePublishFolderType (item);
|
||||
if (publishFolderType == PublishFolderType.Unset) {
|
||||
publishFolderType = ComputePublishFolderType (list, item);
|
||||
item.SetMetadata ("PublishFolderType", publishFolderType.ToString ());
|
||||
}
|
||||
|
||||
// Figure out the relative directory inside the app bundle where the item is supposed to be placed.
|
||||
var relativePath = string.Empty;
|
||||
switch (publishFolderType) {
|
||||
case PublishFolderType.Assembly:
|
||||
relativePath = AssemblyDirectory;
|
||||
break;
|
||||
case PublishFolderType.Resource:
|
||||
relativePath = ResourceDirectory;
|
||||
break;
|
||||
case PublishFolderType.AppleFramework:
|
||||
if (TryGetFrameworkDirectory (item.ItemSpec, out var frameworkDirectory)) {
|
||||
if (!appleFrameworks.TryGetValue (frameworkDirectory!, out var items))
|
||||
appleFrameworks [frameworkDirectory!] = items = new List<ITaskItem> ();
|
||||
items.Add (item);
|
||||
// Remove AppleFramework entries, we'll add back one entry per framework at the end
|
||||
list.Remove (item);
|
||||
continue;
|
||||
}
|
||||
Log.LogError (7094, item.ItemSpec, MSBStrings.E7094 /* The file or directory '{0}' is not a framework nor a file within a framework. */, item.ItemSpec);
|
||||
continue;
|
||||
case PublishFolderType.CompressedAppleFramework:
|
||||
relativePath = FrameworksDirectory;
|
||||
break;
|
||||
case PublishFolderType.AppleBindingResourcePackage:
|
||||
case PublishFolderType.CompressedAppleBindingResourcePackage:
|
||||
// Nothing to do here, this is handled fully in the targets file
|
||||
break;
|
||||
case PublishFolderType.PlugIns:
|
||||
relativePath = PlugInsDirectory;
|
||||
break;
|
||||
case PublishFolderType.CompressedPlugIns:
|
||||
relativePath = PlugInsDirectory;
|
||||
break;
|
||||
case PublishFolderType.RootDirectory:
|
||||
break;
|
||||
case PublishFolderType.DynamicLibrary:
|
||||
relativePath = AssemblyDirectory;
|
||||
break;
|
||||
case PublishFolderType.StaticLibrary:
|
||||
// Nothing to do here.
|
||||
continue;
|
||||
case PublishFolderType.None:
|
||||
continue;
|
||||
case PublishFolderType.Unknown:
|
||||
default:
|
||||
ReportUnknownPublishFolderType (item);
|
||||
item.SetMetadata ("PublishFolderType", "None");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute the relative path of the item relative to the root of the app bundle
|
||||
var virtualProjectPath = GetVirtualAppBundlePath (item);
|
||||
relativePath = Path.Combine (relativePath, virtualProjectPath);
|
||||
item.SetMetadata ("RelativePath", relativePath);
|
||||
}
|
||||
|
||||
// We may have multiple input items for each framework, but we only want to return a single
|
||||
// entry per framework. In the loop above we removed all input items corresponding with a
|
||||
// framework, so add back a single item here.
|
||||
foreach (var entry in appleFrameworks) {
|
||||
var items = entry.Value;
|
||||
var item = new TaskItem (entry.Key);
|
||||
item.SetMetadata ("PublishFolderType", "AppleFramework");
|
||||
item.SetMetadata ("RelativePath", Path.Combine (FrameworksDirectory, Path.GetFileName (entry.Key)));
|
||||
list.Add (item);
|
||||
}
|
||||
|
||||
UpdatedResolvedFileToPublish = list.ToArray ();
|
||||
|
||||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
|
||||
// Check if the input, or any of it's parent directories is either an *.xcframework, or a *.framework
|
||||
static bool TryGetFrameworkDirectory (string path, out string? frameworkDirectory)
|
||||
{
|
||||
if (string.IsNullOrEmpty (path)) {
|
||||
frameworkDirectory = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path.EndsWith (".xcframework", StringComparison.OrdinalIgnoreCase)) {
|
||||
frameworkDirectory = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path.EndsWith (".framework", StringComparison.OrdinalIgnoreCase)) {
|
||||
// We might be inside a .xcframework, so check for that first
|
||||
if (TryGetFrameworkDirectory (Path.GetDirectoryName (path), out var xcframeworkDirectory) && xcframeworkDirectory!.EndsWith (".xcframework", StringComparison.OrdinalIgnoreCase)) {
|
||||
frameworkDirectory = xcframeworkDirectory;
|
||||
return true;
|
||||
}
|
||||
|
||||
frameworkDirectory = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryGetFrameworkDirectory (Path.GetDirectoryName (path), out frameworkDirectory);
|
||||
}
|
||||
|
||||
// Check if the input, or any of it's parent directories is a *.resources directory or a *.resources.zip file next to a *.dll.
|
||||
static bool IsBindingResourcePackage (string path, out PublishFolderType type)
|
||||
{
|
||||
type = PublishFolderType.None;
|
||||
if (string.IsNullOrEmpty (path))
|
||||
return false;
|
||||
|
||||
if (path.EndsWith (".resources", StringComparison.OrdinalIgnoreCase) && File.Exists (Path.ChangeExtension (path, "dll"))) {
|
||||
type = PublishFolderType.AppleBindingResourcePackage;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path.EndsWith (".resources.zip", StringComparison.OrdinalIgnoreCase) && File.Exists (Path.ChangeExtension (Path.GetFileNameWithoutExtension (path), "dll"))) {
|
||||
type = PublishFolderType.CompressedAppleBindingResourcePackage;
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsBindingResourcePackage (Path.GetDirectoryName (path), out type);
|
||||
}
|
||||
|
||||
static string GetVirtualAppBundlePath (ITaskItem item)
|
||||
{
|
||||
// We need to take "TargetPath" into account - this is path of the file relative to the output directory, and may also change the filename itself (it's for instance used to rename 'app.config' to the 'mainassembly.exe.config').
|
||||
// If "TargetPath" is specified, we rename the item to have "TargetPath" as the file name (the rest of the path is kept).
|
||||
// This value takes precedence over the "Link" metadata (https://github.com/dotnet/msbuild/issues/2795)
|
||||
var targetPath = item.GetMetadata ("TargetPath");
|
||||
if (!string.IsNullOrEmpty (targetPath))
|
||||
return targetPath;
|
||||
|
||||
// If there's no "TargetPath" metadata, then we check the "Link" metadata, which works the same way as "TargetPath" otherwise.
|
||||
var link = item.GetMetadata ("Link");
|
||||
if (!string.IsNullOrEmpty (link))
|
||||
return link;
|
||||
|
||||
var virtualPath = Path.GetFileName (item.ItemSpec);
|
||||
|
||||
// If neither "TargetPath" nor "Link" is set, we need to take "DestinationSubDirectory" into account - this is used to specify the subdirectory for resource assemblies for instance.
|
||||
// Ref: https://github.com/dotnet/sdk/blob/0fc72ddb758dd136182972c2aea1d504ea046cfd/src/Tasks/Common/ItemUtilities.cs#L126-L128
|
||||
// Contrary to the "TargetPath" and "Link" metadata, this value doesn't specify the filename itself, only the containing directory name.
|
||||
var destinationSubDirectory = item.GetMetadata ("DestinationSubDirectory");
|
||||
if (!string.IsNullOrEmpty (destinationSubDirectory))
|
||||
virtualPath = Path.Combine (destinationSubDirectory, virtualPath);
|
||||
|
||||
return virtualPath;
|
||||
}
|
||||
|
||||
void ReportUnknownPublishFolderType (ITaskItem item)
|
||||
{
|
||||
var publishFolderType = item.GetMetadata ("PublishFolderType");
|
||||
|
||||
var metadata = item.GetMetadata ("CopyToOutputDirectory");
|
||||
if (!string.IsNullOrEmpty (metadata)) {
|
||||
Log.LogWarning (MSBStrings.E7090 /* The 'PublishFolderType' metadata value '{0}' on the item '{1}' is not recognized. The file will not be copied to the app bundle. If the file is not supposed to be copied to the app bundle, remove the '{2}' metadata on the item. */, publishFolderType, item.ItemSpec, "CopyToOutputDirectory");
|
||||
return;
|
||||
}
|
||||
|
||||
metadata = item.GetMetadata ("CopyToPublishDirectory");
|
||||
if (!string.IsNullOrEmpty (metadata)) {
|
||||
Log.LogWarning (MSBStrings.E7090 /* The 'PublishFolderType' metadata value '{0}' on the item '{1}' is not recognized. The file will not be copied to the app bundle. If the file is not supposed to be copied to the app bundle, remove the '{2}' metadata on the item. */, publishFolderType, item.ItemSpec, "CopyToPublishDirectory");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.LogWarning (MSBStrings.E7088 /* The 'PublishFolderType' metadata value '{0}' on the item '{1}' is not recognized. The file will not be copied to the app bundle. */, publishFolderType, item.ItemSpec);
|
||||
}
|
||||
|
||||
// 'item' is not supposed to have a PublishFolderType set
|
||||
PublishFolderType ComputePublishFolderType (IList<ITaskItem> items, ITaskItem item)
|
||||
{
|
||||
var filename = item.ItemSpec;
|
||||
var targetPath = item.GetMetadata ("TargetPath");
|
||||
if (!string.IsNullOrEmpty (targetPath))
|
||||
filename = Path.Combine (Path.GetDirectoryName (filename), Path.GetFileName (targetPath));
|
||||
|
||||
// Check if the item came from @(BundleResource), @(Content) or @(EmbeddedResource)
|
||||
if (resourceFilesSet.Contains (Path.GetFullPath (item.ItemSpec)))
|
||||
return PublishFolderType.Resource;
|
||||
|
||||
// Assemblies and their related files
|
||||
if (filename.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.Assembly;
|
||||
} else if (filename.EndsWith (".exe", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.Assembly;
|
||||
} else if (filename.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.Assembly;
|
||||
} else if (filename.EndsWith (".dll.mdb", StringComparison.OrdinalIgnoreCase) || filename.EndsWith (".exe.mdb", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.Assembly;
|
||||
} else if (filename.EndsWith (".config", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.Assembly;
|
||||
}
|
||||
|
||||
// Binding resource package (*.resources / *.resources.zip)
|
||||
if (IsBindingResourcePackage (filename, out var type))
|
||||
return type;
|
||||
|
||||
// Native (xc)frameworks.
|
||||
// We do this after checking for binding resource packages, because those might contain frameworks.
|
||||
if (TryGetFrameworkDirectory (filename, out _))
|
||||
return PublishFolderType.AppleFramework;
|
||||
|
||||
// resources (png, jpg, ...?)
|
||||
if (filename.EndsWith (".jpg", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.Resource;
|
||||
} else if (filename.EndsWith (".png", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.Resource;
|
||||
}
|
||||
|
||||
// *.framework.zip, *.xcframework.zip
|
||||
if (filename.EndsWith (".framework.zip", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.CompressedAppleFramework;
|
||||
} else if (filename.EndsWith (".xcframework.zip", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.CompressedAppleFramework;
|
||||
}
|
||||
|
||||
// *.a and *.dylib
|
||||
if (filename.EndsWith (".a", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.StaticLibrary;
|
||||
} else if (filename.EndsWith (".dylib", StringComparison.OrdinalIgnoreCase)) {
|
||||
return PublishFolderType.DynamicLibrary;
|
||||
}
|
||||
|
||||
// no other files are copied
|
||||
|
||||
Log.LogWarning (MSBStrings.E7089 /* The file '{0}' does not specify a 'PublishFolderType' metadata, and a default value could not be calculated. The file will not be copied to the app bundle. */, item.ItemSpec);
|
||||
|
||||
return PublishFolderType.None;
|
||||
}
|
||||
|
||||
static PublishFolderType ParsePublishFolderType (ITaskItem item)
|
||||
{
|
||||
return ParsePublishFolderType (item.GetMetadata ("PublishFolderType"));
|
||||
}
|
||||
|
||||
static PublishFolderType ParsePublishFolderType (string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty (value))
|
||||
return PublishFolderType.Unset;
|
||||
|
||||
if (!Enum.TryParse<PublishFolderType> (value, out var result))
|
||||
result = PublishFolderType.Unknown;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
enum PublishFolderType {
|
||||
Unset,
|
||||
None,
|
||||
RootDirectory,
|
||||
Assembly,
|
||||
Resource,
|
||||
AppleBindingResourcePackage,
|
||||
CompressedAppleBindingResourcePackage,
|
||||
AppleFramework,
|
||||
CompressedAppleFramework,
|
||||
PlugIns,
|
||||
CompressedPlugIns,
|
||||
DynamicLibrary, // link with + copy to app bundle
|
||||
StaticLibrary, // link with (but not copy to app bundle)
|
||||
Unknown,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
namespace Xamarin.MacDev.Tasks {
|
||||
public class ComputeBundleLocation : ComputeBundleLocationTaskBase {
|
||||
}
|
||||
}
|
|
@ -81,6 +81,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
|
|||
<UsingTask TaskName="Xamarin.MacDev.Tasks.CollectBundleResources" AssemblyFile="$(_TaskAssemblyName)" />
|
||||
<UsingTask TaskName="Xamarin.MacDev.Tasks.CollectFrameworks" AssemblyFile="$(_TaskAssemblyName)" />
|
||||
<UsingTask TaskName="Xamarin.MacDev.Tasks.CompileEntitlements" AssemblyFile="$(_TaskAssemblyName)" />
|
||||
<UsingTask TaskName="Xamarin.MacDev.Tasks.ComputeBundleLocation" AssemblyFile="$(_TaskAssemblyName)" />
|
||||
<UsingTask TaskName="Xamarin.MacDev.Tasks.ComputeBundleResourceOutputPaths" AssemblyFile="$(_TaskAssemblyName)" />
|
||||
<UsingTask TaskName="Xamarin.MacDev.Tasks.CoreMLCompiler" AssemblyFile="$(_TaskAssemblyName)" />
|
||||
<UsingTask TaskName="Xamarin.MacDev.Tasks.CreateAssetPackManifest" AssemblyFile="$(_TaskAssemblyName)" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче