Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions docs/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,31 @@ Only applicable to iOS and tvOS projects.

See [CreatePackage](#createpackage) for macOS and Mac Catalyst projects.

## BundleOriginalResources

This property determines whether resources are compiled before being embedded
into library projects, or if the original (uncompiled) version is embedded.

Historically resources have been compiled before being embedded into library
projects, but this requires having Xcode available, which has a few drawbacks:

* It slows down remote builds on Windows.
* It won't work when building locally on Windows, and neither on any other
platform except macOS.
* Resources are compiled using the current available Xcode, which may not have
the same features as a potentially newer Xcode available when the library in
question is consumed.
* It makes it impossible to have a whole-program view of all the resources
when building an app, which is necessary to detect clashing resources.

As such, we've added supported for embedding the original resources into
libraries. This will be opt-in in .NET 9, but opt-out starting in .NET 10.

Default value: `false` in .NET 9, `true` in .NET 10+.

Note: please file an issue if you find that you need to disable this feature,
as it's possible we'll remove the option to disable it at some point.

## CodesignAllocate

The path to the `codesign_allocate` tool.
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
8 changes: 8 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1654,4 +1654,12 @@
{1}: the exit code of a process
</comment>
</data>

<data name="E7135" xml:space="preserve">
<value>Unknown resource type: {1}.</value>
</data>

<data name="E7136" xml:space="preserve">
<value>Unknown resource type: {1}.</value>
</data>
</root>
73 changes: 43 additions & 30 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CollectBundleResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class CollectBundleResources : XamarinTask, ICancelableTask, IHasProjectD
[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 +70,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 (this, item);
// 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, 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 +95,51 @@ bool ExecuteImpl ()
bundleResources.Add (bundleResource);
}

bundleResources.AddRange (UnpackedResources);

BundleResourcesWithLogicalNames = bundleResources.ToArray ();

return !Log.HasLoggedErrors;
}

public static bool TryCreateItemWithLogicalName<T> (T task, ITaskItem item, [NotNullWhen (true)] out TaskItem? itemWithLogicalName) where T : Task, IHasProjectDir, IHasResourcePrefix, IHasSessionId
{
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, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.IO;
using System.Collections.Generic;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Xamarin.Localization.MSBuild;
using Xamarin.Messaging.Build.Client;

namespace Xamarin.MacDev.Tasks {
// This task will collect several item groups with various types of assets/resources,
// add/compute the LogicalName value for each of them, and then add them to the
// ItemsWithLogicalNames item group. The items in this item group will have the
// 'OriginalItemGroup' metadata set indicating where they came from.
public class CollectPackLibraryResources : XamarinTask, IHasProjectDir, IHasResourcePrefix {
#region Inputs

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

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

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

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

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

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

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

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

[Required]
public string ProjectDir { get; set; } = string.Empty;

[Required]
public string ResourcePrefix { get; set; } = string.Empty;

#endregion

#region Outputs

// These items will have the following metadata set:
// * LogicalName
// * OriginalItemGroup: the name of the originating item group
[Output]
public ITaskItem [] ItemsWithLogicalNames { get; set; } = Array.Empty<ITaskItem> ();

#endregion

public override bool Execute ()
{
var prefixes = BundleResource.SplitResourcePrefixes (ResourcePrefix);
var rv = new List<ITaskItem> ();

var resources = new [] {
new { Name = "AtlasTexture", Items = AtlasTextures },
new { Name = "BundleResource", Items = BundleResources },
new { Name = "Collada", Items = ColladaAssets },
new { Name = "CoreMLModel", Items = CoreMLModels },
new { Name = "ImageAsset", Items = ImageAssets },
new { Name = "InterfaceDefinition", Items = InterfaceDefinitions },
new { Name = "PartialAppManifest", Items = PartialAppManifests },
new { Name = "SceneKitAsset", Items = SceneKitAssets },
};

foreach (var kvp in resources) {
var itemName = kvp.Name;
var items = kvp.Items;

foreach (var item in items) {
if (!CollectBundleResources.TryCreateItemWithLogicalName (this, item, out var itemWithLogicalName))
continue;

itemWithLogicalName.SetMetadata ("OriginalItemGroup", itemName);
rv.Add (itemWithLogicalName);
}
}

ItemsWithLogicalNames = rv.ToArray ();

return !Log.HasLoggedErrors;
}
}
}
23 changes: 16 additions & 7 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CompileSceneKitAssets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ Task CopySceneKitAssets (string scnassets, string output, string intermediate)
return ExecuteAsync (GetFullPathToTool (), args, sdkDevPath: SdkDevPath, environment: environment, showErrorIfFailure: true);
}

static bool TryGetScnAssetsPath (string file, out string scnassets)
{
scnassets = file;
while (scnassets.Length > 0 && Path.GetExtension (scnassets).ToLowerInvariant () != ".scnassets")
scnassets = Path.GetDirectoryName (scnassets);
return scnassets.Length > 0;
}

public override bool Execute ()
{
if (ShouldExecuteRemotely ()) {
Expand All @@ -140,15 +148,9 @@ public override bool Execute ()
continue;

// get the .scnassets directory path
var scnassets = Path.GetDirectoryName (asset.ItemSpec);
while (scnassets.Length > 0 && Path.GetExtension (scnassets).ToLowerInvariant () != ".scnassets")
scnassets = Path.GetDirectoryName (scnassets);

if (scnassets.Length == 0)
if (!TryGetScnAssetsPath (asset.ItemSpec, out var scnassets))
continue;

asset.RemoveMetadata ("LogicalName");

var bundleName = BundleResource.GetLogicalName (this, asset);
var output = new TaskItem (Path.Combine (intermediate, bundleName));

Expand All @@ -159,6 +161,13 @@ public override bool Execute ()
// .. but we really want it to be for @scnassets, so set ItemSpec accordingly
scnassetsItem.ItemSpec = scnassets;

// .. and set LogicalName, the original one is for @asset
if (!TryGetScnAssetsPath (bundleName, out var logicalScnAssetsPath)) {
Log.LogError (null, null, null, asset.ItemSpec, MSBStrings.E7136 /* Unable to compute the path of the *.scnassets path from the item's LogicalName '{0}'. */ , bundleName);
continue;
}
scnassetsItem.SetMetadata ("LogicalName", logicalScnAssetsPath);

// .. and remove the @OriginalItemSpec which is for @asset
scnassetsItem.RemoveMetadata ("OriginalItemSpec");

Expand Down
11 changes: 0 additions & 11 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CreateEmbeddedResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,6 @@ public class CreateEmbeddedResources : XamarinTask {

public override bool Execute ()
{
if (ShouldExecuteRemotely ()) {
foreach (var bundleResource in this.BundleResources) {
var logicalName = bundleResource.GetMetadata ("LogicalName");

if (!string.IsNullOrEmpty (logicalName)) {
logicalName = logicalName.Replace ("\\", "/");
bundleResource.SetMetadata ("LogicalName", logicalName);
}
}
}

EmbeddedResources = new ITaskItem [BundleResources.Length];

for (int i = 0; i < BundleResources.Length; i++) {
Expand Down
Loading