Skip to content

.NET: Add AppBundleResourcePrefix property to unify resource prefix properties #22840

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
14 changes: 14 additions & 0 deletions docs/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ The full path to the `altool` tool.

The default behavior is to use `xcrun altool`.

## AppBundleResourcePrefix

The directory where resources are stored (this prefix will be removed when copying resources to the app bundle).

If not explicitly set, this property will inherit its value from the platform-specific resource prefix properties ([IPhoneResourcePrefix](#iphoneresourceprefix), [MonoMacResourcePrefix](#monomacresourceprefix), or [XamMacResourcePrefix](#xammacresourceprefix) depending on the platform).

Default: "Resources"

## AppBundleDir

The location of the built app bundle.
Expand Down Expand Up @@ -379,6 +387,8 @@ The directory where resources are stored (this prefix will be removed when copyi

Applicable to iOS, tvOS and Mac Catalyst projects.

Consider using the unified [AppBundleResourcePrefix](#appbundleresourceprefix) property instead.

See also [MonoMacResourcePrefix](#monomacresourceprefix) and [XamMacResourcePrefix](#xammacresourceprefix).

## IpaIncludeArtwork
Expand Down Expand Up @@ -560,6 +570,8 @@ The directory where resources are stored (this prefix will be removed when copyi

Only applicable to macOS projects.

Consider using the unified [AppBundleResourcePrefix](#appbundleresourceprefix) property instead.

See also [IPhoneResourcePrefix](#iphoneresourceprefix) and [XamMacResourcePrefix](#xammacresourceprefix).

## MtouchDebug
Expand Down Expand Up @@ -978,6 +990,8 @@ The directory where resources are stored (this prefix will be removed when copyi

Applicable to macOS projects.

Consider using the unified [AppBundleResourcePrefix](#appbundleresourceprefix) property instead.

See also [IPhoneResourcePrefix](#iphoneresourceprefix) and [MonoMacResourcePrefix](#monomacresourceprefix).

## ZipPath
Expand Down
2 changes: 1 addition & 1 deletion dotnet/targets/Microsoft.Sdk.DefaultItems.template.props
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
<IsDefaultItem>true</IsDefaultItem>
</SceneKitAsset>

<!-- Include everything in the project's Resources folder (as represented by the _ResourcePrefix property, which is set from the IPhoneResourcePrefix/XamMacResourcePrefix properties),
<!-- Include everything in the project's Resources folder (as represented by the _ResourcePrefix property, which is set from the AppBundleResourcePrefix or legacy IPhoneResourcePrefix/XamMacResourcePrefix properties),
except for files that are already in any of the other resource type item groups -->
<BundleResource Include="$(_ResourcePrefix)\**\*" Exclude="
$(DefaultItemExcludes);
Expand Down
11 changes: 9 additions & 2 deletions msbuild/Xamarin.Shared/Xamarin.Shared.props
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,19 @@ Copyright (C) 2020 Microsoft. All rights reserved.
<!-- Sometimes we've used different variable names for the same thing for Xamarin.iOS and Xamarin.Mac projects. Here we try to unify those variables -->
<PropertyGroup>
<!-- ResourcePrefix -->
<!-- First set platform-specific properties if not already set (for backwards compatibility) -->
<MonoMacResourcePrefix Condition="'$(_PlatformName)' == 'macOS' And '$(MonoMacResourcePrefix)' == ''">Resources</MonoMacResourcePrefix>
<XamMacResourcePrefix Condition="'$(_PlatformName)' == 'macOS' And '$(XamMacResourcePrefix)' == ''">$(MonoMacResourcePrefix)</XamMacResourcePrefix>
<XamMacResourcePrefix Condition="'$(_PlatformName)' == 'macOS' And '$(XamMacResourcePrefix)' == ''">Resources</XamMacResourcePrefix>
<IPhoneResourcePrefix Condition="'$(_PlatformName)' != 'macOS' And '$(IPhoneResourcePrefix)' == ''">Resources</IPhoneResourcePrefix>
<_ResourcePrefix Condition="'$(_PlatformName)' == 'macOS'">$(XamMacResourcePrefix)</_ResourcePrefix>
<_ResourcePrefix Condition="'$(_PlatformName)' != 'macOS'">$(IPhoneResourcePrefix)</_ResourcePrefix>

<!-- Set AppBundleResourcePrefix from platform-specific properties if not already set -->
<AppBundleResourcePrefix Condition="'$(AppBundleResourcePrefix)' == '' And '$(_PlatformName)' == 'macOS'">$(XamMacResourcePrefix)</AppBundleResourcePrefix>
<AppBundleResourcePrefix Condition="'$(AppBundleResourcePrefix)' == '' And '$(_PlatformName)' != 'macOS'">$(IPhoneResourcePrefix)</AppBundleResourcePrefix>
<AppBundleResourcePrefix Condition="'$(AppBundleResourcePrefix)' == ''">Resources</AppBundleResourcePrefix>

<!-- Set _ResourcePrefix from AppBundleResourcePrefix -->
<_ResourcePrefix>$(AppBundleResourcePrefix)</_ResourcePrefix>

<!-- SdkVersion -->
<_SdkVersion Condition="'$(_PlatformName)' == 'macOS'">$(MacOSXSdkVersion)</_SdkVersion>
Expand Down
38 changes: 37 additions & 1 deletion tests/common/DotNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,43 @@ public static ExecutionResult InstallTool (string tool, string path)

public static ExecutionResult RunTool (string tool, params string [] args) => ExecuteCommand (tool, args);

public static ExecutionResult ExecuteCommand (string exe, params string [] args)
public static string GetProperty (string projectPath, string propertyName, Dictionary<string, string>? properties = null)
{
if (!File.Exists (projectPath))
throw new FileNotFoundException ($"The project file '{projectPath}' does not exist.");

var args = new List<string> ();
args.Add ("build");
args.Add (projectPath);
args.Add ($"-getProperty:{propertyName}");
args.Add ("-nologo");
args.Add ("-verbosity:quiet");

if (properties is not null) {
foreach (var prop in properties) {
if (prop.Value.IndexOfAny (new char [] { ';' }) >= 0) {
args.Add ($"/p:{prop.Key}=\"{prop.Value}\"");
} else {
args.Add ($"/p:{prop.Key}={prop.Value}");
}
}
}

var env = new Dictionary<string, string?> ();
env ["MSBuildSDKsPath"] = null;
env ["MSBUILD_EXE_PATH"] = null;

var output = new StringBuilder ();
var rv = Execution.RunWithStringBuildersAsync (Executable, args, env, output, output, null, workingDirectory: Path.GetDirectoryName (projectPath), timeout: TimeSpan.FromMinutes (2)).Result;

if (rv.ExitCode != 0)
throw new Exception ($"Failed to get property '{propertyName}' from project '{projectPath}'. Exit code: {rv.ExitCode}. Output: {output}");

// Extract the property value from the output
return output.ToString ().Trim ();
}

public static ExecutionResult ExecuteCommand (string exe, params string [] args)
{
var env = new Dictionary<string, string?> ();
env ["MSBuildSDKsPath"] = null;
Expand Down
143 changes: 143 additions & 0 deletions tests/dotnet/UnitTests/ResourcePrefixTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;

namespace Xamarin.Tests
{
[TestFixture]
public class ResourcePrefixTest : TestBaseClass
{
[Test]
public void ResourcePrefix_DefaultValues ()
{
// Arrange
var platforms = Configuration.GetAllPlatforms ();

foreach (var platform in platforms)
{
// Act & Assert
var defaultValue = GetResourcePrefix (platform);
Assert.AreEqual ("Resources", defaultValue, $"Default value for {platform} should be 'Resources'");
}
}

[Test]
public void ResourcePrefix_AppBundleResourcePrefix ()
{
// Arrange
var platforms = Configuration.GetAllPlatforms ();
var customPrefix = "CustomResources";

foreach (var platform in platforms)
{
// Act
var value = GetResourcePrefix (platform, ("AppBundleResourcePrefix", customPrefix));

// Assert
Assert.AreEqual (customPrefix, value, $"{platform}: AppBundleResourcePrefix should be used");
}
}

[Test]
public void ResourcePrefix_PlatformSpecific ()
{
// Arrange
var customPrefix = "CustomResources";

// Act & Assert

// iOS and tvOS use IPhoneResourcePrefix
var iOSValue = GetResourcePrefix ("ios", ("IPhoneResourcePrefix", customPrefix));
Assert.AreEqual (customPrefix, iOSValue, "iOS should use IPhoneResourcePrefix");

var tvOSValue = GetResourcePrefix ("tvos", ("IPhoneResourcePrefix", customPrefix));
Assert.AreEqual (customPrefix, tvOSValue, "tvOS should use IPhoneResourcePrefix");

// Mac Catalyst uses IPhoneResourcePrefix
var macCatalystValue = GetResourcePrefix ("maccatalyst", ("IPhoneResourcePrefix", customPrefix));
Assert.AreEqual (customPrefix, macCatalystValue, "Mac Catalyst should use IPhoneResourcePrefix");

// macOS can use either XamMacResourcePrefix or MonoMacResourcePrefix
var macOSXamValue = GetResourcePrefix ("macos", ("XamMacResourcePrefix", customPrefix));
Assert.AreEqual (customPrefix, macOSXamValue, "macOS should use XamMacResourcePrefix");

var macOSMonoValue = GetResourcePrefix ("macos", ("MonoMacResourcePrefix", customPrefix));
Assert.AreEqual (customPrefix, macOSMonoValue, "macOS should use MonoMacResourcePrefix");
}

[Test]
public void ResourcePrefix_Precedence ()
{
// Arrange
var appBundlePrefix = "AppPrefix";
var platformPrefix = "PlatformPrefix";

// Act & Assert

// iOS - AppBundleResourcePrefix should take precedence over IPhoneResourcePrefix
var iOSValue = GetResourcePrefix ("ios",
("AppBundleResourcePrefix", appBundlePrefix),
("IPhoneResourcePrefix", platformPrefix));
Assert.AreEqual (appBundlePrefix, iOSValue, "iOS should prioritize AppBundleResourcePrefix over IPhoneResourcePrefix");

// tvOS - AppBundleResourcePrefix should take precedence over IPhoneResourcePrefix
var tvOSValue = GetResourcePrefix ("tvos",
("AppBundleResourcePrefix", appBundlePrefix),
("IPhoneResourcePrefix", platformPrefix));
Assert.AreEqual (appBundlePrefix, tvOSValue, "tvOS should prioritize AppBundleResourcePrefix over IPhoneResourcePrefix");

// Mac Catalyst - AppBundleResourcePrefix should take precedence over IPhoneResourcePrefix
var macCatalystValue = GetResourcePrefix ("maccatalyst",
("AppBundleResourcePrefix", appBundlePrefix),
("IPhoneResourcePrefix", platformPrefix));
Assert.AreEqual (appBundlePrefix, macCatalystValue, "Mac Catalyst should prioritize AppBundleResourcePrefix over IPhoneResourcePrefix");

// macOS - AppBundleResourcePrefix should take precedence over XamMacResourcePrefix
var macOSXamValue = GetResourcePrefix ("macos",
("AppBundleResourcePrefix", appBundlePrefix),
("XamMacResourcePrefix", platformPrefix));
Assert.AreEqual (appBundlePrefix, macOSXamValue, "macOS should prioritize AppBundleResourcePrefix over XamMacResourcePrefix");

// macOS - AppBundleResourcePrefix should take precedence over MonoMacResourcePrefix
var macOSMonoValue = GetResourcePrefix ("macos",
("AppBundleResourcePrefix", appBundlePrefix),
("MonoMacResourcePrefix", platformPrefix));
Assert.AreEqual (appBundlePrefix, macOSMonoValue, "macOS should prioritize AppBundleResourcePrefix over MonoMacResourcePrefix");
}

private string GetResourcePrefix (string platform, params (string Property, string Value)[] properties)
{
// Create a temporary test project
var testDirectory = CreateTemporaryTestDirectory ();
var projectPath = Path.Combine (testDirectory, "TestApp.csproj");

// Create project file with specified properties
File.WriteAllText (projectPath, GetTestProjectContent (platform, properties));

// Use dotnet build with getProperty to get _ResourcePrefix value
return DotNet.GetProperty (projectPath, "_ResourcePrefix");
}

private string GetTestProjectContent (string platform, params (string Property, string Value)[] properties)
{
// Create project property group with specified properties
var propertyGroup = "";
foreach (var (property, value) in properties)
{
propertyGroup += $" <{property}>{value}</{property}>\n";
}

// Generate the project file content
return $@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-{platform}</TargetFramework>
<OutputType>Exe</OutputType>
{propertyGroup}
</PropertyGroup>
</Project>";
}
}
}
Loading