Skip to content

Commit

Permalink
[msbuild] Add support for inlucing alternate app icons in the compile…
Browse files Browse the repository at this point in the history
…d asset catalog.
  • Loading branch information
rolfbjarne committed Oct 25, 2024
1 parent efb1b4a commit 9ad35cb
Show file tree
Hide file tree
Showing 431 changed files with 4,997 additions and 122 deletions.
21 changes: 21 additions & 0 deletions docs/build-apps/build-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ ms.date: 09/19/2024
Build items control how .NET for iOS, Mac Catalyst, macOS, and tvOS
application or library projects are built.

## AlternateAppIcon

The `AlternateAppIcon` item group can be used to specify alternate app icons.

The `Include` metadata must point to the filename of an `.appiconset` (for
iOS, macOS and Mac Catalyst) or `.brandassets` (for tvOS) image resource
inside an asset catalog.

Example:

```xml
<ItemGroup>
<!-- The value to put in here for the "Resources/MyImages.xcassets/MyAlternateAppIcon.appiconset" resource would be "MyAlternateAppIcon" -->
<AlternateAppIcon Include="MyAlternateAppIcon" />
</ItemGroup>
```

See also:
* The [AppIcon](build-properties.md#AppIcon) property.
* The [IncludeAllAppIcons](build-properties.md#AppIcon) property.

## PartialAppManifest

`PartialAppManifest` can be used to add additional partial app manifests that
Expand Down
41 changes: 41 additions & 0 deletions docs/build-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,53 @@ MSBuild properties control the behavior of the
They're specified within the project file, for example **MyApp.csproj**, within
an MSBuild PropertyGroup.

## AppIcon

The `AppIcon` item group can be used to specify an app icon for the app.

The value of the property must point to the filename of an `.appiconset` (for
iOS, macOS and Mac Catalyst) or `.brandassets` (for tvOS) image resource
inside an asset catalog.

Example:

```xml
<PropertyGroup>
<!-- The value to put in here for the "Resources/MyImages.xcassets/MyAppIcon.appiconset" resource would be "MyAppIcon" -->
<AppIcon>MyAppIcon</AppIcon>
</PropertyGroup>
```

See also:

* The [AlternateAppIcon](build-items.md#AlternateAppIcon) item group.
* The [IncludeAllAppIcons](#IncludeAllAppIcons) property.

## DittoPath

The full path to the `ditto` executable.

The default behavior is to use `/usr/bin/ditto`.

## IncludeAllAppIcons

Set the `IncludeAllAppIcons` property to true to automatically include all app
icons from all asset catalogs in the app.

Example:

```xml
<PropertyGroup>
<IncludeAllAppIcons>true</IncludeAllAppIcons>
</PropertyGroup>
```

See also:

* The [AlternateAppIcon](build-items.md#AlternateAppIcon) item group.
* The [AppIcon](#AppIcon) property.
>>>>>>> 085031cbc4 ([msbuild] Add support for inlucing alternate app icons in the compiled asset catalog.)
## MaciOSPrepareForBuildDependsOn

A semi-colon delimited property that can be used to extend the build process.
Expand Down
31 changes: 31 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,36 @@
<comment>SupportedOSPlatformVersion: don't translate (it's the name of an MSBuild property)</comment>
</data>

<data name="E7127" xml:space="preserve">
<value>Can't find the AlternateAppIcon '{0}' among the image resources.</value>
<comment>
* AlternateAppIcon: don't translate (it's the name of an MSBuild property)
</comment>
</data>

<data name="E7128" xml:space="preserve">
<value>The image resource '{0}' is specified as both 'AppIcon' and 'AlternateAppIcon'</value>
<comment>
* AppIcon: don't translate (it's the name of an MSBuild property)
* AlternateAppIcon: don't translate (it's the name of an MSBuild property)
</comment>
</data>

<data name="E7129" xml:space="preserve">
<value>Can't specify both 'XSAppIconAssets' in the Info.plist and 'AppIcon' in the project file. Please select one or the other.</value>
<comment>
* XSAppIconAssets: don't translate (it's the name of an MSBuild property)
* AppIcon: don't translate (it's the name of an MSBuild property)
</comment>
</data>

<data name="E7130" xml:space="preserve">
<value>Can't find the AppIcon '{0}' among the image resources.</value>
<comment>
* AppIcon: don't translate (it's the name of an MSBuild property)
</comment>
</data>

<data name="E7131" xml:space="preserve">
<value>The source '{0}' does not exist.</value>
<comment>{0}: path to a file or a directory</comment>
Expand All @@ -1589,6 +1619,7 @@
</comment>
</data>


<data name="XcodeBuild_CreateNativeRef" xml:space="preserve">
<value>Adding reference to Xcode project output: '{0}'. The '%(CreateNativeReference)' metadata can be set to 'false' to opt out of this behavior.</value>
<comment>
Expand Down
84 changes: 63 additions & 21 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@

namespace Xamarin.MacDev.Tasks {
public class ACTool : XcodeCompilerToolTask, ICancelableTask {
ITaskItem? partialAppManifest;
string? outputSpecs;
string? partialAppManifestPath;

#region Inputs

public string AccentColor { get; set; } = string.Empty;

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

// The name of an app icon
public string AppIcon { get; set; } = string.Empty;

public string DeviceModel { get; set; } = string.Empty;

public string DeviceOSVersion { get; set; } = string.Empty;
Expand All @@ -29,6 +34,8 @@ public class ACTool : XcodeCompilerToolTask, ICancelableTask {
[Required]
public ITaskItem [] ImageAssets { get; set; } = Array.Empty<ITaskItem> ();

public bool IncludeAllAppIcons { get; set; }

public bool IsWatchApp { get; set; }

[Required]
Expand All @@ -46,6 +53,10 @@ public class ACTool : XcodeCompilerToolTask, ICancelableTask {

#endregion

// ,appiconset on iOS, macOS and Mac Catalyst
// .brandassets on tvOS
HashSet<string> appIconsInAssets = new ();

protected override string DefaultBinDir {
get { return DeveloperRootBinDir; }
}
Expand All @@ -64,6 +75,11 @@ protected override void AppendCommandLineArguments (IDictionary<string, string?>
{
var assetDirs = new HashSet<string> (items.Select (x => BundleResource.GetVirtualProjectPath (ProjectDir, x, !string.IsNullOrEmpty (SessionId))));

if (!string.IsNullOrEmpty (XSAppIconAssets) && !string.IsNullOrEmpty (AppIcon)) {
Log.LogError (MSBStrings.E7129 /* Can't specify both 'XSAppIconAssets' in the Info.plist and 'AppIcon' in the project file. Please select one or the other. */);
return;
}

if (!string.IsNullOrEmpty (XSAppIconAssets)) {
int index = XSAppIconAssets.IndexOf (".xcassets" + Path.DirectorySeparatorChar, StringComparison.Ordinal);
string? assetDir = null;
Expand All @@ -75,13 +91,6 @@ protected override void AppendCommandLineArguments (IDictionary<string, string?>
if (assetDirs is not null && assetDir is not null && assetDirs.Contains (assetDir)) {
var assetName = Path.GetFileNameWithoutExtension (rpath);

if (PartialAppManifest is null && partialAppManifest is not null) {
args.Add ("--output-partial-info-plist");
args.AddQuoted (partialAppManifest.GetMetadata ("FullPath"));

PartialAppManifest = partialAppManifest;
}

args.Add ("--app-icon");
args.AddQuoted (assetName);

Expand All @@ -104,14 +113,6 @@ protected override void AppendCommandLineArguments (IDictionary<string, string?>

if (assetDirs is not null && assetDir is not null && assetDirs.Contains (assetDir)) {
var assetName = Path.GetFileNameWithoutExtension (rpath);

if (PartialAppManifest is null && partialAppManifest is not null) {
args.Add ("--output-partial-info-plist");
args.AddQuoted (partialAppManifest.GetMetadata ("FullPath"));

PartialAppManifest = partialAppManifest;
}

args.Add ("--launch-image");
args.AddQuoted (assetName);
}
Expand Down Expand Up @@ -147,12 +148,42 @@ protected override void AppendCommandLineArguments (IDictionary<string, string?>
foreach (var targetDevice in GetTargetDevices ())
args.Add ("--target-device", targetDevice);

args.Add ("--minimum-deployment-target", MinimumOSVersion);
if (!string.IsNullOrEmpty (MinimumOSVersion))
args.Add ("--minimum-deployment-target", MinimumOSVersion);

var platform = PlatformUtils.GetTargetPlatform (SdkPlatform, IsWatchApp);

if (platform is not null)
args.Add ("--platform", platform);

if (!string.IsNullOrEmpty (AppIcon)) {
if (!appIconsInAssets.Contains (AppIcon)) {
Log.LogError (MSBStrings.E7130 /* Can't find the AppIcon '{0}' among the image resources. */, AppIcon);
return;
}
args.Add ("--app-icon");
args.Add (AppIcon);
}

foreach (var alternate in AlternateAppIcons) {
var alternateAppIcon = alternate.ItemSpec!;
if (!appIconsInAssets.Contains (alternateAppIcon)) {
Log.LogError (MSBStrings.E7127 /* Can't find the AlternateAppIcon '{0}' among the image resources. */, alternateAppIcon);
return;
}
if (string.Equals (alternateAppIcon, AppIcon, StringComparison.OrdinalIgnoreCase)) {
Log.LogError (MSBStrings.E7128 /* The image resource '{0}' is specified as both 'AppIcon' and 'AlternateAppIcon' */, AppIcon);
return;
}
args.Add ("--alternate-app-icon");
args.Add (alternateAppIcon);
}

if (IncludeAllAppIcons)
args.Add ("--include-all-app-icons");

args.Add ("--output-partial-info-plist");
args.AddQuoted (Path.GetFullPath (partialAppManifestPath));
}

IEnumerable<ITaskItem> GetCompiledBundleResources (PDictionary output, string intermediateBundleDir)
Expand Down Expand Up @@ -309,6 +340,14 @@ public override bool Execute ()
var catalog = Path.GetDirectoryName (vpath);
path = Path.GetDirectoryName (path);

if (Platform == ApplePlatform.TVOS) {
if (path.EndsWith (".brandassets", StringComparison.OrdinalIgnoreCase))
appIconsInAssets.Add (Path.GetFileNameWithoutExtension (path));
} else {
if (path.EndsWith (".appiconset", StringComparison.OrdinalIgnoreCase))
appIconsInAssets.Add (Path.GetFileNameWithoutExtension (path));
}

// keep walking up the directory structure until we get to the .xcassets directory
while (!string.IsNullOrEmpty (catalog) && Path.GetExtension (catalog) != ".xcassets") {
catalog = Path.GetDirectoryName (catalog);
Expand Down Expand Up @@ -391,7 +430,8 @@ public override bool Execute ()
return !Log.HasLoggedErrors;
}

partialAppManifest = new TaskItem (Path.Combine (intermediate, "partial-info.plist"));
partialAppManifestPath = Path.Combine (intermediate, "partial-info.plist");
PartialAppManifest = new TaskItem (partialAppManifestPath);

if (specs.Count > 0) {
outputSpecs = Path.Combine (intermediate, "output-specifications.plist");
Expand All @@ -400,12 +440,14 @@ public override bool Execute ()

Directory.CreateDirectory (intermediateBundleDir);

// Note: Compile() will set the PartialAppManifest property if it is used...
if ((Compile (catalogs.ToArray (), intermediateBundleDir, manifest)) != 0)
return false;

if (PartialAppManifest is not null && !File.Exists (PartialAppManifest.GetMetadata ("FullPath")))
Log.LogError (MSBStrings.E0093, PartialAppManifest.GetMetadata ("FullPath"));
if (Log.HasLoggedErrors)
return false;

if (!File.Exists (Path.GetFullPath (partialAppManifestPath)))
Log.LogError (MSBStrings.E0093, Path.GetFullPath (partialAppManifestPath));

try {
var manifestOutput = PDictionary.FromFile (manifest.ItemSpec)!;
Expand Down
3 changes: 3 additions & 0 deletions msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -910,12 +910,15 @@ Copyright (C) 2018 Microsoft. All rights reserved.
ToolExe="$(ACToolExe)"
ToolPath="$(ACToolPath)"
AccentColor="$(AccentColor)"
AlternateAppIcons="@(AlternateAppIcon)"
AppIcon="$(AppIcon)"
BundleIdentifier="$(_BundleIdentifier)"
CLKComplicationGroup="$(_CLKComplicationGroup)"
DeviceModel="$(TargetDeviceModel)"
DeviceOSVersion="$(TargetDeviceOSVersion)"
EnableOnDemandResources="$(EnableOnDemandResources)"
ImageAssets="@(ImageAsset)"
IncludeAllAppIcons="$(IncludeAllAppIcons)"
MinimumOSVersion="$(_MinimumOSVersion)"
NSExtensionPointIdentifier="$(_NSExtensionPointIdentifier)"
OptimizePNGs="$(OptimizePNGs)"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"images" : [
{
"filename" : "Icon1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "Icon16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "Icon32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "Icon32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "Icon64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "Icon128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "Icon256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "Icon256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "Icon512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "Icon512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "Icon1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Icon1280x768.png",
"idiom" : "tv"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9ad35cb

Please sign in to comment.