Skip to content

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

File tree

1 file changed

+58
-6
lines changed

1 file changed

+58
-6
lines changed

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
namespace Microsoft.Android.Build.Tasks
1515
{
16+
[Flags]
17+
public enum RegisterTaskObjectKeyFlags {
18+
None = 0,
19+
IncludeProjectFile = 1 << 0,
20+
}
21+
1622
public static class MSBuildExtensions
1723
{
1824
public static void LogDebugMessage (this TaskLoggingHelper log, string message, params object[] 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
public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = 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+
public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false, RegisterTaskObjectKeyFlags flags = 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
public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) =>
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+
public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = 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
public static T GetRegisteredTaskObjectAssemblyLocal<T> (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime)
268-
where T : class =>
269-
engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime) as T;
290+
where T : 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+
public static T GetRegisteredTaskObjectAssemblyLocal<T> (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile)
296+
where T : class =>
297+
engine.GetRegisteredTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T;
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
public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) =>
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+
public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = 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
public static T UnregisterTaskObjectAssemblyLocal<T> (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime)
317+
where T : 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+
public static T UnregisterTaskObjectAssemblyLocal<T> (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile)
282323
where T : class =>
283-
engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime) as T;
324+
engine.UnregisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T;
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+
static object GetKey (this IBuildEngine4 engine, string location, object key, RegisterTaskObjectKeyFlags flags)
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)