forked from dotnet/android
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Xamarin.Andorid.Build.Tasks] First Pass on Dynamic Asset Features
Context dotnet#4810 This is the first implementation for supporting building Dynamic Feature Assets modules for Android. This idea is to have these "features" as standard Xamarin Android Library projects. The `ProjectReference` will need to use the following ```xml <ItemGroup> <ProjectReference Include="Features\AssetsFeature\AssetsFeature.csproj"> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> <AndroidDynamicFeature>true</AndroidDynamicFeature> </ProjectReference> </ItemGroup> ``` We set `ReferenceOutputAssembly` to `false` to ensure that the assembly for the feature is NOT included in the final aab file. The new meta Data `AndroidDynamicFeature` allows the build system to pick up project references which are "features". As part of the final packaging step of the main app we will gather up all the `ProjectRefernce` items which have `AndroidDynamicFeature` set to `true` (and maybe `ReferenceOutputAssembly` set to `false`). This will be done by the `_BuildDynamicFeatures` which will run just after `_CreateBaseApk`. It will call `_GetDynamicFeatureOutputs` for each `ProjectReference` which will collect the `output` files for each feature. It will then call the `BuildDynamicFeature` target via the `MSBuild` task for each `ProjectReference`. The `BuildDynamicFeature` is the target responsible for collecting all the assets and packaging them using `aapt2` up into a zip. Once all the `BuildDynamicFeature` calls are complete the created `zip` files will be added to the `AndroidAppBundleModules` and then included in the final `aab` file. It might seem odd that the feature projects are built after the main app. However this is required because the feature needs to use the `packaged_resources` file as an input to `aapt2` when building the feature. This is why the `_BuildDynamicFeatures` happens AFTER `_CreateBaseApk`. It is only at that point that the final `packaged_resources` file exists. One of the very weird things is that the feature zip needs to be built using the `aapt2` `--static-lib` flag. As a result we need to call `aapt2 convert` on the final zip. This is because it is in the `apk` `binary` format and needs to be converted over to the `aab` `proto` format. So there is a new `Aapt2Convert` task which handles that job. It also makes sure the `AndroidManifest.xml` file is in the right place when converting to `proto` format. A basic project example using .net 6 for a feature would look like this. ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-android</TargetFramework> </PropertyGroup> </Project> ``` As you can see it is just a normal library project. At this time is CANNOT contain any `AndroidResource` items such as drawables or layouts. It must only contain `AndroidAsset` items. So we probably should have a new template for a `Dynamic Feature` which just creates the `csproj` and the `Assets` folder. One sticking point is probably the `AndroidManifest.xml` file which we need for a `feature`. There is a sample ```xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:dist="http://schemas.android.com/apk/distribution" android:versionCode="1" android:versionName="1.0" package="com.companyname.DynamicAssetsExample" featureSplit="assetsfeature" android:isFeatureSplit="true"> <dist:module dist:title="@string/assetsfeature" dist:instant="false"> <dist:delivery> <dist:on-demand /> </dist:delivery> <dist:fusing dist:include="false" /> </dist:module> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" /> <application android:hasCode="false" tools:replace="android:hasCode" /> </manifest> ``` The interesting parts are all the additional `dist` elements. What we can probably do is auto generate this during the `BuildDynamicFeature`. However we need to think carefully about this since if we plan to have code and `Activities` in the feature at some point, those will also need to end up in the `AndroidManifest.xml`. For additional information on the Play Core Dynamic Features check the following links. [1] https://developer.android.com/guide/playcore/asset-delivery [2] https://developer.android.com/guide/playcore/feature-delivery
- Loading branch information
1 parent
9808988
commit 3d45abf
Showing
20 changed files
with
1,312 additions
and
33 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
...amarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.DynamicFeature.targets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
|
||
<UsingTask TaskName="Xamarin.Android.Tasks.CalculatePackageIdsForFeatures" AssemblyFile="Xamarin.Android.Build.Tasks.dll" /> | ||
<!-- Andorid Feature Build System--> | ||
<PropertyGroup> | ||
<_BuildDynamicFeaturesInParallel>False</_BuildDynamicFeaturesInParallel> | ||
</PropertyGroup> | ||
<Target Name="_CheckIsDynamicFeature" | ||
Returns="@(_DynamicFeaetureProjectMetadata)" Condition=" '$(FeatureSplitName)' != '' Or '$(FeatureType)' != '' "> | ||
<ItemGroup> | ||
<_DynamicFeaetureProjectMetadata Include="$(MSBuildProjectFullPath)"> | ||
<IsDynamicFeature>true</IsDynamicFeature> | ||
<FeaturePackage>$(MSBuildProjectDirectory)\$(_FeaturePackage)</FeaturePackage> | ||
<FeatureType>$(FeatureType)</FeatureType> | ||
<_BaseZipIntermediate>$(IntermediateOutputPath)\$(MSBuildProjectName).zip</_BaseZipIntermediate> | ||
</_DynamicFeaetureProjectMetadata> | ||
</ItemGroup> | ||
</Target> | ||
<Target Name="_ResolveAndroidDynamicFeatureProjects" BeforeTargets="AssignProjectConfiguration" Condition=" '$(AndroidApplication)' == 'true' "> | ||
<MSBuild | ||
Projects="@(ProjectReference)" | ||
Targets="_CheckIsDynamicFeature" | ||
BuildInParallel="$(_BuildDynamicFeaturesInParallel)" | ||
SkipNonexistentTargets="true" | ||
RebaseOutputs="true" | ||
> | ||
<Output TaskParameter="TargetOutputs" ItemName="_AndroidDynamicFeatureProjects" /> | ||
</MSBuild> | ||
<ItemGroup> | ||
<ProjectReference Remove="%(_AndroidDynamicFeatureProjects.OriginalItemSpec)" /> | ||
</ItemGroup> | ||
</Target> | ||
<Target Name="_GetDynamicFeatureOutputs" DependsOnTargets="_ResolveAndroidDynamicFeatureProjects"> | ||
<CalculatePackageIdsForFeatures | ||
FeatureProjects="@(_AndroidDynamicFeatureProjects)" | ||
> | ||
<Output TaskParameter="Output" ItemName="_AndroidDynamicFeature" /> | ||
</CalculatePackageIdsForFeatures> | ||
</Target> | ||
<Target Name="_BuildDynamicFeatures" | ||
AfterTargets="_CreateBaseApk" | ||
DependsOnTargets="_GetDynamicFeatureOutputs;_ConvertAppPackageForFeatureCompilation" | ||
Condition=" '$(AndroidPackageFormat)' == 'aab' And '$(AndroidApplication)' == 'true' " | ||
Inputs="@(_AndroidDynamicFeature)" | ||
Outputs="@(_AndroidDynamicFeatureProjects->'%(FeaturePackage)')"> | ||
<PropertyGroup> | ||
<_BuildArguments> | ||
Configuration=$(Configuration); | ||
BuildingFeature=True; | ||
PackageName=$(_AndroidPackage); | ||
AndroidPackageFormat=aab; | ||
JavaPlatformJarPath=$(JavaPlatformJarPath); | ||
Aapt2ToolPath=$(Aapt2ToolPath);Aapt2ToolExe=$(Aapt2ToolExe); | ||
_TargetSdkVersion=$(_TargetSdkVersion); | ||
_MinSdkVersion=$(_MinSdkVersion); | ||
_AndroidIncludeRuntime=False; | ||
_AppPackagedResources=$(MSBuildProjectDirectory)\$(_PackagedResources).ap_; | ||
_AppAssemblyDirectory=$(MSBuildProjectDirectory)\$(MonoAndroidIntermediateAssemblyDir); | ||
</_BuildArguments> | ||
</PropertyGroup> | ||
<MSBuild Projects="@(_AndroidDynamicFeature)" BuildInParallel="$(_BuildDynamicFeaturesInParallel)" Targets="Restore;BuildDynamicFeature" RebaseOutputs="true" | ||
Properties="$(_BuildArguments)"> | ||
<Output TaskParameter="TargetOutputs" ItemName="_FeatureAssemblies" /> | ||
</MSBuild> | ||
<ItemGroup> | ||
<AndroidAppBundleModules Include="%(_AndroidDynamicFeatureProjects.FeaturePackage)" /> | ||
</ItemGroup> | ||
</Target> | ||
|
||
<Target Name="_CleanDynamicFeatures" | ||
Condition="'@(_AndroidDynamicFeatureProjects.Count())' != '0' " | ||
DependsOnTargets="_CollectDynamicFeatures"> | ||
<MSBuild Projects="@(_AndroidDynamicFeatureProjects)" BuildInParallel="$(_BuildDynamicFeaturesInParallel)" Targets="Clean" RebaseOutputs="true" Properties="Configuration=$(Configuration)" /> | ||
</Target> | ||
<!-- Feature Specific Targets. --> | ||
|
||
<PropertyGroup> | ||
<_FeaturePackage>$(OutputPath)$(MSBuildProjectName).zip</_FeaturePackage> | ||
<_FeaturePackageTemp>$(IntermediateOutputPath)$(MSBuildProjectName).zip</_FeaturePackageTemp> | ||
</PropertyGroup> | ||
|
||
<Target Name="_SetupFeature"> | ||
<PropertyGroup> | ||
<FeaturePackageName Condition=" '$(FeaturePackageName)' == '' " >$(_AndroidPackage).$(MSBuildProjectName)</FeaturePackageName> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<_AdditionalApksToLink Include="$(_AppPackagedResources)" /> | ||
<Reference Include="$(_AppAssemblyDirectory)*.dll" AndroidSkipResourceExtraction="True" AndroidSkipAddToPackage="True" /> | ||
<_AppAssemblies Include="$(_AppAssemblyDirectory)*.dll" /> | ||
<ResolvedAssemblies Include="%(_AppAssemblies.Identity)" AndroidSkipResourceExtraction="True" AndroidSkipAddToPackage="True"> | ||
<DestinationSubPath>%(_AppAssemblies.Filename)</DestinationSubPath> | ||
</ResolvedAssemblies> | ||
</ItemGroup> | ||
</Target> | ||
<PropertyGroup> | ||
<BeforeBuildDynamicFeatureDependsOnTargets> | ||
_SetupFeature; | ||
_SetupDesignTimeBuildForBuild; | ||
_GenerateDynamicFeatureManifest; | ||
</BeforeBuildDynamicFeatureDependsOnTargets> | ||
<AfterBuildDynamicFeatureDependsOnTargets> | ||
;_ConvertFeaturePackage | ||
</AfterBuildDynamicFeatureDependsOnTargets> | ||
<CleanDependsOn> | ||
$(CleanDependsOn); | ||
_CleanDynamicFeatures; | ||
</CleanDependsOn> | ||
</PropertyGroup> | ||
<Target Name="BuildDynamicFeature" | ||
Inputs="$(_AndroidManifestAbs);@(AndroidAsset);$(_AppPackagedResources)" | ||
Outputs="$(_FeaturePackage)"> | ||
|
||
<PropertyGroup> | ||
<_FeatureTargets>$(BeforeBuildDynamicFeatureDependsOnTargets)</_FeatureTargets> | ||
<_FeatureTargets Condition="'$(FeatureType)' == 'Feature' And '@(Compile.Count())' != '0'" >$(_FeatureTargets);PackageForAndroid</_FeatureTargets> | ||
<_FeatureTargets Condition="'$(FeatureType)' == 'AssetPack' " >$(_FeatureTargets);_CreateFeaturePackageWithAapt2</_FeatureTargets> | ||
<_FeatureTargets>$(_FeatureTargets);$(AfterBuildDynamicFeatureDependsOnTargets)</_FeatureTargets> | ||
</PropertyGroup> | ||
<CallTarget Targets="$(_FeatureTargets)" /> | ||
</Target> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
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 Aapt2Convert : Aapt2 { | ||
public override string TaskPrefix => "A2C"; | ||
|
||
[Required] | ||
public ITaskItem [] Files { get; set; } | ||
[Required] | ||
public ITaskItem OutputArchive { get; set; } | ||
public string OutputFormat { get; set; } = "binary"; | ||
public string ExtraArgs { get; set; } | ||
|
||
protected override int GetRequiredDaemonInstances () | ||
{ | ||
return Math.Min (1, DaemonMaxInstanceCount); | ||
} | ||
|
||
public async override System.Threading.Tasks.Task RunTaskAsync () | ||
{ | ||
RunAapt (GenerateCommandLineCommands (Files, 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"); | ||
} | ||
} | ||
} | ||
|
||
protected string[] GenerateCommandLineCommands (IEnumerable<ITaskItem> files, ITaskItem output) | ||
{ | ||
List<string> cmd = new List<string> (); | ||
cmd.Add ("convert"); | ||
if (!string.IsNullOrEmpty (ExtraArgs)) | ||
cmd.Add (ExtraArgs); | ||
if (MonoAndroidHelper.LogInternalExceptions) | ||
cmd.Add ("-v"); | ||
if (!string.IsNullOrEmpty (OutputFormat)) { | ||
cmd.Add ("--output-format"); | ||
cmd.Add (OutputFormat); | ||
} | ||
cmd.Add ($"-o"); | ||
cmd.Add (GetFullPath (output.ItemSpec)); | ||
foreach (var file in files) { | ||
cmd.Add (GetFullPath (file.ItemSpec)); | ||
} | ||
return cmd.ToArray (); | ||
} | ||
} | ||
} |
Oops, something went wrong.