Skip to content
This repository was archived by the owner on Jul 13, 2026. It is now read-only.

Commit 76c076f

Browse files
authored
Add support for Project Specific RegisterTaskObject. (#199)
Context: dotnet/maui#11605 Context: dotnet/maui#11387 (comment) Context: http://github.com/xamarin/xamarin-android/commit/8bc7a3e84f95e70fe12790ac31ecd97957771cb2 In dotnet/maui#11605, when `$(AndroidEnableMarshalMethods)`=True (dotnet/android@8bc7a3e8), the build may fail if the `.sln` contains more than one Android "App" project. We tracked this down to "undesired sharing" between project builds; the `obj` provided to [`IBuildEngine4.RegisterTaskObject()`][0] can be visible across project builds. Consider [`<GeneratePackageManagerJava/>`][1]: var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<MarshalMethodsState> (".:!MarshalMethods!:.", RegisteredTaskObjectLifetime.Build); Further consider a `.sln` with two "App" projects, as the "App" project build hits `<GeneratePackageManagerJava/>`. The lifetime of `.Build` is *not* tied to the the `Build` target of a given `.csproj`; rather, it's for the *build process*. This can result in: 1. `dotnet build Project.sln` is run; `Project.sln` references `App1.csproj` and `App2.csproj`. 2. `App1.csproj` is built. 3. `App1.csproj` calls `<GeneratePackageManagerJava/>`. 4. `App2.csproj` is later built as part in the process, and *also* calls `<GeneratePackageManagerJava/>`. In particular note the key within `<GeneratePackageManagerJava/>`: `".:!MarshalMethods!:."`. This value is unchanged, and means that that when `App2.csproj` is built, it will be using the same key as was used with `App1.csproj`, and thus could be inadvertently using data intended for `App1.csproj`! This would result build errors: …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: System.InvalidOperationException: Unable to translate assembly name 'Xamarin.AndroidX.Activity' to its index …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.MarshalMethodsNativeAssemblyGenerator.WriteNativeMethods(LlvmIrGenerator generator, Dictionary`2 asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.MarshalMethodsNativeAssemblyGenerator.Write(LlvmIrGenerator generator) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.LLVMIR.LlvmIrComposer.Write(AndroidTargetArch arch, StreamWriter output, String fileName) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.GeneratePackageManagerJava.AddEnvironment() …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.GeneratePackageManagerJava.RunTask() The sharing of `RegisterTaskObject` data across project builds is rarely desirable. There are a few instances where it is safe to share the registered objects between projects, e.g. `java` version information (keyed on `java` path). However, most of the time it is specific to the project that is being built. Historically we have probably got away with this because "most" users only have one project. Update the `MSBuildExtensions` extension methods to include an additional `RegisterTaskObjectKeyFlags` parameter: [Flags] public enum RegisterTaskObjectKeyFlags { None = 0, IncludeProjectFile = 1 << 0, } Allowing: var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<MarshalMethodsState> ( ".:!MarshalMethods!:.", RegisteredTaskObjectLifetime.Build, RegisterTaskObjectKeyFlags.IncludeProjectFile ); When `RegisterTaskObjectKeyFlags.IncludeProjectFile` is specified, then [`IBuildEngine.ProjectFileOfTaskNode`][2] is used as part of the key with `RegisterTaskObject()`. This helps ensure that builds in different `.csproj` files will result in different keys. The previous `MSBuildExtensions.GetRegisteredTaskObjectAssemblyLocal()` and related overloads have been updated so that `RegisterTaskObjectKeyFlags.IncludeProjectFile` is used by default. [0]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.framework.ibuildengine4.registertaskobject?view=msbuild-17-netcore [1]: https://github.com/xamarin/xamarin-android/blob/c92ae5eb9fdcb3a2fd7c20f5b42dddf8b3ea781a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs#L407 [2]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.framework.ibuildengine.projectfileoftasknode?view=msbuild-17-netcore
1 parent 9f02d77 commit 76c076f

1 file changed

Lines changed: 58 additions & 6 deletions

File tree

‎src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs‎

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313

1414
namespaceMicrosoft.Android.Build.Tasks
1515
{
16+
[Flags]
17+
publicenumRegisterTaskObjectKeyFlags{
18+
None=0,
19+
IncludeProjectFile=1<<0,
20+
}
21+
1622
publicstaticclassMSBuildExtensions
1723
{
1824
publicstaticvoidLogDebugMessage(thisTaskLoggingHelperlog,stringmessage,paramsobject[]messageArgs)
@@ -252,34 +258,80 @@ public static void SetDestinationSubPath (this ITaskItem assembly)
252258
/// <summary>
253259
/// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key
254260
/// </summary>
261+
[Obsolete("Use RegisterTaskObjectAssemblyLocal (engine, key, value, allowEarlyCollection, lifetime, flags) instead.")]
255262
publicstaticvoidRegisterTaskObjectAssemblyLocal(thisIBuildEngine4engine,objectkey,objectvalue,RegisteredTaskObjectLifetimelifetime,boolallowEarlyCollection=false)=>
256-
engine.RegisterTaskObject((AssemblyLocation,key),value,lifetime,allowEarlyCollection);
263+
RegisterTaskObjectAssemblyLocal(engine,key,value,lifetime,allowEarlyCollection:false,flags:RegisterTaskObjectKeyFlags.IncludeProjectFile);
264+
265+
/// <summary>
266+
/// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key
267+
/// </summary>
268+
publicstaticvoidRegisterTaskObjectAssemblyLocal(thisIBuildEngine4engine,objectkey,objectvalue,RegisteredTaskObjectLifetimelifetime,boolallowEarlyCollection=false,RegisterTaskObjectKeyFlagsflags=RegisterTaskObjectKeyFlags.IncludeProjectFile)=>
269+
engine.RegisterTaskObject(engine.GetKey(AssemblyLocation,key,flags),value,lifetime,allowEarlyCollection);
257270

258271
/// <summary>
259272
/// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key
260273
/// </summary>
274+
[Obsolete("Use GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")]
261275
publicstaticobjectGetRegisteredTaskObjectAssemblyLocal(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime)=>
262-
engine.GetRegisteredTaskObject((AssemblyLocation,key),lifetime);
276+
GetRegisteredTaskObjectAssemblyLocal(engine,key,lifetime,flags:RegisterTaskObjectKeyFlags.IncludeProjectFile);
277+
278+
/// <summary>
279+
/// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key
280+
/// </summary>
281+
publicstaticobjectGetRegisteredTaskObjectAssemblyLocal(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime,RegisterTaskObjectKeyFlagsflags=RegisterTaskObjectKeyFlags.IncludeProjectFile)=>
282+
engine.GetRegisteredTaskObject(engine.GetKey(AssemblyLocation,key,flags),lifetime);
283+
263284

264285
/// <summary>
265286
/// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key
266287
/// </summary>
288+
[Obsolete("Use GetRegisteredTaskObjectAssemblyLocal<T> (engine, key, lifetime, flags) instead.")]
267289
publicstaticTGetRegisteredTaskObjectAssemblyLocal<T>(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime)
268-
whereT:class=>
269-
engine.GetRegisteredTaskObject((AssemblyLocation,key),lifetime)asT;
290+
whereT:class=>GetRegisteredTaskObjectAssemblyLocal<T>(engine,key,lifetime,flags:RegisterTaskObjectKeyFlags.IncludeProjectFile);
270291

292+
/// <summary>
293+
/// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key
294+
/// </summary>
295+
publicstaticTGetRegisteredTaskObjectAssemblyLocal<T>(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime,RegisterTaskObjectKeyFlagsflags=RegisterTaskObjectKeyFlags.IncludeProjectFile)
296+
whereT:class=>
297+
engine.GetRegisteredTaskObject(engine.GetKey(AssemblyLocation,key,flags),lifetime)asT;
271298

272299
/// <summary>
273300
/// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key
274301
/// </summary>
302+
[Obsolete("Use UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")]
275303
publicstaticobjectUnregisterTaskObjectAssemblyLocal(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime)=>
276-
engine.UnregisterTaskObject((AssemblyLocation,key),lifetime);
304+
UnregisterTaskObjectAssemblyLocal(engine,key,lifetime,flags:RegisterTaskObjectKeyFlags.IncludeProjectFile);
305+
306+
/// <summary>
307+
/// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key
308+
/// </summary>
309+
publicstaticobjectUnregisterTaskObjectAssemblyLocal(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime,RegisterTaskObjectKeyFlagsflags=RegisterTaskObjectKeyFlags.IncludeProjectFile)=>
310+
engine.UnregisterTaskObject(engine.GetKey(AssemblyLocation,key,flags),lifetime);
277311

278312
/// <summary>
279313
/// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key
280314
/// </summary>
315+
[Obsolete("Use UnregisterTaskObjectAssemblyLocal<T> (engine, key, lifetime, flags) instead.")]
281316
publicstaticTUnregisterTaskObjectAssemblyLocal<T>(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime)
317+
whereT:class=>UnregisterTaskObjectAssemblyLocal<T>(engine,key,lifetime,flags:RegisterTaskObjectKeyFlags.IncludeProjectFile);
318+
319+
/// <summary>
320+
/// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key
321+
/// </summary>
322+
publicstaticTUnregisterTaskObjectAssemblyLocal<T>(thisIBuildEngine4engine,objectkey,RegisteredTaskObjectLifetimelifetime,RegisterTaskObjectKeyFlagsflags=RegisterTaskObjectKeyFlags.IncludeProjectFile)
282323
whereT:class=>
283-
engine.UnregisterTaskObject((AssemblyLocation,key),lifetime)asT;
324+
engine.UnregisterTaskObject(engine.GetKey(AssemblyLocation,key,flags),lifetime)asT;
325+
326+
/// <summary>
327+
/// Method to calculate the key for the RegisterTaskObject. This is based on the
328+
/// RegisterTaskObjectKeyFlags which are passed.
329+
/// </summary>
330+
staticobjectGetKey(thisIBuildEngine4engine,stringlocation,objectkey,RegisterTaskObjectKeyFlagsflags)
331+
{
332+
return((flags&RegisterTaskObjectKeyFlags.IncludeProjectFile)!=0)
333+
?(location,key,engine.ProjectFileOfTaskNode)
334+
:(location,key,string.Empty);
335+
}
284336
}
285337
}

0 commit comments

Comments
 (0)