55using System . Text ;
66
77using Microsoft . Android . Build . Tasks ;
8+ using Microsoft . Build . Framework ;
89using Microsoft . Build . Utilities ;
910
1011using Xamarin . Android . Tasks . LLVMIR ;
@@ -17,19 +18,107 @@ namespace Xamarin.Android.Tasks;
1718
1819class PreservePinvokesNativeAssemblyGenerator : LlvmIrComposer
1920{
20- readonly TaskLoggingHelper log ;
21- readonly AndroidTargetArch targetArch ;
22- readonly ICollection < PinvokeScanner . PinvokeEntryInfo > pinfos ;
21+ // Maps a component name after ridding it of the `lib` prefix and the extension to a "canonical"
22+ // name of a library, as used in `[DllImport]` attributes.
23+ readonly Dictionary < string , string > libraryNameMap = new ( StringComparer . Ordinal ) {
24+ { "xa-java-interop" , "java-interop" } ,
25+ { "mono-android.release-static" , String . Empty } ,
26+ { "mono-android.release" , String . Empty } ,
27+ } ;
2328
24- public PreservePinvokesNativeAssemblyGenerator ( TaskLoggingHelper log , AndroidTargetArch targetArch , ICollection < PinvokeScanner . PinvokeEntryInfo > pinfos )
29+ readonly NativeCodeGenState state ;
30+ readonly ITaskItem [ ] monoComponents ;
31+
32+ public PreservePinvokesNativeAssemblyGenerator ( TaskLoggingHelper log , NativeCodeGenState codeGenState , ITaskItem [ ] monoComponents )
2533 : base ( log )
2634 {
27- this . log = log ;
28- this . targetArch = targetArch ;
29- this . pinfos = pinfos ;
35+ if ( codeGenState . PinvokeInfos == null ) {
36+ throw new InvalidOperationException ( $ "Internal error: { nameof ( codeGenState ) } `{ nameof ( codeGenState . PinvokeInfos ) } ` property is `null`") ;
37+ }
38+
39+ this . state = codeGenState ;
40+ this . monoComponents = monoComponents ;
3041 }
3142
3243 protected override void Construct ( LlvmIrModule module )
3344 {
45+ Log . LogDebugMessage ( "Constructing p/invoke preserve code" ) ;
46+ List < PinvokeScanner . PinvokeEntryInfo > pinvokeInfos = state . PinvokeInfos ! ;
47+ if ( pinvokeInfos . Count == 0 ) {
48+ // This is a very unlikely scenario, but we will work just fine. The module that this generator produces will merely result
49+ // in an empty (but valid) .ll file and an "empty" object file to link into the shared library.
50+ return ;
51+ }
52+
53+ Log . LogDebugMessage ( " Looking for enabled native components" ) ;
54+ var componentNames = new List < string > ( ) ;
55+ var nativeComponents = new NativeRuntimeComponents ( monoComponents ) ;
56+ foreach ( NativeRuntimeComponents . Archive archiveItem in nativeComponents . KnownArchives ) {
57+ if ( ! archiveItem . Include ) {
58+ continue ;
59+ }
60+
61+ Log . LogDebugMessage ( $ " { archiveItem . Name } ") ;
62+ componentNames . Add ( archiveItem . Name ) ;
63+ }
64+
65+ if ( componentNames . Count == 0 ) {
66+ Log . LogDebugMessage ( "No native framework components are included in the build, not scanning for p/invoke usage" ) ;
67+ return ;
68+ }
69+
70+ Log . LogDebugMessage ( " Checking discovered p/invokes against the list of components" ) ;
71+ foreach ( PinvokeScanner . PinvokeEntryInfo pinfo in pinvokeInfos ) {
72+ Log . LogDebugMessage ( $ " p/invoke: { pinfo . EntryName } in { pinfo . LibraryName } ") ;
73+ if ( MustPreserve ( pinfo , componentNames ) ) {
74+ Log . LogDebugMessage ( " must be preserved" ) ;
75+ } else {
76+ Log . LogDebugMessage ( " no need to preserve" ) ;
77+ }
78+ }
79+ }
80+
81+ // Returns `true` for all p/invokes that we know are part of our set of components, otherwise returns `false`.
82+ // Returning `false` merely means that the p/invoke isn't in any of BCL or our code and therefore we shouldn't
83+ // care. It doesn't mean the p/invoke will be removed in any way.
84+ bool MustPreserve ( PinvokeScanner . PinvokeEntryInfo pinfo , List < string > components )
85+ {
86+ if ( String . Compare ( "xa-internal-api" , pinfo . LibraryName , StringComparison . Ordinal ) == 0 ) {
87+ return true ;
88+ }
89+
90+ foreach ( string component in components ) {
91+ // The most common pattern for the BCL - file name without extension
92+ string componentName = Path . GetFileNameWithoutExtension ( component ) ;
93+ if ( Matches ( pinfo . LibraryName , componentName ) ) {
94+ return true ;
95+ }
96+
97+ // If it starts with `lib`, drop the prefix
98+ if ( componentName . StartsWith ( "lib" , StringComparison . Ordinal ) ) {
99+ if ( Matches ( pinfo . LibraryName , componentName . Substring ( 3 ) ) ) {
100+ return true ;
101+ }
102+ }
103+
104+ // Might require mapping of component name to a canonical one
105+ if ( libraryNameMap . TryGetValue ( componentName , out string ? mappedComponentName ) && ! String . IsNullOrEmpty ( mappedComponentName ) ) {
106+ if ( Matches ( pinfo . LibraryName , mappedComponentName ) ) {
107+ return true ;
108+ }
109+ }
110+
111+ // Try full file name, as the last resort
112+ if ( Matches ( pinfo . LibraryName , Path . GetFileName ( component ) ) ) {
113+ return true ;
114+ }
115+ }
116+
117+ return false ;
118+
119+ bool Matches ( string libraryName , string componentName )
120+ {
121+ return String . Compare ( libraryName , componentName , StringComparison . Ordinal ) == 0 ;
122+ }
34123 }
35124}
0 commit comments