Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[msbuild] Add support for bundling original resources in libraries. Fixes #19028. #20822

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
fd106e9
[DEBUG] Disable all but iOS.
rolfbjarne Sep 20, 2024
683127f
[tests] Use a file as the Outputs property in MSBuild.
rolfbjarne Oct 28, 2024
bdae426
[devops] Provision simulator on remote Mac bots.
rolfbjarne Sep 19, 2024
116ba61
[devops] Improve Windows tests a little bit.
rolfbjarne Oct 2, 2024
e0ecad8
[msbuild] Refactor out function in CollectBundleResource to make code…
rolfbjarne Apr 29, 2024
166c954
[msbuild] Refactor out function in CompileSceneKitAssets to make code…
rolfbjarne Apr 29, 2024
bcbe67a
[msbuild] Add support for bundling original resources in libraries. F…
rolfbjarne Feb 1, 2024
18d5fdf
[tests] Add test.
rolfbjarne Jan 30, 2024
2e77d2a
[msbuild] Honor the LogicalName metadata on TextureAtlas items.
rolfbjarne Apr 30, 2024
1a59c31
[msbuild] Fix LogicalName for SceneKitAsset items.
rolfbjarne May 2, 2024
5fdf311
[msbuild] Don't execute the _CreateEmbeddedResources and _PackLibrary…
rolfbjarne Jun 5, 2024
2bae276
[msbuild] Always collect bundle resources if we're bundling original …
rolfbjarne Jun 10, 2024
d25bac5
[msbuild] Don't call _ResolveUniversalTypeIdentifiers when bundling o…
rolfbjarne Jun 10, 2024
70b43f2
[msbuild] Store DefiningProjectFullPath in a different metadata.
rolfbjarne Jun 11, 2024
7ca7fa8
[msbuild] Libraries don't have to build remotely anymore.
rolfbjarne Oct 2, 2024
ed34050
[msbuild] Unpack on Windows.
rolfbjarne Oct 24, 2024
eb535b5
[msbuild] Remove the GetBundleResourceWithLogicalNameItems target, no…
rolfbjarne Oct 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Make.config
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,9 @@ MIN_TVOS_SIMULATOR_VERSION=15.0
EXTRA_SIMULATORS=com.apple.pkg.iPhoneSimulatorSDK15_0 com.apple.pkg.AppleTVSimulatorSDK15_0 com.apple.pkg.WatchSimulatorSDK8_0

INCLUDE_IOS=1
INCLUDE_MAC=1
INCLUDE_TVOS=1
INCLUDE_MACCATALYST=1
INCLUDE_MAC=
INCLUDE_TVOS=
INCLUDE_MACCATALYST=
INCLUDE_DEVICE=1
INCLUDE_XAMARIN_LEGACY=
INCLUDE_HOTRESTART=1
Expand Down
1 change: 1 addition & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@
<!-- outer build for multi-rid build -->
<BuildDependsOn Condition="'$(RuntimeIdentifiers)' != ''">
_ErrorRuntimeIdentifiersClash;
BuildOnlySettings;
_CollectBundleResources;
_RunRidSpecificBuild;
_DetectAppManifest;
Expand Down
134 changes: 111 additions & 23 deletions msbuild/Xamarin.MacDev.Tasks/BundleResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
using System.Collections.Generic;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

using Xamarin.Utils;
using Xamarin.MacDev.Tasks;

namespace Xamarin.MacDev {
public static class BundleResource {
Expand Down Expand Up @@ -56,66 +58,152 @@ public static IList<string> SplitResourcePrefixes (string prefix)
.ToList ();
}

public static string GetVirtualProjectPath (string projectDir, ITaskItem item, bool isVSBuild)
static void Log (Task? task, string msg)
{
if (task is not null) {
task.Log.LogMessage (MessageImportance.Low, msg);
} else {
Console.WriteLine (msg);
}
}

public static string GetVirtualProjectPath (XamarinTask task, string projectDir, ITaskItem item)
{
return GetVirtualProjectPath (task, projectDir, item, !string.IsNullOrEmpty (task.SessionId));
}

public static string GetVirtualProjectPath (XamarinToolTask task, string projectDir, ITaskItem item)
{
return GetVirtualProjectPath (task, projectDir, item, !string.IsNullOrEmpty (task.SessionId));
}

public static string GetVirtualProjectPath (Task task, string projectDir, ITaskItem item, bool isVSBuild)
{
var link = item.GetMetadata ("Link");

// Note: if the Link metadata exists, then it will be the equivalent of the ProjectVirtualPath
if (!string.IsNullOrEmpty (link)) {
if (Path.DirectorySeparatorChar != '\\')
if (Path.DirectorySeparatorChar != '\\') {
Log (task, $"GetVirtualProjectPath ({projectDir}, {item.ItemSpec}, {isVSBuild}) => Link={link.Replace ('\\', '/')} (original {link})");
return link.Replace ('\\', '/');
}

Log (task, $"GetVirtualProjectPath ({projectDir}, {item.ItemSpec}, {isVSBuild}) => Link={link.Replace ('\\', '/')}");
return link;
}

// HACK: This is for Visual Studio iOS projects
var isDefaultItem = item.GetMetadata ("IsDefaultItem") == "true";
var localMSBuildProjectFullPath = item.GetMetadata ("LocalMSBuildProjectFullPath");
var localDefiningProjectFullPath = item.GetMetadata ("LocalDefiningProjectFullPath").Replace ('\\', '/');
string path;
string baseDir;

string rv;

if (string.IsNullOrEmpty (localDefiningProjectFullPath)) {
task.Log.LogError ($"The item {item.ItemSpec} does not have a 'LocalDefiningProjectFullPath' value set.");
return "placeholder";
}

if (string.IsNullOrEmpty (localMSBuildProjectFullPath)) {
task.Log.LogError ($"The item {item.ItemSpec} does not have a 'LocalMSBuildProjectFullPath' value set.");
return "placeholder";
}

if (isVSBuild) {
if (item.GetMetadata ("DefiningProjectFullPath") != item.GetMetadata ("MSBuildProjectFullPath")) {
return item.GetMetadata ("FullPath").Replace (item.GetMetadata ("DefiningProjectDirectory"), string.Empty);
// 'path' is full path on Windows
path = PathUtils.PathCombineWindows (projectDir, item.ItemSpec);

// 'baseDir' is the base directory in Windows
if (isDefaultItem) {
baseDir = Path.GetDirectoryName (localMSBuildProjectFullPath);
} else {
return item.ItemSpec;
baseDir = Path.GetDirectoryName (localDefiningProjectFullPath);
}
}

var isDefaultItem = item.GetMetadata ("IsDefaultItem") == "true";
var definingProjectFullPath = item.GetMetadata (isDefaultItem ? "MSBuildProjectFullPath" : "DefiningProjectFullPath");
var path = item.GetMetadata ("FullPath");
string baseDir;

if (!string.IsNullOrEmpty (definingProjectFullPath)) {
baseDir = Path.GetDirectoryName (definingProjectFullPath);
rv = PathUtils.AbsoluteToRelativeWindows (baseDir, path);
// Make it a mac-style path
rv = rv.Replace ('\\', '/');

Log (task, $"GetVirtualProjectPath\n" +
$"\t\t\t{projectDir}\n" +
$"\t\t\t{item.ItemSpec}\n" +
$"\t\t\t{isVSBuild}\n" +
$"\t\t\t\tisDefaultItem={isDefaultItem}\n" +
$"\t\t\t\tLocalMSBuildProjectFullPath={localMSBuildProjectFullPath}\n" +
$"\t\t\t\tLocalDefiningProjectFullPath={localDefiningProjectFullPath}\n" +
$"\t\t\t\tpath={path}\n" +
$"\t\t\t\tbaseDir={baseDir}\n" +
$"\t\t\t\t ==> {rv}");
} else {
baseDir = projectDir;
path = Path.Combine (projectDir, item.ItemSpec);

if (isDefaultItem) {
baseDir = Path.GetDirectoryName (localMSBuildProjectFullPath);
} else if (!string.IsNullOrEmpty (localDefiningProjectFullPath)) {
baseDir = Path.GetDirectoryName (localDefiningProjectFullPath);
} else {
baseDir = projectDir;
}

var originalBaseDir = baseDir;
var originalPath = path;

baseDir = PathUtils.ResolveSymbolicLinks (baseDir);
path = PathUtils.ResolveSymbolicLinks (path);

rv = PathUtils.AbsoluteToRelative (baseDir, path);
Log (task, $"GetVirtualProjectPath\n" +
$"\t\t\t{projectDir}\n" +
$"\t\t\t{item.ItemSpec}\n" +
$"\t\t\t{isVSBuild}\n" +
$"\t\t\t\tisDefaultItem={isDefaultItem}\n" +
$"\t\t\t\tLocalMSBuildProjectFullPath={localMSBuildProjectFullPath}\n" +
$"\t\t\t\tLocalDefiningProjectFullPath={localDefiningProjectFullPath}\n" +
$"\t\t\t\tpath={path} ({originalPath})\n" +
$"\t\t\t\tbaseDir={baseDir} ({originalBaseDir})\n" +
$"\t\t\t\t ==> {rv}");
}

baseDir = PathUtils.ResolveSymbolicLinks (baseDir);
path = PathUtils.ResolveSymbolicLinks (path);
return rv;
}

return PathUtils.AbsoluteToRelative (baseDir, path);
static bool GetISVSBuild (Task task)
{
if (task is XamarinTask xt)
return !string.IsNullOrEmpty (xt.SessionId);
if (task is XamarinToolTask xtt)
return !string.IsNullOrEmpty (xtt.SessionId);
return false;
}

public static string GetLogicalName (string projectDir, IList<string> prefixes, ITaskItem item, bool isVSBuild)
public static string GetLogicalName (Task task, string projectDir, IList<string> prefixes, ITaskItem item)
{
var logicalName = item.GetMetadata ("LogicalName");

if (!string.IsNullOrEmpty (logicalName)) {
if (Path.DirectorySeparatorChar != '\\')
if (Path.DirectorySeparatorChar != '\\') {
task?.Log.LogMessage (MessageImportance.Low, $"GetLogicalName ({projectDir}, {string.Join (";", prefixes)}, {item.ItemSpec}) => has LogicalName={logicalName.Replace ('\\', '/')} (original {logicalName})");
return logicalName.Replace ('\\', '/');

}
task?.Log.LogMessage (MessageImportance.Low, $"GetLogicalName ({projectDir}, {string.Join (";", prefixes)}, {item.ItemSpec}) => has LogicalName={logicalName}");
return logicalName;
}

var vpath = GetVirtualProjectPath (projectDir, item, isVSBuild);
var vpath = GetVirtualProjectPath (task, projectDir, item, GetISVSBuild (task));
int matchlen = 0;

foreach (var prefix in prefixes) {
if (vpath.StartsWith (prefix, StringComparison.OrdinalIgnoreCase) && prefix.Length > matchlen)
matchlen = prefix.Length;
}

if (matchlen > 0)
if (matchlen > 0) {
task?.Log.LogMessage (MessageImportance.Low, $"GetLogicalName ({projectDir}, {string.Join (";", prefixes)}, {item.ItemSpec}) => has LogicalName={vpath.Substring (matchlen)} with vpath {vpath} substring {matchlen}");
return vpath.Substring (matchlen);
}

task?.Log.LogMessage (MessageImportance.Low, $"GetLogicalName ({projectDir}, {string.Join (";", prefixes)}, {item.ItemSpec}) => has LogicalName={vpath.Substring (matchlen)} with vpath {vpath}");
return vpath;
}
}
Expand Down
8 changes: 4 additions & 4 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ bool IsMessagesExtension {

protected override void AppendCommandLineArguments (IDictionary<string, string?> environment, CommandLineArgumentBuilder args, ITaskItem [] items)
{
var assetDirs = new HashSet<string> (items.Select (x => BundleResource.GetVirtualProjectPath (ProjectDir, x, !string.IsNullOrEmpty (SessionId))));
var assetDirs = new HashSet<string> (items.Select (x => BundleResource.GetVirtualProjectPath (this, ProjectDir, x)));

if (!string.IsNullOrEmpty (XSAppIconAssets)) {
int index = XSAppIconAssets.IndexOf (".xcassets" + Path.DirectorySeparatorChar, StringComparison.Ordinal);
Expand Down Expand Up @@ -210,7 +210,7 @@ public override bool Execute ()
var specs = new PArray ();

for (int i = 0; i < ImageAssets.Length; i++) {
var vpath = BundleResource.GetVirtualProjectPath (ProjectDir, ImageAssets [i], !string.IsNullOrEmpty (SessionId));
var vpath = BundleResource.GetVirtualProjectPath (this, ProjectDir, ImageAssets [i]);

// Ignore MacOS .DS_Store files...
if (Path.GetFileName (vpath).Equals (".DS_Store", StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -251,7 +251,7 @@ public override bool Execute ()
items.Clear ();

for (int i = 0; i < ImageAssets.Length; i++) {
var vpath = BundleResource.GetVirtualProjectPath (ProjectDir, ImageAssets [i], !string.IsNullOrEmpty (SessionId));
var vpath = BundleResource.GetVirtualProjectPath (this, ProjectDir, ImageAssets [i]);
var clone = false;
ITaskItem item;

Expand Down Expand Up @@ -302,7 +302,7 @@ public override bool Execute ()

// Note: `items` contains only the Contents.json files at this point
for (int i = 0; i < items.Count; i++) {
var vpath = BundleResource.GetVirtualProjectPath (ProjectDir, items [i], !string.IsNullOrEmpty (SessionId));
var vpath = BundleResource.GetVirtualProjectPath (this, ProjectDir, items [i]);
var path = items [i].GetMetadata ("FullPath");

// get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
Expand Down
75 changes: 45 additions & 30 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CollectBundleResources.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
Expand Down Expand Up @@ -31,6 +33,8 @@ public class CollectBundleResources : XamarinTask, ICancelableTask {
[Output]
public ITaskItem [] BundleResourcesWithLogicalNames { get; set; } = Array.Empty<ITaskItem> ();

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

#endregion

static bool CanOptimize (string path)
Expand Down Expand Up @@ -68,37 +72,8 @@ bool ExecuteImpl ()
var bundleResources = new List<ITaskItem> ();

foreach (var item in BundleResources) {
// Skip anything with the PublishFolderType metadata, these are copied directly to the ResolvedFileToPublish item group instead.
var publishFolderType = item.GetMetadata ("PublishFolderType");
if (!string.IsNullOrEmpty (publishFolderType))
continue;

var logicalName = BundleResource.GetLogicalName (ProjectDir, prefixes, item, !string.IsNullOrEmpty (SessionId));
// We need a physical path here, ignore the Link element
var path = item.GetMetadata ("FullPath");

if (!File.Exists (path)) {
Log.LogError (MSBStrings.E0099, logicalName, path);
continue;
}

if (logicalName.StartsWith (".." + Path.DirectorySeparatorChar, StringComparison.Ordinal)) {
Log.LogError (null, null, null, item.ItemSpec, 0, 0, 0, 0, MSBStrings.E0100, logicalName);
continue;
}

if (logicalName == "Info.plist") {
Log.LogWarning (null, null, null, item.ItemSpec, 0, 0, 0, 0, MSBStrings.E0101);
if (!TryCreateItemWithLogicalName (this, item, ProjectDir, prefixes, SessionId, out var bundleResource))
continue;
}

if (BundleResource.IsIllegalName (logicalName, out var illegal)) {
Log.LogError (null, null, null, item.ItemSpec, 0, 0, 0, 0, MSBStrings.E0102, illegal);
continue;
}

var bundleResource = new TaskItem (item);
bundleResource.SetMetadata ("LogicalName", logicalName);

bool optimize = false;

Expand All @@ -122,11 +97,51 @@ bool ExecuteImpl ()
bundleResources.Add (bundleResource);
}

bundleResources.AddRange (UnpackedResources);

BundleResourcesWithLogicalNames = bundleResources.ToArray ();

return !Log.HasLoggedErrors;
}

public static bool TryCreateItemWithLogicalName (Task task, ITaskItem item, string projectDir, IList<string> prefixes, string sessionId, [NotNullWhen (true)] out TaskItem? itemWithLogicalName)
{
itemWithLogicalName = null;

// Skip anything with the PublishFolderType metadata, these are copied directly to the ResolvedFileToPublish item group instead.
var publishFolderType = item.GetMetadata ("PublishFolderType");
if (!string.IsNullOrEmpty (publishFolderType))
return false;

var logicalName = BundleResource.GetLogicalName (task, projectDir, prefixes, item);
// We need a physical path here, ignore the Link element
var path = item.GetMetadata ("FullPath");

if (!File.Exists (path)) {
task.Log.LogError (MSBStrings.E0099, logicalName, path);
return false;
}

if (logicalName.StartsWith (".." + Path.DirectorySeparatorChar, StringComparison.Ordinal)) {
task.Log.LogError (null, null, null, item.ItemSpec, 0, 0, 0, 0, MSBStrings.E0100, logicalName);
return false;
}

if (logicalName == "Info.plist") {
task.Log.LogWarning (null, null, null, item.ItemSpec, 0, 0, 0, 0, MSBStrings.E0101);
return false;
}

if (BundleResource.IsIllegalName (logicalName, out var illegal)) {
task.Log.LogError (null, null, null, item.ItemSpec, 0, 0, 0, 0, MSBStrings.E0102, illegal);
return false;
}

itemWithLogicalName = new TaskItem (item);
itemWithLogicalName.SetMetadata ("LogicalName", logicalName);
return true;
}

public void Cancel ()
{
if (ShouldExecuteRemotely ())
Expand Down
Loading