-
Notifications
You must be signed in to change notification settings - Fork 552
Ignore split configs when bundle config moves shared libraries to base.apk #8987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
591d4a7
Fixes the bundle issue
grendello 579a3a6
Test fixes + add issue test
grendello 21fb10d
Fixlets
grendello f9efc83
Better run code
grendello 58d09d7
Merge branch 'main' into dev/grendel/bundle-and-blobs
grendello 5b87b56
Change test category
grendello File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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
190 changes: 190 additions & 0 deletions
190
src/Xamarin.Android.Build.Tasks/Utilities/BundleConfigSplitConfigsChecker.cs
This file contains hidden or 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,190 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text; | ||
using System.Text.Json; | ||
|
||
using Microsoft.Build.Utilities; | ||
|
||
/// <para> | ||
/// When bundle configuration uses standard settings for split configs, the per-ABI library | ||
/// directory (which contains all of our DSOs/assemblies/blobs etc) will be placed in a per-ABI | ||
/// split config file named `split_config.{ARCH}.apk` and we use the fact to optimize startup | ||
/// time. | ||
/// </para> | ||
/// | ||
/// <para> | ||
/// However, if a custom build config file with the following settings is found, Android bundletool | ||
/// doesn't create the per-ABI split config file, and so we need to search all the files in order | ||
/// to find shared libraries, assemblies/blobs etc: | ||
/// <code> | ||
/// { | ||
/// "optimizations": { | ||
/// "splitsConfig": { | ||
/// "splitDimension": [ | ||
/// { | ||
/// "value": "ABI", | ||
/// "negate": true | ||
/// } | ||
/// ], | ||
/// } | ||
/// } | ||
/// } | ||
///</code></para> | ||
/// | ||
/// <para> | ||
/// The presence or absence of split config files is checked in our Java startup code which will | ||
/// notice that split configs are present, but will not check (for performance reasons, to avoid | ||
/// string comparisons) whether the per-ABI split config is present. We, therefore, need to let | ||
/// our native runtime know in some inexpensive way that the split configs should be ignored and | ||
/// that the DSOs/assemblies/blobs should be searched for in the usual, non-split config, way. | ||
/// </para> | ||
/// | ||
/// <para> | ||
/// Since we know at build time whether this is the case, it's best to record the fact then and | ||
/// let the native runtime merely check a boolean flag instead of dynamic detection at each app | ||
/// startup. | ||
/// </para> | ||
static class BundleConfigSplitConfigsChecker | ||
{ | ||
enum BundleConfigObject | ||
{ | ||
None, | ||
Root, | ||
Other, | ||
Optimizations, | ||
SplitsConfig, | ||
SplitDimension, | ||
} | ||
|
||
ref struct Strings { | ||
public readonly ReadOnlySpan<byte> UTF8BOM; | ||
public readonly ReadOnlySpan<byte> ValuePropertyName; | ||
public readonly ReadOnlySpan<byte> NegatePropertyName; | ||
|
||
public Strings () | ||
{ | ||
UTF8BOM = new byte[] { 0xEF, 0xBB, 0xBF }; | ||
ValuePropertyName = Encoding.ASCII.GetBytes ("value"); | ||
NegatePropertyName = Encoding.ASCII.GetBytes ("negate"); | ||
} | ||
} | ||
|
||
public static bool ShouldIgnoreSplitConfigs (TaskLoggingHelper log, string configFilePath) | ||
{ | ||
try { | ||
return DoShouldIgnoreSplitConfigs (log, configFilePath); | ||
} catch (Exception ex) { | ||
log.LogWarning ($"Failed to process bundle config file '{configFilePath}', split config files will be ignored at run time."); | ||
log.LogWarningFromException (ex); | ||
return true; | ||
} | ||
} | ||
|
||
static bool DoShouldIgnoreSplitConfigs (TaskLoggingHelper log, string configFilePath) | ||
{ | ||
var options = new JsonReaderOptions { | ||
AllowTrailingCommas = true, | ||
CommentHandling = JsonCommentHandling.Skip | ||
}; | ||
|
||
Strings strings = new (); | ||
ReadOnlySpan<byte> json = File.ReadAllBytes (configFilePath); | ||
if (json.StartsWith (strings.UTF8BOM)) { | ||
json = json.Slice (strings.UTF8BOM.Length); | ||
} | ||
|
||
var state = new Stack<BundleConfigObject> (); | ||
state.Push (BundleConfigObject.None); | ||
|
||
bool? valueIsAbi = null; | ||
bool? negate = null; | ||
string? lastPropertyName = null; | ||
var reader = new Utf8JsonReader (json, options); | ||
while (reader.Read ()) { | ||
JsonTokenType tokenType = reader.TokenType; | ||
|
||
switch (tokenType) { | ||
case JsonTokenType.StartObject: | ||
TransitionState (strings, reader, state, lastPropertyName); | ||
lastPropertyName = null; | ||
break; | ||
|
||
case JsonTokenType.EndObject: | ||
if (state.Peek () != BundleConfigObject.None) { | ||
BundleConfigObject popped = state.Pop (); | ||
} | ||
lastPropertyName = null; | ||
break; | ||
|
||
case JsonTokenType.PropertyName: | ||
lastPropertyName = reader.GetString (); | ||
if (state.Peek () == BundleConfigObject.SplitDimension) { | ||
CheckSplitDimensionProperty (reader, strings, ref valueIsAbi, ref negate); | ||
} | ||
break; | ||
} | ||
} | ||
|
||
if (!valueIsAbi.HasValue || !negate.HasValue) { | ||
return false; | ||
} | ||
|
||
return valueIsAbi.Value && negate.Value; | ||
} | ||
|
||
static void CheckSplitDimensionProperty (Utf8JsonReader reader, Strings strings, ref bool? valueIsAbi, ref bool? negate) | ||
{ | ||
if (!valueIsAbi.HasValue) { | ||
if (reader.ValueTextEquals (strings.ValuePropertyName)) { | ||
reader.Read (); | ||
string v = reader.GetString (); | ||
valueIsAbi = String.CompareOrdinal ("ABI", v) == 0; | ||
return; | ||
} | ||
} | ||
|
||
if (negate.HasValue) { | ||
return; | ||
} | ||
|
||
if (reader.ValueTextEquals (strings.NegatePropertyName)) { | ||
reader.Read (); | ||
negate = reader.GetBoolean (); | ||
} | ||
} | ||
|
||
static void TransitionState (Strings strings, Utf8JsonReader reader, Stack<BundleConfigObject> state, string? objectName) | ||
{ | ||
BundleConfigObject current = state.Peek (); | ||
if (current == BundleConfigObject.None) { | ||
state.Push (BundleConfigObject.Root); | ||
return; | ||
} | ||
|
||
BundleConfigObject need = current switch { | ||
BundleConfigObject.Root => BundleConfigObject.Optimizations, | ||
BundleConfigObject.Optimizations => BundleConfigObject.SplitsConfig, | ||
BundleConfigObject.SplitsConfig => BundleConfigObject.SplitDimension, | ||
_ => BundleConfigObject.Other | ||
}; | ||
|
||
if (need == BundleConfigObject.Other) { | ||
state.Push (need); | ||
return; | ||
} | ||
|
||
string needName = need switch { | ||
BundleConfigObject.Optimizations => "optimizations", | ||
BundleConfigObject.SplitsConfig => "splitsConfig", | ||
BundleConfigObject.SplitDimension => "splitDimension", | ||
_ => throw new InvalidOperationException ($"Internal error: unsupported state transition to '{need}'") | ||
}; | ||
|
||
if (!String.IsNullOrEmpty (objectName) && String.CompareOrdinal (needName, objectName) == 0) { | ||
state.Push (need); | ||
} else { | ||
state.Push (BundleConfigObject.Other); | ||
} | ||
} | ||
} |
This file contains hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.