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 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b6ab749
[msbuild] Refactor out function in CollectBundleResource to make code…
rolfbjarne Apr 29, 2024
71d6803
[msbuild] Refactor out function in CompileSceneKitAssets to make code…
rolfbjarne Apr 29, 2024
65f6828
[msbuild] Add support for bundling original resources in libraries. F…
rolfbjarne Feb 1, 2024
b6f84f8
[msbuild] Add support for packing/unpacking PartialAppManifest items.
rolfbjarne Apr 30, 2024
a474a8a
[tests] Automatically set properties for remote builds.
rolfbjarne Feb 1, 2024
5327616
[tests] Add test.
rolfbjarne Jan 30, 2024
c20574f
[msbuild] Honor the LogicalName metadata on TextureAtlas items.
rolfbjarne Apr 30, 2024
5146cea
[msbuild] Fix LogicalName for SceneKitAsset items.
rolfbjarne May 2, 2024
4d7fb78
[devops] Unset MAC_AGENT_IP for local testing.
rolfbjarne Jun 4, 2024
6660359
[tests] Adjust to accept Windows-style paths.
rolfbjarne Jun 4, 2024
af28504
[msbuild] Don't execute the _CreateEmbeddedResources and _PackLibrary…
rolfbjarne Jun 5, 2024
bf00be6
[tests] Adjust expected output in an MSBuild test.
rolfbjarne Jun 6, 2024
9c2659a
Update expected file paths in tests.
rolfbjarne Jun 7, 2024
1a0d336
Always collect bundle resources if we're bundling original resources.
rolfbjarne Jun 10, 2024
1d14d15
[msbuild] Don't call _ResolveUniversalTypeIdentifiers when bundling o…
rolfbjarne Jun 10, 2024
78c90ff
Add debug spew.
rolfbjarne Jun 11, 2024
edf0ac6
Fix test build.
rolfbjarne Jul 5, 2024
12a0e0b
Merge branch 'main' into dev/rolf/msbuild-bundle-original-resources-i…
rolfbjarne Aug 8, 2024
565e45f
Merge branch 'main' into dev/rolf/msbuild-bundle-original-resources-i…
rolfbjarne Sep 12, 2024
9bb271a
Merge remote-tracking branch 'origin/main' into dev/rolf/msbuild-bund…
rolfbjarne Sep 16, 2024
7a60736
Add debug spew.
rolfbjarne Sep 16, 2024
437eff1
More debug spew.
rolfbjarne Sep 16, 2024
79463b6
Next attemp
rolfbjarne Sep 16, 2024
194ffc0
More debug code
rolfbjarne Sep 17, 2024
6fe6167
HOME
rolfbjarne Sep 17, 2024
cb2cae4
[msbuild] Store DefiningProjectFullPath in a different metadata.
rolfbjarne Sep 17, 2024
da32824
wip
rolfbjarne Sep 17, 2024
933f1bc
wip
rolfbjarne Sep 17, 2024
6daa1d7
wip
rolfbjarne Sep 18, 2024
e27d89d
wip
rolfbjarne Sep 18, 2024
1d4a546
Provision simulator
rolfbjarne Sep 19, 2024
e45b7c9
Don't use warnings.
rolfbjarne Sep 19, 2024
527cfd9
hash it
rolfbjarne Sep 19, 2024
adbfc0a
Auto-format source code
Sep 19, 2024
1e4889d
try again
rolfbjarne Sep 19, 2024
7465fb6
debug spew
rolfbjarne Sep 20, 2024
5d4e4eb
Merge remote-tracking branch 'origin/main' into dev/rolf/msbuild-bund…
rolfbjarne Sep 20, 2024
d0743b4
Speed this up a bit.
rolfbjarne Sep 20, 2024
3222d66
fix build
rolfbjarne Sep 20, 2024
5a64682
[msbuild] Convert Ditto to a XamarinTask.
rolfbjarne Sep 20, 2024
195435a
Merge branch 'main' into dev/rolf/msbuild-bundle-original-resources-i…
rolfbjarne Sep 23, 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
8 changes: 4 additions & 4 deletions Make.config
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ 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_WATCH=1
INCLUDE_TVOS=1
INCLUDE_MACCATALYST=1
INCLUDE_MAC=
INCLUDE_WATCH=
INCLUDE_TVOS=
INCLUDE_MACCATALYST=
INCLUDE_DEVICE=1
INCLUDE_DOTNET_WATCHOS=
INCLUDE_XAMARIN_LEGACY=
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 @@ -226,6 +226,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.LogWarning ($"GetLogicalName ({projectDir}, {string.Join (";", prefixes)}, {item.ItemSpec}) => has LogicalName={logicalName.Replace ('\\', '/')} (original {logicalName})");
return logicalName.Replace ('\\', '/');

}
task?.Log.LogWarning ($"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.LogWarning ($"GetLogicalName ({projectDir}, {string.Join (";", prefixes)}, {item.ItemSpec}) => has LogicalName={vpath.Substring (matchlen)} with vpath {vpath} substring {matchlen}");
return vpath.Substring (matchlen);
}

task?.Log.LogWarning ($"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