forked from ppy/osu
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathosu.Android.props
More file actions
203 lines (192 loc) · 14.1 KB
/
Copy pathosu.Android.props
File metadata and controls
203 lines (192 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<Project>
<PropertyGroup>
<SupportedOSPlatformVersion>33.0</SupportedOSPlatformVersion>
<!-- arm64-v8a only. Modern Android devices are 99%+ arm64; 32-bit arm32 (armeabi-v7a)
adds ~200 MB of duplicate native libraries for negligible device coverage.
x86 was already removed — modern x86_64 emulators run arm64 via translation. -->
<RuntimeIdentifiers>android-arm64</RuntimeIdentifiers>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<!-- CJK and Mideast encodings are needed for song metadata display.
Rare covers supplementary Unicode blocks. West is the default Latin set. -->
<MandroidI18n>CJK;Mideast;Rare;West;Other;</MandroidI18n>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidMessageHandler</AndroidHttpClientHandlerType>
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<!-- Do NOT enable AndroidEnableMarshalMethods on .NET 10 with profiled AOT + trimming.
The build-time P/Invoke stub generator can produce stubs that crash in native code
when combined with partial AOT and trimmed assemblies — the crash bypasses managed
exception handling entirely, resulting in silent SIGSEGV/SIGABRT with no crash logs.
This is especially dangerous for apps with complex interop (UnmanagedCallersOnly,
unmanaged function pointers, reflection-based handle discovery).
The default runtime-generated trampolines are slower but proven reliable. -->
<AndroidEnableMarshalMethods>false</AndroidEnableMarshalMethods>
<NoWarn>$(NoWarn);NU1608;XA4301</NoWarn>
</PropertyGroup>
<!-- Release-only optimisations.
History:
Trimming + AOT were once enabled together (PublishTrimmed + TrimMode=partial +
AndroidEnableProfiledAot, with SuppressTrimAnalysisWarnings=true masking the
build-time signal). That combination caused a reproducible "splash → black screen
→ crash, no logs" failure: only Microsoft.CSharp and ppy.Veldrid.SPIRV were rooted,
while osu.Game, the rulesets, Realm, Newtonsoft.Json, AutoMapper, Sentry, OsuTK
and other reflection-heavy assemblies were not. A trimmed method/type or an
un-AOT'd profiled-AOT call site threw TypeLoadException / MissingMethodException
early in OsuGame construction, before the file logger was open; the process died
on SDLThread / .NET ThreadPool with only a tombstone, so the user got no osu.log
and Logcat showed a bare native crash.
Current policy:
* AndroidEnableProfiledAot=true — re-enabled. The crash root cause was the
trimming side of the combination (untrimmed-but-AOT'd is safe — every method
the profile lists exists in the assembly because nothing was stripped; methods
outside the profile fall back to the JIT and are also still present). This
gives a measurable startup time reduction and cuts steady-state CPU on hot
managed paths (the JIT no longer needs to tier them up at runtime, and Mono's
profiled AOT covers framework/game hot loops including draw-thread Schedulers,
bindable propagation, and the texture upload path).
* PublishTrimmed stays OFF — re-enabling it requires explicitly rooting every
reflection-using assembly (osu.Game, rulesets, Realm, Newtonsoft.Json,
AutoMapper, Sentry, OsuTK, Microsoft.CSharp, ppy.Veldrid.SPIRV …) and running
with SuppressTrimAnalysisWarnings=false to surface gaps before shipping. That
is a separate, larger PR.
* AndroidEnableMarshalMethods stays OFF (see PropertyGroup above) — silent
SIGSEGV with current interop on .NET 10 Android.
* Server GC is enabled for Release: workstation GC's STW pauses on the Update/
Draw threads are a primary source of >1ms frame spikes that defeat our 1ms
frame-time goal. Server GC parallelises mark/sweep across cores and uses
per-core LOH/SOH allocation contexts, dramatically lowering pause time at
the cost of ~20-30% extra resident memory. Mono Android has supported this
since .NET 8; ppy/osu desktop already uses it. Concurrent GC is the .NET
default but we set it explicitly so Server GC runs in the background mode
rather than blocking foreground.
To re-enable trimming in the future, reintroduce it on its own, root every
reflection-using assembly explicitly, and run with SuppressTrimAnalysisWarnings=false
to surface the gaps before shipping. -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<!-- Compress managed assemblies inside the APK (LZ4). Android extracts them on first run
but the download/APK size is significantly smaller. -->
<AndroidEnableAssemblyCompression>true</AndroidEnableAssemblyCompression>
<!-- Don't ship PDB files in the APK — they add ~15-20 MB.
Stack traces still work via embedded metadata. -->
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<!-- Profiled AOT: pre-compile the hot methods listed in the bundled .NET / Android
profiles. Methods outside the profile remain JIT-compiled at runtime. NO trimming —
every method in every assembly stays present, so a profile-miss falls back to JIT
instead of throwing MissingMethodException like the previous trim+AOT combo did. -->
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
<!-- Concurrent Server GC. Server GC uses per-core allocation contexts and parallel
collections to reduce STW pause time, which is the largest source of >1ms frame
spikes on the Update/Draw threads. Memory usage rises ~20-30% in exchange. -->
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<!-- Defensive explicit-set: we rely on the SDK linker to walk only Android SDK types
(NOT user assemblies — see PublishTrimmed=off rationale above). The .NET Android
SDK default is currently 'SdkOnly' but has changed across SDK versions; making it
explicit here pins the behaviour so a future SDK bump cannot silently flip us to
'Full' linking and break the reflection-heavy code paths in osu.Game / Realm /
Newtonsoft.Json / AutoMapper / Sentry / OsuTK. -->
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<!-- Defensive explicit-set: keep IL alongside profiled-AOT native code so any method
outside the bundled AOT profile has a JIT fallback (instead of MissingMethodException
at first call). The .NET Android SDK default is 'false' for non-trimmed builds
already, but we make it explicit so a future SDK bump cannot silently start
stripping IL and reproduce the trim-then-AOT crash class we removed above. -->
<AndroidStripILAfterAOT>false</AndroidStripILAfterAOT>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.508.1" />
<!-- `ppy.osu.Framework.NativeLibs` is a transitive dependency of `ppy.osu.Framework`
that ships desktop-only natives (Linux/macOS/Windows) under `runtimes/<rid>/native/`
— including a bare Linux `libbass.so`/`libbass_fx.so`/`libbassmix.so` for linux-arm64.
The default .NET RID fallback graph routes `android-arm64` through
`linux-bionic-arm64` → `linux-arm64`, so NuGet resolves those Linux `.so` files for
an Android publish and the .NET Android SDK packs them into the APK's
`lib/arm64-v8a/` — replacing the correct Android arm64 natives from
`ppy.osu.Framework.Android`'s AAR (`jni/arm64-v8a/libbass*.so`). The Linux ELFs
reference GLIBC_* versioned symbols and cannot be loaded by Android's bionic
linker, crashing the app at startup with `System.DllNotFoundException: bass`.
Cutting the package's assets off at the NuGet level is the only reliable fix:
previous attempts to scrub items via an `AfterTargets="ResolveRuntimePackAssets;
ComputeFilesToPublish;ComputeResolvedFilesToPublishList"` target (see
`FixRuntimePackAssetTypes` below) consistently missed one of the many item
collections the .NET Android SDK consults during APK packaging, and the Linux
`.so` leaked into the APK (observed in build v2026.421.144 and again in v145
after a more thorough item filter). The package has no managed assets we need
— all Android runtime natives come from the `ppy.osu.Framework.Android` AAR —
so excluding it entirely is safe. -->
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2025.806.0-nativelibs"
ExcludeAssets="all" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
Since Realm objects are not declared directly in Android projects, simply disable Fody. -->
<DisableFody>true</DisableFody>
</PropertyGroup>
<!-- Fix for .NET 10 Android AOT misclassifying native .so assets in runtime pack.
Only .so files need the AssetType override — the previous broader filter
(all non-DLL, non-PDB) incorrectly reclassified signing metadata and config
files, which corrupted the APK signature (INSTALL_PARSE_FAILED_NO_CERTIFICATES).
Scoped to Release only to avoid interfering with Debug builds.
IMPORTANT: only Android-runtime .so files may be marked as native and packaged
into the APK. ppy.osu.Framework transitively depends on ppy.osu.Framework.NativeLibs
which ships desktop-only natives under runtimes/linux-arm64/native/, runtimes/osx/native/,
runtimes/win-*/native/ etc. — including a bare libbass.so, libbass_fx.so, libbassmix.so
for Linux. If any of those are marked AssetType=native, the .NET Android SDK packs them
into lib/arm64-v8a/ of the APK, racing with (and replacing) the proper Android arm64
libbass*.so coming from ppy.osu.Framework.Android's AAR (jni/arm64-v8a/). The Linux ELF
is linked against glibc and cannot be loaded by Android's bionic dynamic linker, which
surfaces at startup as System.DllNotFoundException: bass from AudioManager..ctor →
ManagedBass.Bass.get_DeviceCount, immediately crashing the app.
Implementation notes:
* Uses a two-step item helper list + Remove-by-identity pattern, which batches
over item metadata reliably (an earlier inline `Remove="@(X)" Condition="…"`
with a nested OR of Replace/Contains calls silently no-op'd — the Linux libbass
leaked into the APK in build v2026.421.144).
* Uses a single inverted match: any `.so` under `/runtimes/<rid>/native/` whose
RID does not start with `android` is stripped. This covers every non-Android
desktop/mobile RID in one rule and is future-proof against new RIDs.
* The path match uses forward-slash normalisation with the doubled-backslash
escape `'\\'` required inside MSBuild property-function string literals
(the previous `'\'` form is ambiguous to the expression parser).
* Applied to `ResolvedFileToPublish` (the primary input to the .NET Android SDK's
publish step) and also to `ReferenceCopyLocalPaths` / `RuntimeCopyLocalItems`
in case a NuGet package flows native assets through those collections. -->
<Target Name="FixRuntimePackAssetTypes" AfterTargets="ResolveRuntimePackAssets;ComputeFilesToPublish;ComputeResolvedFilesToPublishList"
Condition="'$(Configuration)' == 'Release'">
<ItemGroup>
<!-- Build filter lists of non-Android runtime .so files. The two-step pattern
(Include into helper list, then Remove by identity) is more reliable than a
conditional Remove because MSBuild batches per-item on metadata references
in the helper list's Condition, and the subsequent Remove is an unconditional
identity match. -->
<_DesktopNativeToStripFromPublish Include="@(ResolvedFileToPublish)"
Condition="'%(Extension)' == '.so'
AND $([System.String]::Copy('%(Identity)').Replace('\\','/').Contains('/runtimes/'))
AND !$([System.String]::Copy('%(Identity)').Replace('\\','/').Contains('/runtimes/android'))" />
<ResolvedFileToPublish Remove="@(_DesktopNativeToStripFromPublish)" />
<_DesktopNativeToStripFromRefCopy Include="@(ReferenceCopyLocalPaths)"
Condition="'%(Extension)' == '.so'
AND $([System.String]::Copy('%(Identity)').Replace('\\','/').Contains('/runtimes/'))
AND !$([System.String]::Copy('%(Identity)').Replace('\\','/').Contains('/runtimes/android'))" />
<ReferenceCopyLocalPaths Remove="@(_DesktopNativeToStripFromRefCopy)" />
<_DesktopNativeToStripFromRuntimeCopy Include="@(RuntimeCopyLocalItems)"
Condition="'%(Extension)' == '.so'
AND $([System.String]::Copy('%(Identity)').Replace('\\','/').Contains('/runtimes/'))
AND !$([System.String]::Copy('%(Identity)').Replace('\\','/').Contains('/runtimes/android'))" />
<RuntimeCopyLocalItems Remove="@(_DesktopNativeToStripFromRuntimeCopy)" />
<!-- Only mark Android-RID runtime pack assets as native. RuntimePackAsset items
carry an explicit RuntimeIdentifier metadata, so this filter is exact. -->
<RuntimePackAsset Update="@(RuntimePackAsset)"
Condition="'%(Extension)' == '.so'
AND $([System.String]::Copy('%(RuntimeIdentifier)').StartsWith('android'))">
<AssetType>native</AssetType>
</RuntimePackAsset>
<!-- Mark the surviving (Android-only after the Remove above) publish .so files as native
so the Android SDK packs them into lib/<abi>/ rather than dropping them as data. -->
<ResolvedFileToPublish Update="@(ResolvedFileToPublish)" Condition="'%(Extension)' == '.so'">
<AssetType>native</AssetType>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>