Skip to content

Commit 2ba9af5

Browse files
committed
[api-compat] Provide API diffs around API breakage
Context: #4356 Context: 54beb90 Context: a20be39 The use of `Microsoft.DotNet.ApiCompat.exe` added in 07e7477 has one major deficiency: The error messages reported by `Microsoft.DotNet.ApiCompat.exe` are *awful* and borderline useless or misleading. For example, consider commit PR #4356, which attempts to bring sanity and consistency around `$(AndroidPlatformId)` and `Mono.Android.dll` builds. It contains an API break, which we'll hand wave away and accept for preview release purposes, in which the property type for `Android.Telephony.CellInfoGsm.CellIdentity` changes from `CellIdentityGsm` to `CellIdentity`: // API-29 namespace Android.Telephony { public sealed partial class CellInfoGsm: Android.Telephony.CellInfo, Android.OS.IParcelable { public unsafe Android.Telephony.CellIdentityGsm CellIdentity { } } // API-R namespace Android.Telephony { public sealed partial class CellInfoGsm : Android.Telephony.CellInfo, Android.OS.IParcelable { public unsafe Android.Telephony.CellIdentity CellIdentity { } } This is clearly a break. How does `Microsoft.DotNet.ApiCompat.exe` report the breakage? error : MembersMustExist : Member 'Android.Telephony.CellInfoGsm.CellIdentity.get()' does not exist in the implementation but it does exist in the contract. Which is infuriatingly terrible. The message *implies* that `Android.Telephony.CellInfoGsm.get_CellIdentity()` doesn't exist, but it *does* exist. The problem is that the return type changed! Or consider 54beb90, in which we now emit a slew of default interface members within the `Mono.Android.dll` binding, which *should* be API compatible. `Microsoft.DotNet.ApiCompat.exe` complains as well: InterfacesShouldHaveSameMembers : Interface member 'Java.Util.Functions.IUnaryOperator.Identity()' is present in the implementation but not in the contract. What these messages have in common is that they provide no context, lack important types, and in no way suggest how to *fix* the error other than to just ignore it. Overhaul this infrastructure so that crucial context is provided. The context is provided by using "reference assembly source": the [`Microsoft.DotNet.GenAPI.exe` utility][0] can be run on an assembly to generate C# source code that shows the same API but no implementation: namespace Android.Accounts { [Android.Runtime.RegisterAttribute("android/accounts/AbstractAccountAuthenticator", DoNotGenerateAcw=true, ApiSince=5)] public abstract partial class AbstractAccountAuthenticator : Java.Lang.Object { [Android.Runtime.RegisterAttribute("KEY_CUSTOM_TOKEN_EXPIRY", ApiSince=23)] public const string KeyCustomTokenExpiry = "android.accounts.expiry"; [Android.Runtime.RegisterAttribute(".ctor", "(Landroid/content/Context;)V", "")] public AbstractAccountAuthenticator(Android.Content.Context context) { } Update the `src/Mono.Android` build so that after every build, when `Microsoft.DotNet.ApiCompat.exe` fails we *also* run `Microsoft.DotNet.GenAPI.exe` on the generated assembly, then run `git diff -u` against the recently created assembly and the reference assembly source for the contract assembly. This allows us to get *useful diffs* in the API: Task "Exec" (TaskId:570) Task Parameter:Command=git diff --no-index "../../tests/api-compatibility/reference/Mono.Android.dll.cs" "/Volumes/Xamarin-Work/xamarin-android/bin/Debug/lib/xamarin.android/xbuild-frameworks/MonoAndroid/v10.0/Mono.Android.dll.cs" (TaskId:570) diff -u "../../tests/api-compatibility/reference/Mono.Android.dll.cs" "/Volumes/Xamarin-Work/xamarin-android/bin/Debug/lib/xamarin.android/xbuild-frameworks/MonoAndroid/v10.0/Mono.Android.dll.cs" (TaskId:570) --- ../../tests/api-compatibility/reference/Mono.Android.dll.cs 2020-03-05 13:20:59.000000000 -0500 (TaskId:570) +++ /Volumes/Xamarin-Work/xamarin-android/bin/Debug/lib/xamarin.android/xbuild-frameworks/MonoAndroid/v10.0/Mono.Android.dll.cs 2020-03-05 13:40:12.000000000 -0500 (TaskId:570) @@ -27,7 +27,7 @@ (TaskId:570) { (TaskId:570) [Android.Runtime.RegisterAttribute("ACCEPT_HANDOVER", ApiSince=28)] (TaskId:570) public const string AcceptHandover = "android.permission.ACCEPT_HANDOVER"; (TaskId:570) - [Android.Runtime.RegisterAttribute("ACCESS_BACKGROUND_LOCATION")] (TaskId:570) + [Android.Runtime.RegisterAttribute("ACCESS_BACKGROUND_LOCATION", ApiSince=29)] (TaskId:570) (The above changes are courtesy commmit 4cd2060, which added `RegisterAttribute.ApiSince` on a large number of members.) Finally, how do we update the "contract" `Mono.Android.dll` assembly? Add a new `tests/api-compatibility/api-compatibility.targets` file which contains a `UpdateMonoAndroidContract` target which will update `tests/api-compatibility/reference/Mono.Android.zip` with the contents of a `cil-strip`'d `Mono.Android.dll` and updated "reference assembly source". `Mono.Android.zip` contains a contract from Xamarin.Android 10.2.0.100 for `$(TargetFrameworkVersion)` v10.0. [0]: https://github.com/dotnet/arcade/tree/bc4fa8e7149769db4efd466f160417a32b11f0bf/src/Microsoft.DotNet.GenAPI
1 parent 12df2be commit 2ba9af5

File tree

9 files changed

+91
-6
lines changed

9 files changed

+91
-6
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props
2424
external/monodroid/
2525
external/mono/
2626
tests/api-compatibility/reference/*.dll
27+
tests/api-compatibility/reference/*.cs
2728
Novell

Before.Xamarin.Android.sln.targets

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
<Import Project="$(MSBuildThisFileDirectory)build-tools\scripts\ImportExportDocs.targets" />
44
<Import Project="$(MSBuildThisFileDirectory)build-tools\scripts\PrepareWindows.targets" Condition=" '$(OS)' == 'Windows_NT' " />
55
<Import Project="$(MSBuildThisFileDirectory)build-tools\scripts\RunTests.targets" />
6+
<Import Project="$(MSBuildThisFileDirectory)tests\api-compatibility\api-compatibility.targets" />
67
</Project>

build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/Zip.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ public override bool Execute ()
5757
if (prefix != null && entryDir.StartsWith (prefix, StringComparison.OrdinalIgnoreCase)) {
5858
zipDir = entryDir.Substring (prefix.Length);
5959
}
60-
zip.AddFileToDirectory (entryPath, zipDir, useFileDirectory: false);
60+
if (string.IsNullOrEmpty (zipDir)) {
61+
// JonP can't figure out how to actually clear the archive directory name
62+
// using AddFileToDirectory(). This works as desired.
63+
zip.AddFile (entryPath, Path.GetFileName (entryPath));
64+
} else {
65+
zip.AddFileToDirectory (entryPath, zipDir, useFileDirectory: false);
66+
}
6167
}
6268
}
6369
return !Log.HasLoggedErrors;

build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/Git.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ protected virtual bool LogTaskMessages {
2626
get { return true; }
2727
}
2828

29+
protected virtual bool PreserveOutput {
30+
get { return true; }
31+
}
32+
2933
protected override string ToolBaseName {
3034
get { return "git"; }
3135
}
@@ -68,7 +72,9 @@ protected override string GetWorkingDirectory ()
6872
protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance)
6973
{
7074
base.LogEventsFromTextOutput (singleLine, messageImportance);
71-
Lines.Add (singleLine);
75+
if (PreserveOutput) {
76+
Lines.Add (singleLine);
77+
}
7278
}
7379
}
7480
}

src/Mono.Android/Mono.Android.targets

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<UsingTask AssemblyFile="$(PrepTasksAssembly)" TaskName="Xamarin.Android.BuildTools.PrepTasks.GitDiff" />
34
<UsingTask AssemblyFile="$(PrepTasksAssembly)" TaskName="Xamarin.Android.BuildTools.PrepTasks.ReplaceFileContents" />
45
<UsingTask AssemblyFile="$(BootstrapTasksAssembly)" TaskName="Xamarin.Android.Tools.BootstrapTasks.CheckApiCompatibility" />
56
<Import Project="..\..\build-tools\scripts\XAVersionInfo.targets" />
@@ -174,23 +175,41 @@
174175
<ApiCompatibilityFiles Include="$(ApiCompatibilityDir)/reference/*.*" />
175176
</ItemGroup>
176177
<Target
177-
Name="AfterBuild"
178+
Name="_CheckApiCompatibility"
178179
Condition=" '$(DisableApiCompatibilityCheck)' != 'True' "
180+
AfterTargets="Build"
179181
Inputs="$(TargetPath);@(ApiCompatibilityFiles)"
180182
Outputs="$(IntermediateOutputPath)CheckApiCompatibility.stamp">
181183
<CheckApiCompatibility
184+
ContinueOnError="ErrorAndContinue"
182185
ApiCompatPath="$(XAPackagesDir)\microsoft.dotnet.apicompat\5.0.0-beta.20078.1\tools\net472\"
183186
ApiLevel="$(AndroidFrameworkVersion)"
184187
LastStableApiLevel="$(AndroidLatestStableFrameworkVersion)"
185188
TargetImplementationPath="$(OutputPath)"
186189
ApiCompatibilityPath="$(ApiCompatibilityDir)"
187190
/>
188-
<Touch
189-
Files="$(IntermediateOutputPath)CheckApiCompatibility.stamp"
190-
AlwaysCreate="True"
191+
<PropertyGroup>
192+
<_RunApiDiff Condition=" '$(MSBuildLastTaskResult)' == 'False' ">True</_RunApiDiff>
193+
<_GenAPI>"$(XAPackagesDir)\microsoft.dotnet.genapi\5.0.0-beta.20078.1\tools\net472\Microsoft.DotNet.GenAPI.exe"</_GenAPI>
194+
<_ContractRefSrc>"$(ApiCompatibilityDir)\reference\Mono.Android.dll.cs"</_ContractRefSrc>
195+
<_TargetRefSrc>"$(TargetPath).cs"</_TargetRefSrc>
196+
</PropertyGroup>
197+
<Exec
198+
Condition=" '$(_RunApiDiff)' == 'True' "
199+
Command="$(ManagedRuntime) $(ManagedRuntimeArgs) $(_GenAPI) &quot;$(TargetPath)&quot; > $(_TargetRefSrc)"
200+
/>
201+
<GitDiff
202+
Condition=" '$(_RunApiDiff)' == 'True' "
203+
ContinueOnError="ErrorAndContinue"
204+
Arguments="--no-index $(_ContractRefSrc) $(_TargetRefSrc)"
205+
WorkingDirectory="$(MSBuildThisFileDirectory)"
191206
/>
192207
<ItemGroup>
193208
<FileWrites Include="$(IntermediateOutputPath)CheckApiCompatibility.stamp" />
194209
</ItemGroup>
210+
<Touch
211+
Files="$(IntermediateOutputPath)CheckApiCompatibility.stamp"
212+
AlwaysCreate="True"
213+
/>
195214
</Target>
196215
</Project>

tests/api-compatibility/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ current version of an assembly. We could also called it the V2 assembly.
1616
[mdac]: https://github.com/dotnet/arcade/tree/master/src/Microsoft.DotNet.ApiCompat
1717

1818

19+
## Update Contract Assembly
20+
21+
To update the contract assembly, run the `UpdateMonoAndroidContract` target
22+
and provide the `$(ContractAssembly)` MSBuild property. `$(ContractAssembly)`
23+
should be the path to the new contract assembly to use:
24+
25+
msbuild Xamarin.Android.sln /t:UpdateMonoAndroidContract '/p:ContractAssembly=/Users/example/Downloads/\$ReferenceAssemblies/Microsoft/Framework/MonoAndroid/v10.0/Mono.Android.dll'
26+
27+
1928
## Build Task
2029

2130
We have developed a build task that will wrap *Microsoft.DotNet.ApiCompat.exe*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="..\..\Configuration.props" />
4+
<UsingTask AssemblyFile="$(BootstrapTasksAssembly)" TaskName="Xamarin.Android.Tools.BootstrapTasks.Zip" />
5+
<Target Name="UpdateMonoAndroidContract">
6+
<Error
7+
Condition=" '$(ContractAssembly)' == '' "
8+
Text="Please set the %24(ContractAssembly) property!"
9+
/>
10+
<PropertyGroup>
11+
<_GenAPI>"$(XAPackagesDir)\microsoft.dotnet.genapi\5.0.0-beta.20078.1\tools\net472\Microsoft.DotNet.GenAPI.exe"</_GenAPI>
12+
<_CilStrip>"$(XAInstallPrefix)\xbuild\Xamarin\Android\cil-strip.exe"</_CilStrip>
13+
<_ContractRefDll>$(MSBuildThisFileDirectory)\reference\Mono.Android.dll</_ContractRefDll>
14+
<_ContractRefSrc>$(MSBuildThisFileDirectory)\reference\Mono.Android.dll.cs</_ContractRefSrc>
15+
</PropertyGroup>
16+
<Copy
17+
SourceFiles="$(ContractAssembly)"
18+
DestinationFiles="$(_ContractRefDll)"
19+
/>
20+
<Exec
21+
Command="$(ManagedRuntime) $(ManagedRuntimeArgs) $(_GenAPI) &quot;$(_ContractRefDll)&quot; > &quot;$(_ContractRefSrc)&quot;"
22+
/>
23+
<Exec
24+
Command="$(ManagedRuntime) $(ManagedRuntimeArgs) $(_CilStrip) &quot;$(_ContractRefDll)&quot;"
25+
/>
26+
<ItemGroup>
27+
<_ZipEntry Include="$(_ContractRefDll)" />
28+
<_ZipEntry Include="$(_ContractRefSrc)" />
29+
</ItemGroup>
30+
<Zip
31+
File="$(MSBuildThisFileDirectory)\reference\Mono.Android.zip"
32+
Entries="@(_ZipEntry)"
33+
Prefix="$(MSBuildThisFileDirectory)\reference\"
34+
Overwrite="True"
35+
/>
36+
<!--
37+
<Exec
38+
Command="zip Mono.Android.zip Mono.Android.dll Mono.Android.dll.cs"
39+
WorkingDirectory="$(MSBuildThisFileDirectory)\reference"
40+
/>
41+
-->
42+
</Target>
43+
</Project>
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)