From 7953e4f8b7e1ffb3c87077ce583a6533ac18e3cf Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 12 Jan 2024 10:04:56 +0000 Subject: [PATCH 01/16] Android AssetPack Implementation Google Android began supporting splitting up the app package into multiple packs with the introduction of the `aab` package format. This format allows the developer to split the app up into multiple `packs`. Each `pack` can be downloaded to the device either at install time or on demand. This allows application developers to save space and install time by only installing the required parts of the app initially. Then installing other `packs` as required. There are two types of `pack`. The first is a `Feature` pack, this type of pack contains code and other resources. Code in these types of `pack` can be launched via the `StartActivity` API call. At this time due to various constraints .NET Android cannot support `Feature` packs. The second type of `pack` is the `Asset` pack. This type of pack ONLY contains `AndroidAsset` items. It CANNOT contain any code or other resources. This type of `feature` pack can be installed at install-time, fast-follow or ondemand. It is most useful for apps which contain allot of `Assets`, such as Games or Multi Media applications. See the [documentation](https://developer.android.com/guide/playcore/asset-delivery) for details on how this all works. .NET Android does not have any official support for this type of pack. However a hack is available via the excellent @infinitespace-studios on [github](https://github.com/infinitespace-studios/MauiAndroidAssetPackExample). This hack allows developers to place additional assets in a special `NoTargets` project. This project is built just after the final `aab` is produced. It builds a zip file which is then added to the `@(Modules)` ItemGroup in the main application. This zip is then included into the final app as an additional feature. We want to provide our users the ability to use `Asset` packs without having to implement the hack provided by @infinitespace-studios. Using a separate project like in the hack is one way to go. It does have some issues though. 1. It is a `special` type of project. It requires a `global.json` which imports the `NoTargets` sdk. 2. There is no IDE support for building this type of project. Having the user go through a number of hoops to implement this for .NET Android or .net Maui is not ideal. We need a simpler method. The new idea is to make use of additional metadata on `AndroidAsset` Items to allow the build system to split up the assets into packs automatically. So it is proposed that we implement support for something like this ```xml ``` In this case the additional `AssetPack` attribute is used to tell the build system which pack to place this asset in. Since auto import of items is common now we need a way for a user to add this additional attribute to auto included items. Fortunately we are able to use the following. ```xml ``` This code uses the `Update` attribute to tell MSBuild that we are going to update a specific item. Note in the sample we do NOT need to include an `Update` for the `data.xml`, since this is auto imported it will still end up in the main feature in the aab. Additional attributes can be used to control what type of asset pack is produced. The only extra one supported at this time is `DeliveryType`, this can have a value of `InstallTime`, `FastFollow` or `OnDemand`. Additional attributes do not need to be included on ALL items. Any one will do, only the `AssetPack` attribute will be needed. See Google's [documentation](https://developer.android.com/guide/playcore/asset-delivery#asset-updates) for details on what each item does. ```xml ``` If the `AssetPack` attribute is not present, the default behavior will be to include the asset in the main application package. There are a few changes we need to make in order to support this feature. One of the issues we will hit is the build times when dealing with large assets. Current the assets which are to be included in the `aab` are COPIED into the `$(IntermediateOutputPath)assets` directory. This folder is then passed to `aapt2` for the build process. The new system adds a new directory `$(IntermediateOutputPath)assetpacks`. This directory would contain a subdirectory for each `pack` that the user wants to include. ```dotnetcli assetpacks/ assets1/ assets/ movie1.mp4 feature2/ assets/ movie2.mp4 ``` All the building of the `pack` zip file would take place in these subfolders. The name of the pack will be based on the main "packagename" with the asset pack name appended to the end. e.g `com.microsoft.assetpacksample.assets1`. During the build process we identify ALL the `AndroidAsset` items which define an `AssetPack` attribute. These files are then copied to the new `$(IntermediateOutputPath)assetpacks` directory rather than the existing `$(IntermediateOutputPath)assets` directory. This allows us to continue to support the normal `AndroidAsset` behavior while adding the new system. Once we have collected and copied all the assets we then use the new `GetAssetPacks` Task to figure out which asset packs we need to create. We then call the `CreateDynamicFeatureManifest` to create a required `AndroidManifest.xml` file for the asset pack. This file will end up in the same `$(IntermediateOutputPath)assetpacks` directory. We call this Task `CreateDynamicFeatureManifest` because it can be used to create any feature pack if and when we get to implement full feature packs. ```dotnetcli assetpacks/ assets1/ AndroidManifest.xml assets/ movie1.mp4 feature2/ AndroidManifest.xml assets/ movie2.mp4 ``` We can then call `aapt2` to build these packs into `.zip` files. A new task `Aapt2LinkAssetPack` takes care of this. This is a special version of `Aapt2Link` which implements linking for asset packs only. It also takes care of a few problems which `aapt2` introduces. For some reason the zip file that is created has the `AndroidManifest.xml` file in the wrong place. It creates it in the root of the zip file, but the `bundletool` expects it to be in a `manifest` directory. `bundletool` will error out if its not in the right place. So `Aapt2LinkAssetPack` takes care of this for us. It also removes a `resources.pb` which gets added. Again, `bundletool` will error if this file is in the zip file. Once the zip files have been created they are then added to the `AndroidAppBundleModules` ItemGroup. This will ensure that when the final `.aab` file is generated they are included as asset packs. --- Documentation/guides/AndroidAssetPacks.md | 176 +++++++++++++++ .../guides/building-apps/build-items.md | 32 +++ .../guides/building-apps/build-process.md | 1 + .../guides/building-apps/build-properties.md | 19 ++ Documentation/guides/messages/README.md | 1 + Documentation/guides/messages/xa0138.md | 13 ++ .../installers/create-installers.targets | 1 + .../Android/Xamarin.Android.Assets.targets | 147 +++++++++++++ .../Microsoft.Android.Sdk.BuildOrder.targets | 2 + .../Properties/Resources.resx | 4 + .../Tasks/Aapt2LinkAssetPack.cs | 75 +++++++ .../Tasks/AndroidComputeResPaths.cs | 8 + .../Tasks/CreateDynamicFeatureManifest.cs | 140 ++++++++++++ .../Tasks/GetAssetPacks.cs | 72 ++++++ .../Tasks/RemoveUnknownFiles.cs | 55 +++-- .../AssetPackTests.cs | 208 ++++++++++++++++++ .../Utilities/ZipArchiveEx.cs | 16 ++ .../Xamarin.Android.Build.Tasks.targets | 4 + .../Xamarin.Android.Common.targets | 43 +--- 19 files changed, 956 insertions(+), 61 deletions(-) create mode 100644 Documentation/guides/AndroidAssetPacks.md create mode 100644 Documentation/guides/messages/xa0138.md create mode 100644 src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md new file mode 100644 index 00000000000..21b427c05b9 --- /dev/null +++ b/Documentation/guides/AndroidAssetPacks.md @@ -0,0 +1,176 @@ +# Android Asset Packs + +Google Android began supporting splitting up the app package into multiple +packs with the introduction of the `aab` package format. This format allows +the developer to split the app up into multiple `packs`. Each `pack` can be +downloaded to the device either at install time or on demand. This allows +application developers to save space and install time by only installing +the required parts of the app initially. Then installing other `packs` +as required. + +There are two types of `pack`. The first is a `Feature` pack, this type +of pack contains code and other resources. Code in these types of `pack` +can be launched via the `StartActivity` API call. At this time due to +various constraints .NET Android cannot support `Feature` packs. + +The second type of `pack` is the `Asset` pack. This type of pack ONLY +contains `AndroidAsset` items. It CANNOT contain any code or other +resources. This type of `feature` pack can be installed at install-time, +fast-follow or ondemand. It is most useful for apps which contain allot +of `Assets`, such as Games or Multi Media applications. +See the [documentation](https://developer.android.com/guide/playcore/asset-delivery) for details on how this all works. +.NET Android does not have any official support for this type of pack. +However a hack is available via the excellent @infinitespace-studios on +[github](https://github.com/infinitespace-studios/MauiAndroidAssetPackExample). +This hack allows developers to place additional assets in a special +`NoTargets` project. This project is built just after the final `aab` is +produced. It builds a zip file which is then added to the `@(Modules)` +ItemGroup in the main application. This zip is then included into the +final app as an additional feature. + +## Asset Pack Specification + +We want to provide our users the ability to use `Asset` packs without +having to implement the hack provided by @infinitespace-studios. Using +a separate project like in the hack is one way to go. It does have some +issues though. + +1. It is a `special` type of project. It requires a `global.json` which imports the + `NoTargets` sdk. +2. There is no IDE support for building this type of project. + +Having the user go through a number of hoops to implement this for +.NET Android or .net Maui is not ideal. We need a simpler method. + +The new idea is to make use of additional metadata on `AndroidAsset` +Items to allow the build system to split up the assets into packs +automatically. So it is proposed that we implement support for something +like this + +```xml + + + + + +``` + +In this case the additional `AssetPack` attribute is used to tell the +build system which pack to place this asset in. Since auto import of items +is common now we need a way for a user to add this additional attribute +to auto included items. Fortunately we are able to use the following. + +```xml + + + + + +``` + +This code uses the `Update` attribute to tell MSBuild that we are going +to update a specific item. Note in the sample we do NOT need to include +an `Update` for the `data.xml`, since this is auto imported it will still +end up in the main feature in the aab. + +Additional attributes can be used to control what type of asset pack is +produced. The only extra one supported at this time is `DeliveryType`, +this can have a value of `InstallTime`, `FastFollow` or `OnDemand`. +Additional attributes do not need to be included on ALL items. Any one +will do, only the `AssetPack` attribute will be needed. +See Google's [documentation](https://developer.android.com/guide/playcore/asset-delivery#asset-updates) for details on what each item does. + +```xml + + + + + +``` + +If the `AssetPack` attribute is not present, the default behavior will +be to include the asset in the main application package. + +If however you have a large number of assets it might be more efficient to make use of the `base` asset pack setting. In this scenario you update ALL assets to be in a single asset pack then use the `AssetPack="base"` metadata to declare which specific assets end up in the base aab file. With this you can use wildcards to move most assets into the asset pack. + +```xml + + + + + +``` + +In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, but ALL the other assets +will end up in the `assets1` asset pack. + +## Implementation Details + +There are a few changes we need to make in order to support this feature. +One of the issues we will hit is the build times when dealing with large assets. +Current the assets which are to be included in the `aab` are COPIED +into the `$(IntermediateOutputPath)assets` directory. This folder is +then passed to `aapt2` for the build process. + +The new system adds a new directory `$(IntermediateOutputPath)assetpacks`. +This directory would contain a subdirectory for each `pack` that the +user wants to include. + +```dotnetcli +assetpacks/ + assets1/ + assets/ + movie1.mp4 + feature2/ + assets/ + movie2.mp4 +``` + +All the building of the `pack` zip file would take place in these subfolders. +The name of the pack will be based on the main "packagename" with the asset pack +name appended to the end. e.g `com.microsoft.assetpacksample.assets1`. + +During the build process we identify ALL the `AndroidAsset` items which +define an `AssetPack` attribute. These files are then copied to the +new `$(IntermediateOutputPath)assetpacks` directory rather than the +existing `$(IntermediateOutputPath)assets` directory. This allows us to +continue to support the normal `AndroidAsset` behavior while adding the +new system. + +Once we have collected and copied all the assets we then use the new +`GetAssetPacks` Task to figure out which asset packs we need to create. +We then call the `CreateDynamicFeatureManifest` to create a required +`AndroidManifest.xml` file for the asset pack. This file will end +up in the same `$(IntermediateOutputPath)assetpacks` directory. +We call this Task `CreateDynamicFeatureManifest` because it can be used +to create any feature pack if and when we get to implement full feature +packs. + +```dotnetcli +assetpacks/ + assets1/ + AndroidManifest.xml + assets/ + movie1.mp4 + feature2/ + AndroidManifest.xml + assets/ + movie2.mp4 +``` + +We can then call `aapt2` to build these packs into `.zip` files. A new +task `Aapt2LinkAssetPack` takes care of this. This is a special version +of `Aapt2Link` which implements linking for asset packs only. +It also takes care of a few problems which `aapt2` introduces. For some +reason the zip file that is created has the `AndroidManifest.xml` file +in the wrong place. It creates it in the root of the zip file, but the +`bundletool` expects it to be in a `manifest` directory. +`bundletool` will error out if its not in the right place. +So `Aapt2LinkAssetPack` takes care of this for us. It also removes a +`resources.pb` which gets added. Again, `bundletool` will error if this +file is in the zip file. + +Once the zip files have been created they are then added to the +`AndroidAppBundleModules` ItemGroup. This will ensure that when the +final `.aab` file is generated they are included as asset packs. + diff --git a/Documentation/guides/building-apps/build-items.md b/Documentation/guides/building-apps/build-items.md index cd78fd4fa02..77b8410d0ce 100644 --- a/Documentation/guides/building-apps/build-items.md +++ b/Documentation/guides/building-apps/build-items.md @@ -19,6 +19,38 @@ or library project is built. Supports [Android Assets](https://developer.android.com/guide/topics/resources/providing-resources#OriginalFiles), files that would be included in the `assets` folder in a Java Android project. +The `AndroidAsset` ItemGroup also supports additional metadata for generating [Asset Packs](https://developer.android.com/guide/playcore/asset-delivery). Adding the `AssetPack` attribute to and `AndroidAsset` will automatically generate an asset pack of that name. This feature is only supported when using the `.aab` +`AndroidPackageFormat`. The following example will place `movie2.mp4` and `movie3.mp4` in separate asset packs. + +```xml + + + + + +``` + +This feature can be used to include large files in your application which would normally exceed the max +package size limits of Google Play. + +If you have a large number of assets it might be more efficient to make use of the `base` asset pack. +In this scenario you update ALL assets to be in a single asset pack then use the `AssetPack="base"` metadata +to declare which specific assets end up in the base aab file. With this you can use wildcards to move most +assets into the asset pack. + +```xml + + + + + +``` + +In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, but ALL the other assets +will end up in the `assets1` asset pack. + +The additional metadata is only supported on .NET Android 9 and above. + ## AndroidAarLibrary The Build action of `AndroidAarLibrary` should be used to directly diff --git a/Documentation/guides/building-apps/build-process.md b/Documentation/guides/building-apps/build-process.md index 42e4837ed9a..0ec934e1610 100644 --- a/Documentation/guides/building-apps/build-process.md +++ b/Documentation/guides/building-apps/build-process.md @@ -195,6 +195,7 @@ Extension points include: - [`$(AfterGenerateAndroidManifest)](~/android/deploy-test/building-apps/build-properties.md#aftergenerateandroidmanifest) - [`$(BeforeGenerateAndroidManifest)](~/android/deploy-test/building-apps/build-properties.md#beforegenerateandroidmanifest) +- [`$(BeforeBuildAndroidAssetPacks)`](~/android/deploy-test/building-apps/build-properties.md#beforebuildandroidassetpacks) A word of caution about extending the build process: If not written correctly, build extensions can affect your build diff --git a/Documentation/guides/building-apps/build-properties.md b/Documentation/guides/building-apps/build-properties.md index cd29dca973e..f341870af60 100644 --- a/Documentation/guides/building-apps/build-properties.md +++ b/Documentation/guides/building-apps/build-properties.md @@ -832,6 +832,18 @@ APK root directory. The format of the path is `lib\ARCH\wrap.sh` where + `x86_64` + `x86` +## AndroidIncludeAssetPacksInPackage + +This property controls if an Asset Packs build automatically are auto +included in the final `.aab` file. It will default to `true`. + +In certain cases the user might want to release an interim release. In +these cases the user does not NEED to update the asset pack. Especially +if the contents of the asset pack have not changed. This property allows +the user to skip the asset packs if they are not required. + +Added in .NET 9 + ## AndroidInstallJavaDependencies The default value is `true` for command line builds. When set to `true`, enables @@ -1674,6 +1686,13 @@ as support for `$(AotAssemblies)` will be removed in a future release. Extra options to pass to `aprofutil`. +## BeforeBuildAndroidAssetPacks + +MSBuild Targets listed in this +property will run directly before `_BuildAssetPacks`. + +Added in .NET 9 + ## BeforeGenerateAndroidManifest MSBuild Targets listed in this diff --git a/Documentation/guides/messages/README.md b/Documentation/guides/messages/README.md index bab9d5dc5d0..e137dd0b20d 100644 --- a/Documentation/guides/messages/README.md +++ b/Documentation/guides/messages/README.md @@ -100,6 +100,7 @@ package from all the users on device and try again. If that does not work you ca Fast Deployment is not currently supported on this device. Please file an issue with the exact error message using the 'Help->Send Feedback->Report a Problem' menu item in Visual Studio or 'Help->Report a Problem' in Visual Studio for Mac. ++ [XA0138](xa0138.md): @(AndroidAsset) build action does not support 'AssetPack' Metadata in Library Projects. ## XA1xxx: Project related diff --git a/Documentation/guides/messages/xa0138.md b/Documentation/guides/messages/xa0138.md new file mode 100644 index 00000000000..23249009439 --- /dev/null +++ b/Documentation/guides/messages/xa0138.md @@ -0,0 +1,13 @@ +title: Xamarin.Android error XA0138 +description: XA0138 error code +ms.date: 02/05/2024 +--- +# Xamarin.Android error XA0138 + +## Issue + +@(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects. + +## Solution + +Remove the 'AssetPack' or 'DeliveryType' Metadata from your `AndroidAsset` build Items. \ No newline at end of file diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index dbd3982b5bf..85c8b990475 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -136,6 +136,7 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Aapt2.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Analysis.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Application.targets" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Assets.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.ClassParse.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.Core.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.Maven.targets" /> diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets new file mode 100644 index 00000000000..f53313816e9 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets @@ -0,0 +1,147 @@ + + + + + + + + + + + + + $(IntermediateOutputPath)assets\ + $(IntermediateOutputPath)assetpacks + Assets + true + + + + + UpdateAndroidAssets + ;_CalculateAssetsWithAssetPackMetaData + ;_CalculateAssetPacks + ;$(BeforeBuildAndroidAssetPacks) + ;_CreateAssetPackManifests + + + + + + + + + + + + + + + + + <_AssetDirectories Include="$(MonoAndroidAssetsDirIntermediate)" /> + <_AssetDirectories Include="@(_AssetPacks->'%(AssetPackDirectory)')" /> + + + + + + + + + + + + + <_AssetsWithAssetPackMetaData Include="@(AndroidAsset)" Condition=" '%(AndroidAsset.AssetPack)' != '' " /> + + + + + + + + + + + <_AssetPacks Include="@(_AndroidAsset)"> + $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack)\assets + $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack).zip + $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack)\AndroidManifest.xml + InstallTime + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets index 19d0c698d1f..838db26e7e1 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets @@ -76,6 +76,7 @@ properties that determine build ordering. _LintChecks; _IncludeNativeSystemLibraries; _CheckGoogleSdkRequirements; + _BuildAssetPacks; @@ -91,6 +92,7 @@ properties that determine build ordering. _AddAndroidDefines; _CheckForContent; _CheckForObsoleteFrameworkAssemblies; + _CalculateAssetsWithAssetPackMetaData; _RemoveLegacyDesigner; _ValidateAndroidPackageProperties; AddLibraryJarsToBind; diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index a1a45e18ff3..75b410c61a4 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -329,6 +329,10 @@ If this file comes from a NuGet package, update to a newer version of the NuGet The capitalized word "Portable" that appears earlier in the message is plain text and should be translated, but the lowercase word "portable" later in the message is a literal value and should not be translated. {0} - The file name of a deprecated symbol file + + @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects. + + There was a problem parsing {0}. This is likely due to incomplete or invalid XML. Exception: {1} {0} - The file name diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs new file mode 100644 index 00000000000..1370a7a6631 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using Xamarin.Android.Tools; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks { + + public class Aapt2LinkAssetPack : Aapt2 { + public override string TaskPrefix => "A2LAP"; + + [Required] + public ITaskItem Manifest { get; set; } + + [Required] + public ITaskItem[] AssetDirectories { get; set; } + + [Required] + public string PackageName { get; set; } + + [Required] + public ITaskItem OutputArchive { get; set; } + + public string OutputFormat { get; set; } = "proto"; + + protected override int GetRequiredDaemonInstances () + { + return Math.Min (1, DaemonMaxInstanceCount); + } + + public async override System.Threading.Tasks.Task RunTaskAsync () + { + RunAapt (GenerateCommandLineCommands (Manifest, OutputArchive), OutputArchive.ItemSpec); + ProcessOutput (); + if (OutputFormat == "proto" && File.Exists (OutputArchive.ItemSpec)) { + // move the manifest to the right place. + using (var zip = new ZipArchiveEx (OutputArchive.ItemSpec, File.Exists (OutputArchive.ItemSpec) ? FileMode.Open : FileMode.Create)) { + zip.MoveEntry ("AndroidManifest.xml", "manifest/AndroidManifest.xml"); + zip.Archive.DeleteEntry ("resources.pb"); + } + } + } + + protected string[] GenerateCommandLineCommands (ITaskItem manifest, ITaskItem output) + { + //link --manifest AndroidManifest.xml --proto-format --custom-package $(Package) -A $(AssetsDirectory) -o $(_TempOutputFile) + List cmd = new List (); + cmd.Add ("link"); + if (MonoAndroidHelper.LogInternalExceptions) + cmd.Add ("-v"); + cmd.Add ("--manifest"); + cmd.Add (GetFullPath (manifest.ItemSpec)); + if (OutputFormat == "proto") { + cmd.Add ("--proto-format"); + } + cmd.Add ("--custom-package"); + cmd.Add (PackageName); + foreach (var assetDirectory in AssetDirectories) { + cmd.Add ("-A"); + cmd.Add (GetFullPath (assetDirectory.ItemSpec)); + } + cmd.Add ($"-o"); + cmd.Add (GetFullPath (output.ItemSpec)); + return cmd.ToArray (); + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs index 96a3ccadbe9..f86ac39cf40 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs @@ -45,6 +45,8 @@ public class AndroidComputeResPaths : AndroidTask [Required] public string IntermediateDir { get; set; } + public string AssetPackIntermediateDir { get; set; } + public string Prefixes { get; set; } public bool LowercaseFilenames { get; set; } @@ -83,6 +85,7 @@ public override bool RunTask () continue; //compute the target path string rel; + var assetPack = item.GetMetadata ("AssetPack"); var logicalName = item.GetMetadata ("LogicalName").Replace ('\\', Path.DirectorySeparatorChar); if (item.GetMetadata ("IsWearApplicationResource") == "True") { rel = item.ItemSpec.Substring (IntermediateDir.Length); @@ -126,6 +129,11 @@ public override bool RunTask () } string dest = Path.GetFullPath (Path.Combine (IntermediateDir, baseFileName)); string intermediateDirFullPath = Path.GetFullPath (IntermediateDir); + if (!string.IsNullOrEmpty (assetPack) && (string.Compare (assetPack, "base", StringComparison.OrdinalIgnoreCase) != 0) && !string.IsNullOrEmpty (AssetPackIntermediateDir)) { + dest = Path.GetFullPath (Path.Combine (AssetPackIntermediateDir, assetPack, "assets", baseFileName)); + intermediateDirFullPath = Path.GetFullPath (AssetPackIntermediateDir); + } + // if the path ends up "outside" of our target intermediate directory, just use the filename if (String.Compare (intermediateDirFullPath, 0, dest, 0, intermediateDirFullPath.Length, StringComparison.OrdinalIgnoreCase) != 0) { dest = Path.GetFullPath (Path.Combine (IntermediateDir, Path.GetFileName (baseFileName))); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs new file mode 100644 index 00000000000..e999f588782 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; + +using Xamarin.Android.Tools; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + public class CreateDynamicFeatureManifest : AndroidTask + { + readonly XNamespace androidNS = "http://schemas.android.com/apk/res/android"; + readonly XNamespace distNS = "http://schemas.android.com/apk/distribution"; + readonly XNamespace toolsNS = "http://schemas.android.com/tools"; + + public override string TaskPrefix => "CDFM"; + + [Required] + public string FeatureSplitName { get; set; } + + [Required] + public string FeatureDeliveryType { get; set; } + + [Required] + public string FeatureType { get; set; } + + [Required] + public string PackageName { get; set; } + + [Required] + public ITaskItem OutputFile { get; set; } + + public string FeatureTitleResource { get; set; } + + public string MinSdkVersion { get; set; } + + public string TargetSdkVersion { get; set; } + + public bool IsFeatureSplit { get; set; } = false; + public bool IsInstant { get; set; } = false; + public bool HasCode { get; set; } = false; + + public override bool RunTask () + { + XDocument doc = new XDocument (); + switch (FeatureType) { + case "AssetPack": + GenerateAssetPackManifest (doc); + break; + default: + GenerateFeatureManifest (doc); + break; + } + doc.SaveIfChanged (OutputFile.ItemSpec); + return !Log.HasLoggedErrors; + } + + void GenerateFeatureManifest (XDocument doc) { + XAttribute featureTitleResource = null; + if (!string.IsNullOrEmpty (FeatureTitleResource)) + featureTitleResource = new XAttribute (distNS + "title", FeatureTitleResource); + XElement usesSdk = new XElement ("uses-sdk"); + if (!string.IsNullOrEmpty (MinSdkVersion)) + usesSdk.Add (new XAttribute (androidNS + "minSdkVersion", MinSdkVersion)); + if (!string.IsNullOrEmpty (MinSdkVersion)) + usesSdk.Add (new XAttribute (androidNS + "targetSdkVersion", TargetSdkVersion)); + doc.Add (new XElement ("manifest", + new XAttribute(XNamespace.Xmlns + "android", androidNS), + new XAttribute(XNamespace.Xmlns + "tools", toolsNS), + new XAttribute(XNamespace.Xmlns + "dist", distNS), + new XAttribute (androidNS + "versionCode", "1"), + new XAttribute (androidNS + "versionName", "1.0"), + new XAttribute ("package", PackageName), + new XAttribute ("featureSplit", FeatureSplitName), + new XAttribute (androidNS + "isFeatureSplit", IsFeatureSplit), + new XElement (distNS + "module", + featureTitleResource, + new XAttribute (distNS + "instant", IsInstant), + new XElement (distNS + "delivery", + GetDistribution () + ), + new XElement (distNS + "fusing", + new XAttribute (distNS + "include", true) + ) + ), + usesSdk, + new XElement ("application", + new XAttribute (androidNS + "hasCode", HasCode), + new XAttribute (toolsNS + "replace", "android:hasCode") + ) + ) + ); + } + + void GenerateAssetPackManifest (XDocument doc) { + doc.Add (new XElement ("manifest", + new XAttribute (XNamespace.Xmlns + "android", androidNS), + new XAttribute (XNamespace.Xmlns + "tools", toolsNS), + new XAttribute (XNamespace.Xmlns + "dist", distNS), + new XAttribute ("package", PackageName), + new XAttribute ("split", FeatureSplitName), + new XElement (distNS + "module", + new XAttribute (distNS + "type", "asset-pack"), + new XElement (distNS + "delivery", + GetDistribution () + ), + new XElement (distNS + "fusing", + new XAttribute (distNS + "include", true) + ) + ) + ) + ); + } + + XElement GetDistribution () + { + XElement distribution; + switch (FeatureDeliveryType) + { + case "OnDemand": + distribution = new XElement (distNS + "on-demand"); + break; + case "FastFollow": + distribution = new XElement (distNS + "fast-follow"); + break; + case "InstallTime": + default: + distribution = new XElement (distNS + "install-time"); + break; + } + return distribution; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs new file mode 100644 index 00000000000..09de3794ece --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + // We have a list of files, we want to get the + // ones that actually exist on disk. + public class GetAssetPacks : AndroidTask + { + public override string TaskPrefix => "GAP"; + + [Required] + public ITaskItem[] Assets { get; set; } + + [Required] + public ITaskItem IntermediateDir { get; set; } + + public string[] MetadataToCopy { get; set; } = { "DeliveryType" }; + + [Output] + public ITaskItem[] AssetPacks { get; set; } + + public override bool RunTask () + { + Dictionary assetPacks = new Dictionary (); + Dictionary> files = new Dictionary> (); + foreach (var asset in Assets) + { + var assetPack = asset.GetMetadata ("AssetPack"); + if (string.IsNullOrEmpty (assetPack) || string.Compare (assetPack, "base", StringComparison.OrdinalIgnoreCase) == 0) + continue; + if (!assetPacks.TryGetValue (assetPack, out ITaskItem item)) + { + item = new TaskItem (assetPack); + item.SetMetadata ("AssetPack", assetPack); + item.SetMetadata ("AssetPackCacheFile", Path.Combine (IntermediateDir.ItemSpec, assetPack, "assetpack.cache")); + assetPacks[assetPack] = item; + } + foreach (var metadata in MetadataToCopy) + if (string.IsNullOrEmpty (item.GetMetadata (metadata))) + item.SetMetadata (metadata, asset.GetMetadata (metadata)); + if (!files.ContainsKey (assetPack)) + files[assetPack] = new List (); + files[assetPack].Add (asset.ItemSpec); + } + + foreach (var kvp in assetPacks) { + // write out the file cache list + // write out the metadata as well. + ITaskItem item = kvp.Value; + var cacheFile = kvp.Value.GetMetadata ("AssetPackCacheFile"); + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { + foreach (var file in files [kvp.Key]) + sw.WriteLine ($"{file}:{File.GetLastWriteTimeUtc (file)}"); + sw.WriteLine (item.GetMetadata ("DeliveryType") ?? "InstallTime"); + sw.Flush (); + Files.CopyIfStreamChanged (sw.BaseStream, cacheFile); + } + } + + AssetPacks = assetPacks.Values.ToArray(); + + return true; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs index c6a42050a3c..157cc512363 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs @@ -19,10 +19,12 @@ public class RemoveUnknownFiles : AndroidTask public ITaskItem[] Files { get; set; } [Required] - public string Directory { get; set; } + public string[] Directories { get; set; } public bool RemoveDirectories { get; set; } + public string FileType { get; set; } = "AndroidResource"; + [Output] public ITaskItem[] RemovedFiles { get; set; } @@ -31,8 +33,6 @@ public class RemoveUnknownFiles : AndroidTask public override bool RunTask () { - var absDir = Path.GetFullPath (Directory); - HashSet knownFiles; List removedFiles = new List (); List removedDirectories = new List (); @@ -43,27 +43,38 @@ public override bool RunTask () else knownFiles = new HashSet (Files.Select (f => f.GetMetadata ("FullPath"))); - var files = System.IO.Directory.GetFiles (absDir, "*", SearchOption.AllDirectories); - foreach (string f in files) - if (!knownFiles.Contains (f)) { - Log.LogDebugMessage ("Deleting File {0}", f); - var item = new TaskItem (f.Replace (absDir, "res" + Path.DirectorySeparatorChar)); - removedFiles.Add (item); - Microsoft.Android.Build.Tasks.Files.SetWriteable (f); - File.Delete (f); - } - - if (RemoveDirectories) { - var knownDirs = new HashSet (knownFiles.Select (d => Path.GetDirectoryName (d))); - var dirs = System.IO.Directory.GetDirectories (absDir, "*", SearchOption.AllDirectories); + var root = "res"; + if (FileType == "AndroidAsset") + root = "assets"; - foreach (string d in dirs.OrderByDescending (s => s.Length)) - if (!knownDirs.Contains (d) && IsDirectoryEmpty (d)) { - Log.LogDebugMessage ("Deleting Directory {0}", d); - removedDirectories.Add (new TaskItem(d)); - Microsoft.Android.Build.Tasks.Files.SetDirectoryWriteable (d); - System.IO.Directory.Delete (d); + foreach (var directory in Directories) { + var absDir = Path.GetFullPath (directory); + if (!System.IO.Directory.Exists (absDir)) { + Log.LogDebugMessage ("Skipping Directory {0}. It does not exists yet.", directory); + continue; + } + var files = System.IO.Directory.GetFiles (absDir, "*", SearchOption.AllDirectories); + foreach (string f in files) + if (!knownFiles.Contains (f)) { + Log.LogDebugMessage ("Deleting File {0}", f); + var item = new TaskItem (f.Replace (absDir, root + Path.DirectorySeparatorChar)); + removedFiles.Add (item); + Microsoft.Android.Build.Tasks.Files.SetWriteable (f); + File.Delete (f); } + + if (RemoveDirectories) { + var knownDirs = new HashSet (knownFiles.Select (d => Path.GetDirectoryName (d))); + var dirs = System.IO.Directory.GetDirectories (absDir, "*", SearchOption.AllDirectories); + + foreach (string d in dirs.OrderByDescending (s => s.Length)) + if (!knownDirs.Contains (d) && IsDirectoryEmpty (d)) { + Log.LogDebugMessage ("Deleting Directory {0}", d); + removedDirectories.Add (new TaskItem(d)); + Microsoft.Android.Build.Tasks.Files.SetDirectoryWriteable (d); + System.IO.Directory.Delete (d); + } + } } RemovedFiles = removedFiles.ToArray (); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs new file mode 100644 index 00000000000..60e1136bacd --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs @@ -0,0 +1,208 @@ +using System; +using NUnit.Framework; +using System.IO; +using System.Text; +using Xamarin.ProjectTools; + +namespace Xamarin.Android.Build.Tests +{ + [Category ("Node-3")] + [Parallelizable (ParallelScope.Children)] + public class AssetPackTests : BaseTest + { + [Test] + [Category ("SmokeTests")] + public void BuildLibraryWithAssetPack ([Values (true, false)] bool isRelease) + { + var path = Path.Combine ("temp", TestName); + var lib = new XamarinAndroidLibraryProject { + IsRelease = isRelease, + OtherBuildItems = { + new AndroidItem.AndroidAsset ("Assets\\asset1.txt") { + TextContent = () => "Asset1", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack1", + }, + } + }; + using (var builder = CreateDllBuilder (Path.Combine (path, lib.ProjectName))) { + builder.ThrowOnBuildFailure = false; + Assert.IsFalse (builder.Build (lib), $"{lib.ProjectName} should fail."); + StringAssertEx.Contains ("error XA0138: @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects.", builder.LastBuildOutput, + "Build Output did not contain error XA0138'."); + } + } + + [Test] + [Category ("SmokeTests")] + public void BuildApplicationWithAssetPackOutsideProjectDirectory ([Values (true, false)] bool isRelease) + { + var path = Path.Combine ("temp", TestName); + var app = new XamarinAndroidApplicationProject { + ProjectName = "MyApp", + IsRelease = isRelease, + OtherBuildItems = { + new AndroidItem.AndroidAsset ("..\\Assets\\asset1.txt") { + TextContent = () => "Asset1", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack1;Link=Assets\\asset1.txt", + }, + new AndroidItem.AndroidAsset ("..\\Assets\\asset2.txt") { + TextContent = () => "Asset2", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack1;Link=Assets\\asset2.txt", + }, + new AndroidItem.AndroidAsset ("..\\Assets\\SubDirectory\\asset3.txt") { + TextContent = () => "Asset2", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack1;Link=Assets\\SubDirectory\\asset3.txt", + }, + } + }; + app.SetProperty ("AndroidPackageFormat", "aab"); + using (var appBuilder = CreateApkBuilder (Path.Combine (path, app.ProjectName))) { + Assert.IsTrue (appBuilder.Build (app), $"{app.ProjectName} should succeed"); + // Check the final aab has the required feature files in it. + var aab = Path.Combine (Root, appBuilder.ProjectDirectory, + app.OutputPath, $"{app.PackageName}.aab"); + using (var zip = ZipHelper.OpenZip (aab)) { + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset1.txt"), "aab should not contain base/assets/asset1.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset2.txt"), "aab should not contain base/assets/asset2.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/SubDirectory/asset3.txt"), "aab should not contain base/assets/SubDirectory/asset3.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets/asset1.txt"), "aab should contain assetpack1/assets/asset1.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets/asset2.txt"), "aab should contain assetpack1/assets/asset2.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets/SubDirectory/asset3.txt"), "aab should contain assetpack1/assets/SubDirectory/asset3.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets.pb"), "aab should contain assetpack1/assets.pb"); + Assert.IsFalse (zip.ContainsEntry ("assetpack1/resources.pb"), "aab should not contain assetpack1/resources.pb"); + } + } + } + + [Test] + [Category ("SmokeTests")] + public void BuildApplicationWithAssetPackOverrides ([Values (true, false)] bool isRelease) + { + var path = Path.Combine ("temp", TestName); + var app = new XamarinAndroidApplicationProject { + ProjectName = "MyApp", + IsRelease = isRelease, + OtherBuildItems = { + new AndroidItem.AndroidAsset ("Assets\\asset1.txt") { + TextContent = () => "Asset1", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack1", + }, + new AndroidItem.AndroidAsset ("Assets\\asset2.txt") { + TextContent = () => "Asset2", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=base", + }, + } + }; + app.SetProperty ("AndroidPackageFormat", "aab"); + using (var appBuilder = CreateApkBuilder (Path.Combine (path, app.ProjectName))) { + Assert.IsTrue (appBuilder.Build (app), $"{app.ProjectName} should succeed"); + // Check the final aab has the required feature files in it. + var aab = Path.Combine (Root, appBuilder.ProjectDirectory, + app.OutputPath, $"{app.PackageName}.aab"); + using (var zip = ZipHelper.OpenZip (aab)) { + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset1.txt"), "aab should not contain base/assets/asset1.txt"); + Assert.IsTrue (zip.ContainsEntry ("base/assets/asset2.txt"), "aab should contain base/assets/asset2.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets/asset1.txt"), "aab should contain assetpack1/assets/asset1.txt"); + Assert.IsFalse (zip.ContainsEntry ("assetpack1/assets/asset2.txt"), "aab should not contain assetpack1/assets/asset2.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets.pb"), "aab should contain assetpack1/assets.pb"); + Assert.IsFalse (zip.ContainsEntry ("assetpack1/resources.pb"), "aab should not contain assetpack1/resources.pb"); + } + } + } + + [Test] + [Category ("SmokeTests")] + public void BuildApplicationWithAssetPack ([Values (true, false)] bool isRelease) { + var path = Path.Combine ("temp", TestName); + var asset3 = new AndroidItem.AndroidAsset ("Assets\\asset3.txt") { + TextContent = () => "Asset3", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack1", + }; + var app = new XamarinAndroidApplicationProject { + ProjectName = "MyApp", + IsRelease = isRelease, + OtherBuildItems = { + new AndroidItem.AndroidAsset ("Assets\\asset1.txt") { + TextContent = () => "Asset1", + Encoding = Encoding.ASCII, + }, + new AndroidItem.AndroidAsset ("Assets\\asset2.txt") { + TextContent = () => "Asset2", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack1;DeliveryType=InstallTime", + }, + asset3, + new AndroidItem.AndroidAsset ("Assets\\asset4.txt") { + TextContent = () => "Asset4", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack2;DeliveryType=OnDemand", + }, + new AndroidItem.AndroidAsset ("Assets\\asset5.txt") { + TextContent = () => "Asset5", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=assetpack3;DeliveryType=FastFollow", + }, + } + }; + app.SetProperty ("AndroidPackageFormat", "aab"); + using (var appBuilder = CreateApkBuilder (Path.Combine (path, app.ProjectName))) { + Assert.IsTrue (appBuilder.Build (app), $"{app.ProjectName} should succeed"); + // Check the final aab has the required feature files in it. + var aab = Path.Combine (Root, appBuilder.ProjectDirectory, + app.OutputPath, $"{app.PackageName}.aab"); + var asset3File = Path.Combine (Root, path, app.ProjectName, + app.IntermediateOutputPath, "assetpacks", "assetpack1", "assets", "asset3.txt"); + using (var zip = ZipHelper.OpenZip (aab)) { + Assert.IsTrue (zip.ContainsEntry ("base/assets/asset1.txt"), "aab should contain base/assets/asset1.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset2.txt"), "aab should not contain base/assets/asset2.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset3.txt"), "aab should not contain base/assets/asset3.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset4.txt"), "aab should not contain base/assets/asset4.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets/asset2.txt"), "aab should contain assetpack1/assets/asset2.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets/asset3.txt"), "aab should contain assetpack1/assets/asset3.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack2/assets/asset4.txt"), "aab should contain assetpack2/assets/asset4.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack3/assets/asset5.txt"), "aab should contain assetpack3/assets/asset5.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets.pb"), "aab should contain assetpack1/assets.pb"); + Assert.IsFalse (zip.ContainsEntry ("assetpack1/resources.pb"), "aab should not contain assetpack1/resources.pb"); + } + Assert.IsTrue (appBuilder.Build (app, doNotCleanupOnUpdate: true, saveProject: false), $"{app.ProjectName} should succeed"); + appBuilder.Output.AssertTargetIsSkipped ("_CreateAssetPackManifests"); + appBuilder.Output.AssertTargetIsSkipped ("_BuildAssetPacks"); + appBuilder.Output.AssertTargetIsSkipped ("_GenerateAndroidAssetsDir"); + FileAssert.Exists (asset3File, $"file {asset3File} should exist."); + asset3.TextContent = () => "Asset3 Updated"; + asset3.Timestamp = DateTime.UtcNow.AddSeconds(1); + Assert.IsTrue (appBuilder.Build (app, doNotCleanupOnUpdate: true, saveProject: false), $"{app.ProjectName} should succeed"); + appBuilder.Output.AssertTargetIsNotSkipped ("_CreateAssetPackManifests"); + appBuilder.Output.AssertTargetIsNotSkipped ("_BuildAssetPacks"); + appBuilder.Output.AssertTargetIsNotSkipped ("_GenerateAndroidAssetsDir"); + FileAssert.Exists (asset3File, $"file {asset3File} should exist."); + Assert.AreEqual (asset3.TextContent (), File.ReadAllText (asset3File), $"Contents of {asset3File} should have been updated."); + app.OtherBuildItems.Remove (asset3); + Assert.IsTrue (appBuilder.Build (app, doNotCleanupOnUpdate: true), $"{app.ProjectName} should succeed"); + FileAssert.DoesNotExist (asset3File, $"file {asset3File} should not exist."); + using (var zip = ZipHelper.OpenZip (aab)) { + Assert.IsTrue (zip.ContainsEntry ("base/assets/asset1.txt"), "aab should contain base/assets/asset1.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset2.txt"), "aab should not contain base/assets/asset2.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset3.txt"), "aab should not contain base/assets/asset3.txt"); + Assert.IsFalse (zip.ContainsEntry ("base/assets/asset4.txt"), "aab should not contain base/assets/asset4.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets/asset2.txt"), "aab should contain assetpack1/assets/asset2.txt"); + Assert.IsFalse (zip.ContainsEntry ("assetpack1/assets/asset3.txt"), "aab should not contain assetpack1/assets/asset3.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack2/assets/asset4.txt"), "aab should contain assetpack2/assets/asset4.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack3/assets/asset5.txt"), "aab should contain assetpack3/assets/asset5.txt"); + Assert.IsTrue (zip.ContainsEntry ("assetpack1/assets.pb"), "aab should contain assetpack1/assets.pb"); + Assert.IsFalse (zip.ContainsEntry ("assetpack1/resources.pb"), "aab should not contain assetpack1/resources.pb"); + } + appBuilder.Output.AssertTargetIsNotSkipped ("_CreateAssetPackManifests"); + appBuilder.Output.AssertTargetIsNotSkipped ("_BuildAssetPacks"); + appBuilder.Output.AssertTargetIsNotSkipped ("_GenerateAndroidAssetsDir"); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs index 90a8371aad7..54e510218ad 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs @@ -124,6 +124,22 @@ public void RemoveFile (string folder, string file) zip.DeleteEntry ((ulong)index); } + public bool MoveEntry (string from, string to) + { + if (!zip.ContainsEntry (from)) { + return false; + } + var entry = zip.ReadEntry (from); + using (var stream = new MemoryStream ()) { + entry.Extract (stream); + stream.Position = 0; + zip.AddEntry (to, stream); + zip.DeleteEntry (entry); + Flush (); + } + return true; + } + public void AddDirectory (string folder, string folderInArchive, CompressionMethod method = CompressionMethod.Default) { if (!string.IsNullOrEmpty (folder)) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets index 91a88bedf80..258389e3d18 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets @@ -98,6 +98,10 @@ PreserveNewest Xamarin.Android.Aapt2.targets + + PreserveNewest + Xamarin.Android.Assets.targets + PreserveNewest Xamarin.Android.Javac.targets diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 0fda73a444e..93c66174eed 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -351,12 +351,6 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - - - $(IntermediateOutputPath)assets\ - Assets - - + @@ -593,8 +588,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - $(IntermediateOutputPath)android\bin\$(_AndroidPackage).apk - <_BaseZipIntermediate>$(IntermediateOutputPath)android\bin\base.zip + $(IntermediateOutputPath)android\bin\$(_AndroidPackage).apk + <_BaseZipIntermediate Condition=" '$(_BaseZipIntermediate)' == '' ">$(IntermediateOutputPath)android\bin\base.zip <_AppBundleIntermediate>$(IntermediateOutputPath)android\bin\$(_AndroidPackage).aab <_ApkSetIntermediate>$(IntermediateOutputPath)android\bin\$(_AndroidPackage).apks <_UniversalApkSetIntermediate>$(IntermediateOutputPath)android\bin\$(_AndroidPackage)-Universal.apks @@ -870,27 +865,6 @@ because xbuild doesn't support framework reference assemblies. /> - - - - - - - - - - - - - - - - - - $(IntermediateOutputPath)res\ @@ -1095,7 +1069,7 @@ because xbuild doesn't support framework reference assemblies. <_AndroidResourceDest Include="@(_WearableApplicationDescriptionFile);@(_BundledWearApplicationApkResourceFile)" /> - + @@ -1796,15 +1770,6 @@ because xbuild doesn't support framework reference assemblies. LibraryProjectImportsDirectoryName="$(_LibraryProjectImportsDirectoryName)"> - - - From 8b951cbc00d593800994a095509656a27c7608fe Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 22 Feb 2024 09:55:36 +0000 Subject: [PATCH 02/16] Fix manformed paths in asset packs --- src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs index 1370a7a6631..20422cc4ffd 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs @@ -45,6 +45,8 @@ public async override System.Threading.Tasks.Task RunTaskAsync () using (var zip = new ZipArchiveEx (OutputArchive.ItemSpec, File.Exists (OutputArchive.ItemSpec) ? FileMode.Open : FileMode.Create)) { zip.MoveEntry ("AndroidManifest.xml", "manifest/AndroidManifest.xml"); zip.Archive.DeleteEntry ("resources.pb"); + // Fix up aapt2 not dealing with '\' in subdirectories for assets. + zip.FixupWindowsPathSeparators ((a, b) => Log.LogDebugMessage ($"Fixing up malformed entry `{a}` -> `{b}`")); } } } From 7fc4f78b97ac9bc89ace4e7396c07a4105de660c Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 22 Feb 2024 10:29:59 +0000 Subject: [PATCH 03/16] Fix warning about async method --- src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs index 20422cc4ffd..0522f3394c0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs @@ -49,6 +49,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () zip.FixupWindowsPathSeparators ((a, b) => Log.LogDebugMessage ($"Fixing up malformed entry `{a}` -> `{b}`")); } } + await System.Threading.Tasks.Task.CompletedTask; } protected string[] GenerateCommandLineCommands (ITaskItem manifest, ITaskItem output) From 806089aa84db0dffd75dab49b72627a6172ef2cb Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 23 Feb 2024 10:55:10 +0000 Subject: [PATCH 04/16] Fix code formatting --- .../Tasks/GetAssetPacks.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs index 09de3794ece..f5afb136a7d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs @@ -35,18 +35,19 @@ public override bool RunTask () var assetPack = asset.GetMetadata ("AssetPack"); if (string.IsNullOrEmpty (assetPack) || string.Compare (assetPack, "base", StringComparison.OrdinalIgnoreCase) == 0) continue; - if (!assetPacks.TryGetValue (assetPack, out ITaskItem item)) - { + if (!assetPacks.TryGetValue (assetPack, out ITaskItem item)) { item = new TaskItem (assetPack); item.SetMetadata ("AssetPack", assetPack); item.SetMetadata ("AssetPackCacheFile", Path.Combine (IntermediateDir.ItemSpec, assetPack, "assetpack.cache")); assetPacks[assetPack] = item; } - foreach (var metadata in MetadataToCopy) + foreach (var metadata in MetadataToCopy) { if (string.IsNullOrEmpty (item.GetMetadata (metadata))) item.SetMetadata (metadata, asset.GetMetadata (metadata)); - if (!files.ContainsKey (assetPack)) + } + if (!files.ContainsKey (assetPack)) { files[assetPack] = new List (); + } files[assetPack].Add (asset.ItemSpec); } @@ -56,8 +57,9 @@ public override bool RunTask () ITaskItem item = kvp.Value; var cacheFile = kvp.Value.GetMetadata ("AssetPackCacheFile"); using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - foreach (var file in files [kvp.Key]) + foreach (var file in files [kvp.Key]) { sw.WriteLine ($"{file}:{File.GetLastWriteTimeUtc (file)}"); + } sw.WriteLine (item.GetMetadata ("DeliveryType") ?? "InstallTime"); sw.Flush (); Files.CopyIfStreamChanged (sw.BaseStream, cacheFile); From 2ede5585b0f8f73feecc00e283999e405ff01793 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 23 Feb 2024 11:49:17 +0000 Subject: [PATCH 05/16] Fix this last brace --- src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs index f5afb136a7d..9a7bd22d662 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs @@ -30,8 +30,7 @@ public override bool RunTask () { Dictionary assetPacks = new Dictionary (); Dictionary> files = new Dictionary> (); - foreach (var asset in Assets) - { + foreach (var asset in Assets) { var assetPack = asset.GetMetadata ("AssetPack"); if (string.IsNullOrEmpty (assetPack) || string.Compare (assetPack, "base", StringComparison.OrdinalIgnoreCase) == 0) continue; From 0f3af30ae5408c718eb34a4d49135df5bfa43d32 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 23 Feb 2024 12:59:26 +0000 Subject: [PATCH 06/16] Fix up more formatting --- src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs index 157cc512363..28a2253f640 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveUnknownFiles.cs @@ -54,7 +54,7 @@ public override bool RunTask () continue; } var files = System.IO.Directory.GetFiles (absDir, "*", SearchOption.AllDirectories); - foreach (string f in files) + foreach (string f in files) { if (!knownFiles.Contains (f)) { Log.LogDebugMessage ("Deleting File {0}", f); var item = new TaskItem (f.Replace (absDir, root + Path.DirectorySeparatorChar)); @@ -62,18 +62,20 @@ public override bool RunTask () Microsoft.Android.Build.Tasks.Files.SetWriteable (f); File.Delete (f); } + } if (RemoveDirectories) { var knownDirs = new HashSet (knownFiles.Select (d => Path.GetDirectoryName (d))); var dirs = System.IO.Directory.GetDirectories (absDir, "*", SearchOption.AllDirectories); - foreach (string d in dirs.OrderByDescending (s => s.Length)) + foreach (string d in dirs.OrderByDescending (s => s.Length)) { if (!knownDirs.Contains (d) && IsDirectoryEmpty (d)) { Log.LogDebugMessage ("Deleting Directory {0}", d); removedDirectories.Add (new TaskItem(d)); Microsoft.Android.Build.Tasks.Files.SetDirectoryWriteable (d); System.IO.Directory.Delete (d); } + } } } From d2422c74a1d26b3b3f3a992b80a0680421d1e19a Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 26 Feb 2024 14:24:54 +0000 Subject: [PATCH 07/16] rework based on feedback --- Documentation/guides/AndroidAssetPacks.md | 69 ++++--- .../guides/building-apps/build-items.md | 3 +- .../guides/building-apps/build-properties.md | 2 +- Documentation/guides/messages/README.md | 3 +- Documentation/guides/messages/xa0138.md | 4 +- Documentation/guides/messages/xa0139.md | 13 ++ .../Android/Xamarin.Android.Assets.targets | 190 +++++++++--------- .../Microsoft.Android.Sdk.BuildOrder.targets | 2 +- .../Properties/Resources.Designer.cs | 10 + .../Properties/Resources.resx | 7 +- .../Tasks/AndroidComputeResPaths.cs | 4 +- .../Tasks/CreateDynamicFeatureManifest.cs | 20 +- .../Tasks/GetAssetPacks.cs | 17 +- 13 files changed, 199 insertions(+), 145 deletions(-) create mode 100644 Documentation/guides/messages/xa0139.md diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md index 21b427c05b9..4458d119d5a 100644 --- a/Documentation/guides/AndroidAssetPacks.md +++ b/Documentation/guides/AndroidAssetPacks.md @@ -15,8 +15,8 @@ various constraints .NET Android cannot support `Feature` packs. The second type of `pack` is the `Asset` pack. This type of pack ONLY contains `AndroidAsset` items. It CANNOT contain any code or other -resources. This type of `feature` pack can be installed at install-time, -fast-follow or ondemand. It is most useful for apps which contain allot +resources. This type of `pack` can be installed at install-time, +fast-follow or ondemand. It is most useful for apps which contain a lot of `Assets`, such as Games or Multi Media applications. See the [documentation](https://developer.android.com/guide/playcore/asset-delivery) for details on how this all works. .NET Android does not have any official support for this type of pack. @@ -31,16 +31,7 @@ final app as an additional feature. ## Asset Pack Specification We want to provide our users the ability to use `Asset` packs without -having to implement the hack provided by @infinitespace-studios. Using -a separate project like in the hack is one way to go. It does have some -issues though. - -1. It is a `special` type of project. It requires a `global.json` which imports the - `NoTargets` sdk. -2. There is no IDE support for building this type of project. - -Having the user go through a number of hoops to implement this for -.NET Android or .net Maui is not ideal. We need a simpler method. +having rely on hacks provided by the community. The new idea is to make use of additional metadata on `AndroidAsset` Items to allow the build system to split up the assets into packs @@ -56,12 +47,12 @@ like this ``` In this case the additional `AssetPack` attribute is used to tell the -build system which pack to place this asset in. Since auto import of items -is common now we need a way for a user to add this additional attribute -to auto included items. Fortunately we are able to use the following. +build system which pack to place this asset in. If the `AssetPack` attribute is not present, the default behavior will be to include the asset in the main application package. +Since auto import of items is common now we need a way for a user to add this additional attribute to auto included items. Fortunately we are able to use the following. ```xml + @@ -76,22 +67,23 @@ end up in the main feature in the aab. Additional attributes can be used to control what type of asset pack is produced. The only extra one supported at this time is `DeliveryType`, this can have a value of `InstallTime`, `FastFollow` or `OnDemand`. -Additional attributes do not need to be included on ALL items. Any one -will do, only the `AssetPack` attribute will be needed. -See Google's [documentation](https://developer.android.com/guide/playcore/asset-delivery#asset-updates) for details on what each item does. +The `DeliveryType` attribute will be picked up from the first item which has it +for a specified `AssetPack`. For example the `DeliveryType` attribute in the +code below will be applied to the items for `AssetPack` `assets1`, it will not be applied +to the other `packs` or the `base` pack. ```xml - - - + + + + ``` -If the `AssetPack` attribute is not present, the default behavior will -be to include the asset in the main application package. +See Google's [documentation](https://developer.android.com/guide/playcore/asset-delivery#asset-updates) for details on what each of the `DeliveryType` values do. -If however you have a large number of assets it might be more efficient to make use of the `base` asset pack setting. In this scenario you update ALL assets to be in a single asset pack then use the `AssetPack="base"` metadata to declare which specific assets end up in the base aab file. With this you can use wildcards to move most assets into the asset pack. +If however you have a large number of assets it might be cleaner in the csproj to make use of the `base` value for the `AssetPack` attribute. In this scenario you update ALL assets to be in a single asset pack then use the `AssetPack="base"` metadata to declare which specific assets end up in the base aab file. With this you can use wildcards to move most assets into the asset pack. ```xml @@ -101,8 +93,9 @@ If however you have a large number of assets it might be more efficient to make ``` -In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, but ALL the other assets -will end up in the `assets1` asset pack. +In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, but ALL the other assets will end up in the `assets1` asset pack. + +At this time @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects. ## Implementation Details @@ -120,10 +113,10 @@ user wants to include. assetpacks/ assets1/ assets/ - movie1.mp4 - feature2/ + movie2.mp4 + assets2/ assets/ - movie2.mp4 + movie3.mp4 ``` All the building of the `pack` zip file would take place in these subfolders. @@ -151,11 +144,11 @@ assetpacks/ assets1/ AndroidManifest.xml assets/ - movie1.mp4 - feature2/ + movie2.mp4 + assets2/ AndroidManifest.xml assets/ - movie2.mp4 + movie3.mp4 ``` We can then call `aapt2` to build these packs into `.zip` files. A new @@ -174,3 +167,15 @@ Once the zip files have been created they are then added to the `AndroidAppBundleModules` ItemGroup. This will ensure that when the final `.aab` file is generated they are included as asset packs. +## Alternative Methods + +to implement the hack provided by @infinitespace-studios. Using +a separate project like in the hack is one way to go. It does have some +issues though. + +1. It is a `special` type of project. It requires a `global.json` which imports the + `NoTargets` sdk. +2. There is no IDE support for building this type of project. + +Having the user go through a number of hoops to implement this for +.NET Android or .net Maui is not ideal. We need a simpler method. \ No newline at end of file diff --git a/Documentation/guides/building-apps/build-items.md b/Documentation/guides/building-apps/build-items.md index 77b8410d0ce..2125cc60f8a 100644 --- a/Documentation/guides/building-apps/build-items.md +++ b/Documentation/guides/building-apps/build-items.md @@ -19,8 +19,7 @@ or library project is built. Supports [Android Assets](https://developer.android.com/guide/topics/resources/providing-resources#OriginalFiles), files that would be included in the `assets` folder in a Java Android project. -The `AndroidAsset` ItemGroup also supports additional metadata for generating [Asset Packs](https://developer.android.com/guide/playcore/asset-delivery). Adding the `AssetPack` attribute to and `AndroidAsset` will automatically generate an asset pack of that name. This feature is only supported when using the `.aab` -`AndroidPackageFormat`. The following example will place `movie2.mp4` and `movie3.mp4` in separate asset packs. +Starting with .NET 9 the `AndroidAsset` ItemGroup also supports additional metadata for generating [Asset Packs](https://developer.android.com/guide/playcore/asset-delivery). Adding the `AssetPack` attribute to and `AndroidAsset` will automatically generate an asset pack of that name. This feature is only supported when the [`$(AndroidPackageFormat)`](#androidpackageformat) is set to `.aab`. The following example will place `movie2.mp4` and `movie3.mp4` in separate asset packs. ```xml diff --git a/Documentation/guides/building-apps/build-properties.md b/Documentation/guides/building-apps/build-properties.md index f341870af60..e47b190fce4 100644 --- a/Documentation/guides/building-apps/build-properties.md +++ b/Documentation/guides/building-apps/build-properties.md @@ -1689,7 +1689,7 @@ Extra options to pass to `aprofutil`. ## BeforeBuildAndroidAssetPacks MSBuild Targets listed in this -property will run directly before `_BuildAssetPacks`. +property will run directly before the `AssetPack` items are built. Added in .NET 9 diff --git a/Documentation/guides/messages/README.md b/Documentation/guides/messages/README.md index e137dd0b20d..64530c583c0 100644 --- a/Documentation/guides/messages/README.md +++ b/Documentation/guides/messages/README.md @@ -100,7 +100,8 @@ package from all the users on device and try again. If that does not work you ca Fast Deployment is not currently supported on this device. Please file an issue with the exact error message using the 'Help->Send Feedback->Report a Problem' menu item in Visual Studio or 'Help->Report a Problem' in Visual Studio for Mac. -+ [XA0138](xa0138.md): @(AndroidAsset) build action does not support 'AssetPack' Metadata in Library Projects. ++ [XA0138](xa0138.md): %(AndroidAsset.AssetPack) and %(AndroidAsset.AssetPack) item metadata are only supported when `$(AndroidApplication)` is `true`. ++ [XA0139](xa0139.md): `@(AndroidAsset)` `{0}` has invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow` ## XA1xxx: Project related diff --git a/Documentation/guides/messages/xa0138.md b/Documentation/guides/messages/xa0138.md index 23249009439..6dc8a4341ae 100644 --- a/Documentation/guides/messages/xa0138.md +++ b/Documentation/guides/messages/xa0138.md @@ -6,8 +6,8 @@ ms.date: 02/05/2024 ## Issue -@(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects. +%(AndroidAsset.AssetPack) and %(AndroidAsset.AssetPack) item metadata are only supported when `$(AndroidApplication)` is `true`. ## Solution -Remove the 'AssetPack' or 'DeliveryType' Metadata from your `AndroidAsset` build Items. \ No newline at end of file +Remove the 'AssetPack' or 'DeliveryType' Metadata from your `AndroidAsset` build Items in the project the error was raised for. diff --git a/Documentation/guides/messages/xa0139.md b/Documentation/guides/messages/xa0139.md new file mode 100644 index 00000000000..1ba498b9736 --- /dev/null +++ b/Documentation/guides/messages/xa0139.md @@ -0,0 +1,13 @@ +title: Xamarin.Android error XA0138 +description: XA0138 error code +ms.date: 02/05/2024 +--- +# Xamarin.Android error XA0138 + +## Issue + +`@(AndroidAsset)` `{0}` has an invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow`. + +## Solution + +Make sure that all `DeliveryType` attributes are one of the following valid values, `installtime`, `ondemand` or `fastfollow`. diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets index f53313816e9..69d0a27d382 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets @@ -3,8 +3,8 @@ Xamarin.Android.Assets.targets WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have - created a backup copy. Incorrect changes to this file will make it - impossible to load or build your projects from the command-line or the IDE. + created a backup copy. Incorrect changes to this file will make it + impossible to load or build your projects from the command-line or the IDE. This file imports the version- and platform-specific targets for the project importing this file. This file also defines targets to produce an error if the specified targets @@ -25,123 +25,125 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - $(IntermediateOutputPath)assets\ - $(IntermediateOutputPath)assetpacks - Assets - true + $(IntermediateOutputPath)assets\ + $(IntermediateOutputPath)assetpacks + Assets + true - - UpdateAndroidAssets - ;_CalculateAssetsWithAssetPackMetaData - ;_CalculateAssetPacks - ;$(BeforeBuildAndroidAssetPacks) - ;_CreateAssetPackManifests - + + UpdateAndroidAssets + ;_CalculateAssetsWithAssetPackMetaData + ;_CalculateAssetPacks + ;$(BeforeBuildAndroidAssetPacks) + ;_CreateAssetPackManifests + + DependsOnTargets="$(CoreResolveReferencesDependsOn);_ComputeAndroidAssetsPaths;_CalculateAssetPacks;_GenerateAndroidAssetsDir" /> - - - - + + + + - - <_AssetDirectories Include="$(MonoAndroidAssetsDirIntermediate)" /> - <_AssetDirectories Include="@(_AssetPacks->'%(AssetPackDirectory)')" /> - - - - - - - - + Inputs="@(_AndroidMSBuildAllProjects);@(_AndroidResolvedAssets)" + Outputs="@(_AndroidAssetsDest)"> + + <_AssetDirectories Include="$(MonoAndroidAssetsDirIntermediate)" /> + <_AssetDirectories Include="@(_AssetPacks->'%(AssetPackDirectory)')" /> + + + + + + + + - - <_AssetsWithAssetPackMetaData Include="@(AndroidAsset)" Condition=" '%(AndroidAsset.AssetPack)' != '' " /> - - + + <_AssetsWithAssetPackMetaData Include="@(AndroidAsset)" Condition=" '%(AndroidAsset.AssetPack)' != '' " /> + + - - - - - - <_AssetPacks Include="@(_AndroidAsset)"> - $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack)\assets - $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack).zip - $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack)\AndroidManifest.xml - InstallTime - - - + DependsOnTargets="_CalculateAssetsWithAssetPackMetaData" + Condition=" ('$(AndroidPackageFormat)' == 'aab' And '$(AndroidApplication)' == 'true') " + > + + + + + + <_AssetPacks Include="@(_AndroidAsset)"> + $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack)\assets + $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack).zip + $(MonoAndroidAssetPacksDirIntermediate)\%(_AndroidAsset.AssetPack)\AndroidManifest.xml + InstallTime + + + - - - - - + Condition=" ('$(AndroidPackageFormat)' == 'aab' And '$(AndroidApplication)' == 'true') " + Inputs="@(_AssetPacks->'%(AssetPackCacheFile)')" + Outputs="@(_AssetPacks->'%(ManifestFile)')"> + + + + + - - - - - - + DependsOnTargets="$(BeforeBuildAndroidAssetPacks)" + Condition=" ('$(AndroidPackageFormat)' == 'aab' And '$(AndroidApplication)' == 'true') " + Inputs="@(_AssetPacks->'%(AssetPackCacheFile)')" + Outputs="@(_AssetPacks->'%(AssetPackOutput)')"> + + + + + + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets index 838db26e7e1..31c9442e8e9 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets @@ -76,7 +76,7 @@ properties that determine build ordering. _LintChecks; _IncludeNativeSystemLibraries; _CheckGoogleSdkRequirements; - _BuildAssetPacks; + BuildAndroidAssetPacks; diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs index e46244bf171..c5df25bdcc2 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs @@ -1600,5 +1600,15 @@ public static string XA1036 { return ResourceManager.GetString("XA1036", resourceCulture); } } + + /// + /// Looks up a localized string similar to AndroidManifest.xml //uses-sdk/@android:minSdkVersion '{0}' does not match the $(SupportedOSPlatformVersion) value '{1}' in the project file (if there is no $(SupportedOSPlatformVersion) value in the project file, then a default value has been assumed). + ///Either change the value in the AndroidManifest.xml to match the $(SupportedOSPlatformVersion) value, or remove the value in the AndroidManifest.xml (and add a $(SupportedOSPlatformVersion) value to the project file if it doesn't already exist).. + /// + public static string XA1039 { + get { + return ResourceManager.GetString("XA1039", resourceCulture); + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index 75b410c61a4..f860ebb1ae7 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -330,9 +330,14 @@ The capitalized word "Portable" that appears earlier in the message is plain tex {0} - The file name of a deprecated symbol file - @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects. + %(AndroidAsset.AssetPack) and %(AndroidAsset.AssetPack) item metadata are only supported when `$(AndroidApplication)` is `true`. + + `@(AndroidAsset)` `{0}` has an invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow`. + {0} - The file name +{1} - The value of the attribute in the project file. + There was a problem parsing {0}. This is likely due to incomplete or invalid XML. Exception: {1} {0} - The file name diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs index f86ac39cf40..4115073151c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs @@ -129,7 +129,9 @@ public override bool RunTask () } string dest = Path.GetFullPath (Path.Combine (IntermediateDir, baseFileName)); string intermediateDirFullPath = Path.GetFullPath (IntermediateDir); - if (!string.IsNullOrEmpty (assetPack) && (string.Compare (assetPack, "base", StringComparison.OrdinalIgnoreCase) != 0) && !string.IsNullOrEmpty (AssetPackIntermediateDir)) { + if (!string.IsNullOrEmpty (assetPack) && + (string.Compare (assetPack, "base", StringComparison.OrdinalIgnoreCase) != 0) && + !string.IsNullOrEmpty (AssetPackIntermediateDir)) { dest = Path.GetFullPath (Path.Combine (AssetPackIntermediateDir, assetPack, "assets", baseFileName)); intermediateDirFullPath = Path.GetFullPath (AssetPackIntermediateDir); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs index e999f588782..697fb11d0e6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs @@ -61,7 +61,8 @@ public override bool RunTask () return !Log.HasLoggedErrors; } - void GenerateFeatureManifest (XDocument doc) { + void GenerateFeatureManifest (XDocument doc) + { XAttribute featureTitleResource = null; if (!string.IsNullOrEmpty (FeatureTitleResource)) featureTitleResource = new XAttribute (distNS + "title", FeatureTitleResource); @@ -71,9 +72,9 @@ void GenerateFeatureManifest (XDocument doc) { if (!string.IsNullOrEmpty (MinSdkVersion)) usesSdk.Add (new XAttribute (androidNS + "targetSdkVersion", TargetSdkVersion)); doc.Add (new XElement ("manifest", - new XAttribute(XNamespace.Xmlns + "android", androidNS), - new XAttribute(XNamespace.Xmlns + "tools", toolsNS), - new XAttribute(XNamespace.Xmlns + "dist", distNS), + new XAttribute (XNamespace.Xmlns + "android", androidNS), + new XAttribute (XNamespace.Xmlns + "tools", toolsNS), + new XAttribute (XNamespace.Xmlns + "dist", distNS), new XAttribute (androidNS + "versionCode", "1"), new XAttribute (androidNS + "versionName", "1.0"), new XAttribute ("package", PackageName), @@ -98,7 +99,8 @@ void GenerateFeatureManifest (XDocument doc) { ); } - void GenerateAssetPackManifest (XDocument doc) { + void GenerateAssetPackManifest (XDocument doc) + { doc.Add (new XElement ("manifest", new XAttribute (XNamespace.Xmlns + "android", androidNS), new XAttribute (XNamespace.Xmlns + "tools", toolsNS), @@ -121,15 +123,15 @@ void GenerateAssetPackManifest (XDocument doc) { XElement GetDistribution () { XElement distribution; - switch (FeatureDeliveryType) + switch (FeatureDeliveryType.ToLowerInvariant ()) { - case "OnDemand": + case "ondemand": distribution = new XElement (distNS + "on-demand"); break; - case "FastFollow": + case "fastfollow": distribution = new XElement (distNS + "fast-follow"); break; - case "InstallTime": + case "installtime": default: distribution = new XElement (distNS + "install-time"); break; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs index 9a7bd22d662..e80ee38ec45 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs @@ -6,6 +6,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.Android.Build.Tasks; +using Irony; namespace Xamarin.Android.Tasks { @@ -59,7 +60,11 @@ public override bool RunTask () foreach (var file in files [kvp.Key]) { sw.WriteLine ($"{file}:{File.GetLastWriteTimeUtc (file)}"); } - sw.WriteLine (item.GetMetadata ("DeliveryType") ?? "InstallTime"); + var deliveryType = item.GetMetadata ("DeliveryType") ?? "InstallTime"; + if (!IsDeliveryTypeValid (item, deliveryType)) { + Log.LogCodedError ("XA1039", Properties.Resources.XA1039, item.ItemSpec, deliveryType); + } + sw.WriteLine (deliveryType); sw.Flush (); Files.CopyIfStreamChanged (sw.BaseStream, cacheFile); } @@ -69,5 +74,15 @@ public override bool RunTask () return true; } + + bool IsDeliveryTypeValid (ITaskItem item, string deliveryType) + { + if (string.Compare (deliveryType, "installtime", StringComparison.OrdinalIgnoreCase) == 0 && + string.Compare (deliveryType, "ondemand", StringComparison.OrdinalIgnoreCase) == 0 && + string.Compare (deliveryType, "fastfollow", StringComparison.OrdinalIgnoreCase) == 0) { + return false; + } + return true; + } } } \ No newline at end of file From 7faadf441f87e2cd08350ac05e309b076ac96903 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 1 Mar 2024 12:53:35 +0000 Subject: [PATCH 08/16] Move some docs --- Documentation/guides/AndroidAssetPacks.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md index 4458d119d5a..55351cb370e 100644 --- a/Documentation/guides/AndroidAssetPacks.md +++ b/Documentation/guides/AndroidAssetPacks.md @@ -1,4 +1,4 @@ -# Android Asset Packs +g# Android Asset Packs Google Android began supporting splitting up the app package into multiple packs with the introduction of the `aab` package format. This format allows @@ -19,14 +19,6 @@ resources. This type of `pack` can be installed at install-time, fast-follow or ondemand. It is most useful for apps which contain a lot of `Assets`, such as Games or Multi Media applications. See the [documentation](https://developer.android.com/guide/playcore/asset-delivery) for details on how this all works. -.NET Android does not have any official support for this type of pack. -However a hack is available via the excellent @infinitespace-studios on -[github](https://github.com/infinitespace-studios/MauiAndroidAssetPackExample). -This hack allows developers to place additional assets in a special -`NoTargets` project. This project is built just after the final `aab` is -produced. It builds a zip file which is then added to the `@(Modules)` -ItemGroup in the main application. This zip is then included into the -final app as an additional feature. ## Asset Pack Specification @@ -169,8 +161,14 @@ final `.aab` file is generated they are included as asset packs. ## Alternative Methods -to implement the hack provided by @infinitespace-studios. Using -a separate project like in the hack is one way to go. It does have some +An alternative method is available on [github](https://github.com/infinitespace-studios/MauiAndroidAssetPackExample). +This method allows developers to place additional assets in a special +[NoTargets](https://github.com/microsoft/MSBuildSdks/blob/main/src/NoTargets/README.md) project. This project is built just after the final `aab` is +produced. It builds a zip file which is then added to the `@(Modules)` +ItemGroup in the main application. This zip is then included into the +final app as an additional feature. + +Using a separate project like in the hack is one way to go. It does have some issues though. 1. It is a `special` type of project. It requires a `global.json` which imports the @@ -178,4 +176,4 @@ issues though. 2. There is no IDE support for building this type of project. Having the user go through a number of hoops to implement this for -.NET Android or .net Maui is not ideal. We need a simpler method. \ No newline at end of file +.NET Android or .net Maui is not ideal. From fe602dc224aa4468b132e540a274f414037e8994 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 6 Mar 2024 13:28:52 +0000 Subject: [PATCH 09/16] Update docs with testing info --- Documentation/guides/AndroidAssetPacks.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md index 55351cb370e..d0d4c605046 100644 --- a/Documentation/guides/AndroidAssetPacks.md +++ b/Documentation/guides/AndroidAssetPacks.md @@ -89,6 +89,26 @@ In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, At this time @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects. +NOTE: `AssetPacks` are only used when the `AndroidPackageFormat` is set to `aab` (the default for Release). When using the `apk` setting the assets will be placed inside the `apk`. + +## Testing and Debugging + +In order to test your asset packs in the `Debug` configuration, you will need to make some changes to your `.csproj`. Firstly we need to change the `AndroidPackageFormat` to `aab`. It will be `aab` by default for `Release` builds, but will default to `apk` for `Debug` builds. Setting the `AndroidPackageFormat` to `aab` will disable +fast deployment, so it is advised that you only do this when you need to test your `AssetPacks`. + +To test your asset packs add the following to the first `PropertyGroup` in your `.csproj`. + +```xml +aab +--local-testing $(AndroidBundleToolExtraArgs) +``` + +The `--local-testing` argument tells the `bundletool` application to install ALL the asset packs in a local cache on the device. `InstallTime` packs will be installed during the app installation process. + +`FastFollow` packs behave like `OnDemand` packs. They will not automatically installed when the game is sideloaded. You will need to request them manually when the game starts. + +For more details see [https://developer.android.com/guide/playcore/asset-delivery/test](https://developer.android.com/guide/playcore/asset-delivery/test). + ## Implementation Details There are a few changes we need to make in order to support this feature. From 7d00b962d0c0f39a6e6c22cc8540ebeba544573b Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 7 Mar 2024 14:50:14 +0000 Subject: [PATCH 10/16] Fix test --- .../Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs index 60e1136bacd..c922531c2ec 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs @@ -28,7 +28,7 @@ public void BuildLibraryWithAssetPack ([Values (true, false)] bool isRelease) using (var builder = CreateDllBuilder (Path.Combine (path, lib.ProjectName))) { builder.ThrowOnBuildFailure = false; Assert.IsFalse (builder.Build (lib), $"{lib.ProjectName} should fail."); - StringAssertEx.Contains ("error XA0138: @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects.", builder.LastBuildOutput, + StringAssertEx.Contains ("error XA0138:", builder.LastBuildOutput, "Build Output did not contain error XA0138'."); } } From 99ee9df21423290c1f2b18769b80c8afa67ca431 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 7 Mar 2024 22:13:50 +0000 Subject: [PATCH 11/16] more docs --- Documentation/guides/AndroidAssetPacks.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md index d0d4c605046..2c5d4924f31 100644 --- a/Documentation/guides/AndroidAssetPacks.md +++ b/Documentation/guides/AndroidAssetPacks.md @@ -91,6 +91,17 @@ At this time @(AndroidAsset) build action does not support 'AssetPack' or 'Deliv NOTE: `AssetPacks` are only used when the `AndroidPackageFormat` is set to `aab` (the default for Release). When using the `apk` setting the assets will be placed inside the `apk`. +## Release Configuration + +In order for the application to function correctly we need to inform the `R8` linker which java classes we need to keep. To do this we need to add the following lines to a `Proguard.cfg` file which is in the root of our project folder. + +``` +-keep com.google.android.play.* +``` + +Alternatively you can create a file called `Proguard.cfg` and use the `ProguardConfiguration` built action. +Adding these lines will ensure that all the required java components are not linked away during the Release build. + ## Testing and Debugging In order to test your asset packs in the `Debug` configuration, you will need to make some changes to your `.csproj`. Firstly we need to change the `AndroidPackageFormat` to `aab`. It will be `aab` by default for `Release` builds, but will default to `apk` for `Debug` builds. Setting the `AndroidPackageFormat` to `aab` will disable From 97c30afdadcefd025f6ef1d34fa567e9e5949310 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 7 Mar 2024 22:41:26 +0000 Subject: [PATCH 12/16] update with a link --- Documentation/guides/AndroidAssetPacks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md index 2c5d4924f31..2eca225ea9e 100644 --- a/Documentation/guides/AndroidAssetPacks.md +++ b/Documentation/guides/AndroidAssetPacks.md @@ -93,13 +93,13 @@ NOTE: `AssetPacks` are only used when the `AndroidPackageFormat` is set to `aab` ## Release Configuration -In order for the application to function correctly we need to inform the `R8` linker which java classes we need to keep. To do this we need to add the following lines to a `Proguard.cfg` file which is in the root of our project folder. +In order for the application to function correctly we need to inform the `R8` linker which java classes we need to keep. To do this we need to add the following lines to a `ProGuard.cfg` file which is in the root of our project folder. ``` -keep com.google.android.play.* ``` -Alternatively you can create a file called `Proguard.cfg` and use the `ProguardConfiguration` built action. +Alternatively you can create a file called `ProGuard.cfg` and use the [@(ProguardConfiguration)](~/android/deploy-test/building-apps/build-items.md#proguardconfiguration) built action. Adding these lines will ensure that all the required java components are not linked away during the Release build. ## Testing and Debugging From 7118e7cd8cce5444e81519ad4a0c5e803f5debaa Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 11 Mar 2024 16:26:47 +0000 Subject: [PATCH 13/16] fix typo --- Documentation/guides/AndroidAssetPacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md index 2eca225ea9e..ba8c0033953 100644 --- a/Documentation/guides/AndroidAssetPacks.md +++ b/Documentation/guides/AndroidAssetPacks.md @@ -1,4 +1,4 @@ -g# Android Asset Packs +# Android Asset Packs Google Android began supporting splitting up the app package into multiple packs with the introduction of the `aab` package format. This format allows From 5f40afe649843444f9599e23df4318528ab9233b Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 13 Mar 2024 14:38:22 -0400 Subject: [PATCH 14/16] Copyediting and code formatting changes. --- Documentation/guides/AndroidAssetPacks.md | 149 ++++++++++-------- .../guides/building-apps/build-items.md | 4 +- .../guides/building-apps/build-properties.md | 2 +- .../Android/Xamarin.Android.Assets.targets | 58 +++---- 4 files changed, 120 insertions(+), 93 deletions(-) diff --git a/Documentation/guides/AndroidAssetPacks.md b/Documentation/guides/AndroidAssetPacks.md index ba8c0033953..920bc2d5959 100644 --- a/Documentation/guides/AndroidAssetPacks.md +++ b/Documentation/guides/AndroidAssetPacks.md @@ -5,30 +5,30 @@ packs with the introduction of the `aab` package format. This format allows the developer to split the app up into multiple `packs`. Each `pack` can be downloaded to the device either at install time or on demand. This allows application developers to save space and install time by only installing -the required parts of the app initially. Then installing other `packs` +the required parts of the app initially, then installing other `packs` as required. -There are two types of `pack`. The first is a `Feature` pack, this type -of pack contains code and other resources. Code in these types of `pack` -can be launched via the `StartActivity` API call. At this time due to -various constraints .NET Android cannot support `Feature` packs. +There are two types of `pack`: Feature packs and Asset packs. +*Feature* pack contains *non-native* Java code and other resources. +Code in these types of `pack` can be launched via the `Context.StartActivity()` +API call. At this time due to various constraints .NET Android cannot support +Feature packs. -The second type of `pack` is the `Asset` pack. This type of pack ONLY -contains `AndroidAsset` items. It CANNOT contain any code or other -resources. This type of `pack` can be installed at install-time, -fast-follow or ondemand. It is most useful for apps which contain a lot -of `Assets`, such as Games or Multi Media applications. -See the [documentation](https://developer.android.com/guide/playcore/asset-delivery) for details on how this all works. +*Asset* packs contain *only* +[`@(AndroidAsset)`](~/android/deploy-test/building-apps/build-items.md#androidasset) items. +It *cannot* contain any code or other resources. This type of `pack` can be +installed at install-time, fast-follow or ondemand. It is most useful for apps +which contain a lot of Assets, such as Games or Multi Media applications. +See the [Android Asset Delivery documentation](https://developer.android.com/guide/playcore/asset-delivery) +for details on how this all works. ## Asset Pack Specification -We want to provide our users the ability to use `Asset` packs without -having rely on hacks provided by the community. +We want to provide our users the ability to use `Asset` packs without relying +on [Alternative Methods](#alternativemethods) -The new idea is to make use of additional metadata on `AndroidAsset` -Items to allow the build system to split up the assets into packs -automatically. So it is proposed that we implement support for something -like this +Add support for new `%(AndroidAsset.AssetPack)` item metadata, which +allows the build system to split up the assets into packs automatically: ```xml @@ -38,44 +38,53 @@ like this ``` -In this case the additional `AssetPack` attribute is used to tell the -build system which pack to place this asset in. If the `AssetPack` attribute is not present, the default behavior will be to include the asset in the main application package. -Since auto import of items is common now we need a way for a user to add this additional attribute to auto included items. Fortunately we are able to use the following. +The default value for `%(AndroidAsset.AssetPack)` is `base`, which will +cause the asset to be included in the main application package. + +As auto import of items is now common, we need a way for a user to add +this additional attribute to auto included items. This can be done by +using `Update`: ```xml - - + ``` -This code uses the `Update` attribute to tell MSBuild that we are going -to update a specific item. Note in the sample we do NOT need to include -an `Update` for the `data.xml`, since this is auto imported it will still -end up in the main feature in the aab. +`%(AndroidAsset.DeliveryType)` item metadata can be specified to control what +*type* of asset pack is produced. Valid values are: + + * `InstallTime`: Asset pack will be delivered when the app is installed. + This is the default value for assets not in the base package. + * `FastFollow`: Asset pack will be downloaded automatically as soon as the app is installed. + * `OnDemand`: Asset pack will be downloaded while the app is running. + +The `DeliveryType` for a given asset pack is based on the *first* +`@(AndroidAsset.DeliveryType)` value encountered for a `%(AssetPack)` name. -Additional attributes can be used to control what type of asset pack is -produced. The only extra one supported at this time is `DeliveryType`, -this can have a value of `InstallTime`, `FastFollow` or `OnDemand`. -The `DeliveryType` attribute will be picked up from the first item which has it -for a specified `AssetPack`. For example the `DeliveryType` attribute in the -code below will be applied to the items for `AssetPack` `assets1`, it will not be applied -to the other `packs` or the `base` pack. +Consider the following example, in which `Asset/movie2.mp4` and `Asset/movie3.mp4` +are both in the `assets1` pack, which will have a `%(DeliveryType)` of `InstallTime` +(the first encountered value "wins"). `Asset/movie1.mp4` will be in the base package, +while `Asset/movie4.mp4` will be in the "asset2" asset pack. ```xml - + ``` See Google's [documentation](https://developer.android.com/guide/playcore/asset-delivery#asset-updates) for details on what each of the `DeliveryType` values do. -If however you have a large number of assets it might be cleaner in the csproj to make use of the `base` value for the `AssetPack` attribute. In this scenario you update ALL assets to be in a single asset pack then use the `AssetPack="base"` metadata to declare which specific assets end up in the base aab file. With this you can use wildcards to move most assets into the asset pack. +If however you have a large number of assets it might be cleaner in the +`.csproj` to make use of the `base` value for the `%(AssetPack)` attribute. +In this scenario you update *all* assets to be in a single asset pack then use +`AssetPack="base"` metadata to declare which specific assets end up in the base +aab file. With this you can use wildcards to move most assets into the asset pack: ```xml @@ -85,38 +94,53 @@ If however you have a large number of assets it might be cleaner in the csproj t ``` -In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, but ALL the other assets will end up in the `assets1` asset pack. +In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, +but *all the other assets* will end up in the `assets1` asset pack. -At this time @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects. +At this time the `@(AndroidAsset)` build action does not support `%(AssetPack)` +or `%(DeliveryType)` Metadata in Library Projects. -NOTE: `AssetPacks` are only used when the `AndroidPackageFormat` is set to `aab` (the default for Release). When using the `apk` setting the assets will be placed inside the `apk`. +NOTE: `AssetPacks` are only used when the +[`$(AndroidPackageFormat)`](~/android/deploy-test/building-apps/build-properties.md#debugsymbols) +property is set to `aab` (the default for Release). +When using the `apk` setting the assets will be placed inside the `apk`. ## Release Configuration -In order for the application to function correctly we need to inform the `R8` linker which java classes we need to keep. To do this we need to add the following lines to a `ProGuard.cfg` file which is in the root of our project folder. +In order for the application to function correctly we need to inform the `R8` +linker which Java classes we need to keep. To do this we need to add the +following lines to a `ProGuard.cfg` file which is in the root of our project folder: ``` -keep com.google.android.play.* ``` -Alternatively you can create a file called `ProGuard.cfg` and use the [@(ProguardConfiguration)](~/android/deploy-test/building-apps/build-items.md#proguardconfiguration) built action. -Adding these lines will ensure that all the required java components are not linked away during the Release build. +Alternatively you can create a file called `ProGuard.cfg` and use the +[@(ProguardConfiguration)](~/android/deploy-test/building-apps/build-items.md#proguardconfiguration) +build action. Adding these lines will ensure that all the required Java components are not linked +away during the Release build. ## Testing and Debugging -In order to test your asset packs in the `Debug` configuration, you will need to make some changes to your `.csproj`. Firstly we need to change the `AndroidPackageFormat` to `aab`. It will be `aab` by default for `Release` builds, but will default to `apk` for `Debug` builds. Setting the `AndroidPackageFormat` to `aab` will disable -fast deployment, so it is advised that you only do this when you need to test your `AssetPacks`. +In order to test your asset packs in the `Debug` configuration, you will need to +make some changes to your `.csproj`. Firstly we need to change the +`$(AndroidPackageFormat)` to `aab`. It will be `aab` by default for `Release` builds, +but will default to `apk` for `Debug` builds. Setting the `AndroidPackageFormat` to `aab` +will disable fast deployment, so it is advised that you only do this when you need to test +your `AssetPacks`. -To test your asset packs add the following to the first `PropertyGroup` in your `.csproj`. +To test your asset packs add the following to the first `` in your `.csproj`. ```xml aab --local-testing $(AndroidBundleToolExtraArgs) ``` -The `--local-testing` argument tells the `bundletool` application to install ALL the asset packs in a local cache on the device. `InstallTime` packs will be installed during the app installation process. +The `--local-testing` argument tells the `bundletool` application to install all the asset packs +in a local cache on the device. `InstallTime` packs will be installed during the app installation process. -`FastFollow` packs behave like `OnDemand` packs. They will not automatically installed when the game is sideloaded. You will need to request them manually when the game starts. +`FastFollow` packs behave like `OnDemand` packs. They will not automatically installed when the app +is sideloaded. You will need to request them manually when the game starts. For more details see [https://developer.android.com/guide/playcore/asset-delivery/test](https://developer.android.com/guide/playcore/asset-delivery/test). @@ -124,7 +148,7 @@ For more details see [https://developer.android.com/guide/playcore/asset-deliver There are a few changes we need to make in order to support this feature. One of the issues we will hit is the build times when dealing with large assets. -Current the assets which are to be included in the `aab` are COPIED +Current the assets which are to be included in the `aab` are ***copied*** into the `$(IntermediateOutputPath)assets` directory. This folder is then passed to `aapt2` for the build process. @@ -146,19 +170,19 @@ All the building of the `pack` zip file would take place in these subfolders. The name of the pack will be based on the main "packagename" with the asset pack name appended to the end. e.g `com.microsoft.assetpacksample.assets1`. -During the build process we identify ALL the `AndroidAsset` items which -define an `AssetPack` attribute. These files are then copied to the +During the build process we identify all the `AndroidAsset` items which +have an `AssetPack` attribute. These files are then copied to the new `$(IntermediateOutputPath)assetpacks` directory rather than the existing `$(IntermediateOutputPath)assets` directory. This allows us to continue to support the normal `AndroidAsset` behavior while adding the new system. Once we have collected and copied all the assets we then use the new -`GetAssetPacks` Task to figure out which asset packs we need to create. -We then call the `CreateDynamicFeatureManifest` to create a required +`` Task to figure out which asset packs we need to create. +We then call the `` task to create a required `AndroidManifest.xml` file for the asset pack. This file will end up in the same `$(IntermediateOutputPath)assetpacks` directory. -We call this Task `CreateDynamicFeatureManifest` because it can be used +We call this Task `` because it can be used to create any feature pack if and when we get to implement full feature packs. @@ -175,36 +199,37 @@ assetpacks/ ``` We can then call `aapt2` to build these packs into `.zip` files. A new -task `Aapt2LinkAssetPack` takes care of this. This is a special version +task `` task takes care of this. This is a special version of `Aapt2Link` which implements linking for asset packs only. It also takes care of a few problems which `aapt2` introduces. For some reason the zip file that is created has the `AndroidManifest.xml` file in the wrong place. It creates it in the root of the zip file, but the `bundletool` expects it to be in a `manifest` directory. `bundletool` will error out if its not in the right place. -So `Aapt2LinkAssetPack` takes care of this for us. It also removes a +So `` takes care of this for us. It also removes a `resources.pb` which gets added. Again, `bundletool` will error if this file is in the zip file. Once the zip files have been created they are then added to the -`AndroidAppBundleModules` ItemGroup. This will ensure that when the +`@(AndroidAppBundleModules)` ItemGroup. This will ensure that when the final `.aab` file is generated they are included as asset packs. ## Alternative Methods An alternative method is available on [github](https://github.com/infinitespace-studios/MauiAndroidAssetPackExample). This method allows developers to place additional assets in a special -[NoTargets](https://github.com/microsoft/MSBuildSdks/blob/main/src/NoTargets/README.md) project. This project is built just after the final `aab` is -produced. It builds a zip file which is then added to the `@(Modules)` -ItemGroup in the main application. This zip is then included into the +[NoTargets](https://github.com/microsoft/MSBuildSdks/blob/main/src/NoTargets/README.md) project. +This project is built just after the final `aab` is produced. It builds a zip +file which is then added to the `@(Modules)` ItemGroup in the main application. +This zip is then included into the final app as an additional feature. Using a separate project like in the hack is one way to go. It does have some issues though. -1. It is a `special` type of project. It requires a `global.json` which imports the - `NoTargets` sdk. -2. There is no IDE support for building this type of project. + 1. It is a `special` type of project. It requires a `global.json` which imports + the `NoTargets` sdk. + 2. There is no IDE support for building this type of project. Having the user go through a number of hoops to implement this for -.NET Android or .net Maui is not ideal. +.NET Android or .NET MAUI is not ideal. diff --git a/Documentation/guides/building-apps/build-items.md b/Documentation/guides/building-apps/build-items.md index 2125cc60f8a..176e875aa3a 100644 --- a/Documentation/guides/building-apps/build-items.md +++ b/Documentation/guides/building-apps/build-items.md @@ -19,7 +19,7 @@ or library project is built. Supports [Android Assets](https://developer.android.com/guide/topics/resources/providing-resources#OriginalFiles), files that would be included in the `assets` folder in a Java Android project. -Starting with .NET 9 the `AndroidAsset` ItemGroup also supports additional metadata for generating [Asset Packs](https://developer.android.com/guide/playcore/asset-delivery). Adding the `AssetPack` attribute to and `AndroidAsset` will automatically generate an asset pack of that name. This feature is only supported when the [`$(AndroidPackageFormat)`](#androidpackageformat) is set to `.aab`. The following example will place `movie2.mp4` and `movie3.mp4` in separate asset packs. +Starting with .NET 9 the `@(AndroidAsset)` build action also supports additional metadata for generating [Asset Packs](https://developer.android.com/guide/playcore/asset-delivery). The `%(AndroidAsset.AssetPack)` metadata can be used to automatically generate an asset pack of that name. This feature is only supported when the [`$(AndroidPackageFormat)`](#androidpackageformat) is set to `.aab`. The following example will place `movie2.mp4` and `movie3.mp4` in separate asset packs. ```xml @@ -45,7 +45,7 @@ assets into the asset pack. ``` -In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, but ALL the other assets +In this example, `movie.mp4` and `some.png` will end up in the `base` aab file, while all the other assets will end up in the `assets1` asset pack. The additional metadata is only supported on .NET Android 9 and above. diff --git a/Documentation/guides/building-apps/build-properties.md b/Documentation/guides/building-apps/build-properties.md index e47b190fce4..5e3468f8a55 100644 --- a/Documentation/guides/building-apps/build-properties.md +++ b/Documentation/guides/building-apps/build-properties.md @@ -838,7 +838,7 @@ This property controls if an Asset Packs build automatically are auto included in the final `.aab` file. It will default to `true`. In certain cases the user might want to release an interim release. In -these cases the user does not NEED to update the asset pack. Especially +these cases the user does not need to update the asset pack. Especially if the contents of the asset pack have not changed. This property allows the user to skip the asset packs if they are not required. diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets index 69d0a27d382..00dc628acaa 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Assets.targets @@ -25,10 +25,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - $(IntermediateOutputPath)assets\ - $(IntermediateOutputPath)assetpacks - Assets - true + $(IntermediateOutputPath)assets\ + $(IntermediateOutputPath)assetpacks + Assets + true @@ -44,15 +44,15 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + DependsOnTargets="$(CoreResolveReferencesDependsOn);_ComputeAndroidAssetsPaths;_CalculateAssetPacks;_GenerateAndroidAssetsDir" /> @@ -80,9 +80,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. <_AssetsWithAssetPackMetaData Include="@(AndroidAsset)" Condition=" '%(AndroidAsset.AssetPack)' != '' " /> @@ -91,7 +91,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. Condition=" ('$(AndroidPackageFormat)' == 'aab' And '$(AndroidApplication)' == 'true') " > - + @@ -111,11 +113,11 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. Outputs="@(_AssetPacks->'%(ManifestFile)')"> @@ -129,14 +131,14 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. Outputs="@(_AssetPacks->'%(AssetPackOutput)')"> @@ -146,4 +148,4 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - \ No newline at end of file + From d91e5e3dd9c44a2616b3f1c5e8057b8f2ad99802 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 14 Mar 2024 12:01:13 +0000 Subject: [PATCH 15/16] Add some AssetPack name checks --- Documentation/guides/messages/README.md | 1 + Documentation/guides/messages/xa0140.md | 13 ++++++++++ .../Properties/Resources.resx | 5 ++++ .../Tasks/CreateDynamicFeatureManifest.cs | 4 ++-- .../Tasks/GetAssetPacks.cs | 13 +++++++++- .../AssetPackTests.cs | 24 +++++++++++++++++++ 6 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 Documentation/guides/messages/xa0140.md diff --git a/Documentation/guides/messages/README.md b/Documentation/guides/messages/README.md index 64530c583c0..df2c6915f35 100644 --- a/Documentation/guides/messages/README.md +++ b/Documentation/guides/messages/README.md @@ -102,6 +102,7 @@ Please file an issue with the exact error message using the 'Help->Send Feedback or 'Help->Report a Problem' in Visual Studio for Mac. + [XA0138](xa0138.md): %(AndroidAsset.AssetPack) and %(AndroidAsset.AssetPack) item metadata are only supported when `$(AndroidApplication)` is `true`. + [XA0139](xa0139.md): `@(AndroidAsset)` `{0}` has invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow` ++ [XA0140](xa0140.md): ## XA1xxx: Project related diff --git a/Documentation/guides/messages/xa0140.md b/Documentation/guides/messages/xa0140.md new file mode 100644 index 00000000000..3d25181c887 --- /dev/null +++ b/Documentation/guides/messages/xa0140.md @@ -0,0 +1,13 @@ +title: Xamarin.Android error XA0138 +description: XA0140 error code +ms.date: 02/05/2024 +--- +# Xamarin.Android error XA0140 + +## Issue + +The AssetPack value defined for `{0}` has invalid characters. `{1}` should only contain A-z, a-z, 0-9 or an underscore. + +## Solution + +Make sure that all `AssetPack` attributes only contain valid characters. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index f860ebb1ae7..f473cd0f265 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -337,6 +337,11 @@ The capitalized word "Portable" that appears earlier in the message is plain tex `@(AndroidAsset)` `{0}` has an invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow`. {0} - The file name {1} - The value of the attribute in the project file. + + + The AssetPack value defined for `{0}` has invalid characters. `{1}` should only contain A-z, a-z, 0-9 or an underscore. + {0} - The file name +{1} - The value of the attribute in the metadata. There was a problem parsing {0}. This is likely due to incomplete or invalid XML. Exception: {1} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs index 697fb11d0e6..48e6fbb9bca 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs @@ -49,8 +49,8 @@ public class CreateDynamicFeatureManifest : AndroidTask public override bool RunTask () { XDocument doc = new XDocument (); - switch (FeatureType) { - case "AssetPack": + switch (FeatureType.ToLowerInvariant ()) { + case "assetpack": GenerateAssetPackManifest (doc); break; default: diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs index e80ee38ec45..4645204bd1d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAssetPacks.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Utilities; using Microsoft.Android.Build.Tasks; using Irony; +using System.Text.RegularExpressions; namespace Xamarin.Android.Tasks { @@ -14,6 +15,7 @@ namespace Xamarin.Android.Tasks // ones that actually exist on disk. public class GetAssetPacks : AndroidTask { + readonly Regex validAssetPackName = new Regex ("^[A-Z0-9_]+$", RegexOptions.Compiled | RegexOptions.IgnoreCase); public override string TaskPrefix => "GAP"; [Required] @@ -35,6 +37,10 @@ public override bool RunTask () var assetPack = asset.GetMetadata ("AssetPack"); if (string.IsNullOrEmpty (assetPack) || string.Compare (assetPack, "base", StringComparison.OrdinalIgnoreCase) == 0) continue; + if (!IsAssetPackNameValid (assetPack)) { + Log.LogCodedError ("XA0140", $"The AssetPack value defined for {asset.ItemSpec} is invalid. '{assetPack}' should match the following Regex '[A-Za-z0-9_]'."); + continue; + } if (!assetPacks.TryGetValue (assetPack, out ITaskItem item)) { item = new TaskItem (assetPack); item.SetMetadata ("AssetPack", assetPack); @@ -72,7 +78,7 @@ public override bool RunTask () AssetPacks = assetPacks.Values.ToArray(); - return true; + return !Log.HasLoggedErrors; } bool IsDeliveryTypeValid (ITaskItem item, string deliveryType) @@ -84,5 +90,10 @@ bool IsDeliveryTypeValid (ITaskItem item, string deliveryType) } return true; } + + bool IsAssetPackNameValid (string assetPackName) + { + return validAssetPackName.IsMatch (assetPackName); + } } } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs index c922531c2ec..29bbea9403d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs @@ -33,6 +33,30 @@ public void BuildLibraryWithAssetPack ([Values (true, false)] bool isRelease) } } + [Test] + [Category ("SmokeTests")] + public void BuildApplicationWithAssetPackThatHasInvalidName ([Values (true, false)] bool isRelease) + { + var path = Path.Combine ("temp", TestName); + var app = new XamarinAndroidApplicationProject { + IsRelease = isRelease, + OtherBuildItems = { + new AndroidItem.AndroidAsset ("Assets\\asset1.txt") { + TextContent = () => "Asset1", + Encoding = Encoding.ASCII, + MetadataValues="AssetPack=asset-pack1", + }, + } + }; + app.SetProperty ("AndroidPackageFormat", "aab"); + using (var builder = CreateApkBuilder (Path.Combine (path, app.ProjectName))) { + builder.ThrowOnBuildFailure = false; + Assert.IsFalse (builder.Build (app), $"{app.ProjectName} should fail."); + StringAssertEx.Contains ("error XA0140:", builder.LastBuildOutput, + "Build Output did not contain error XA0140'."); + } + } + [Test] [Category ("SmokeTests")] public void BuildApplicationWithAssetPackOutsideProjectDirectory ([Values (true, false)] bool isRelease) From ef915f07002dd5a82c4587b460d7839d397ce258 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 14 Mar 2024 12:02:45 +0000 Subject: [PATCH 16/16] Remove unused property --- .../Tasks/Aapt2LinkAssetPack.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs index 0522f3394c0..7c9a1f16a5a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs @@ -29,8 +29,6 @@ public class Aapt2LinkAssetPack : Aapt2 { [Required] public ITaskItem OutputArchive { get; set; } - public string OutputFormat { get; set; } = "proto"; - protected override int GetRequiredDaemonInstances () { return Math.Min (1, DaemonMaxInstanceCount); @@ -40,7 +38,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () { RunAapt (GenerateCommandLineCommands (Manifest, OutputArchive), OutputArchive.ItemSpec); ProcessOutput (); - if (OutputFormat == "proto" && File.Exists (OutputArchive.ItemSpec)) { + if (File.Exists (OutputArchive.ItemSpec)) { // move the manifest to the right place. using (var zip = new ZipArchiveEx (OutputArchive.ItemSpec, File.Exists (OutputArchive.ItemSpec) ? FileMode.Open : FileMode.Create)) { zip.MoveEntry ("AndroidManifest.xml", "manifest/AndroidManifest.xml"); @@ -61,9 +59,7 @@ protected string[] GenerateCommandLineCommands (ITaskItem manifest, ITaskItem ou cmd.Add ("-v"); cmd.Add ("--manifest"); cmd.Add (GetFullPath (manifest.ItemSpec)); - if (OutputFormat == "proto") { - cmd.Add ("--proto-format"); - } + cmd.Add ("--proto-format"); cmd.Add ("--custom-package"); cmd.Add (PackageName); foreach (var assetDirectory in AssetDirectories) {