diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
index f3859e5..a04d86f 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs
@@ -46,5 +46,7 @@ public virtual bool RunTask ()
/// * RunTaskAsync is already on a background thread
///
public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask;
+
+ protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
}
}
diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs
index 4b511c3..9acb7b8 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs
@@ -1,6 +1,7 @@
// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L10
using System;
+using System.IO;
using Microsoft.Build.Utilities;
namespace Microsoft.Android.Build.Tasks
@@ -11,6 +12,13 @@ public abstract class AndroidTask : Task
{
public abstract string TaskPrefix { get; }
+ protected string WorkingDirectory { get; private set; }
+
+ public AndroidTask ()
+ {
+ WorkingDirectory = Directory.GetCurrentDirectory();
+ }
+
public override bool Execute ()
{
try {
@@ -22,5 +30,7 @@ public override bool Execute ()
}
public abstract bool RunTask ();
+
+ protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
}
}
diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs
index 38093a6..cc01e8f 100644
--- a/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs
@@ -1,6 +1,7 @@
// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L75
using System;
+using System.IO;
using Microsoft.Build.Utilities;
namespace Microsoft.Android.Build.Tasks
@@ -9,6 +10,13 @@ public abstract class AndroidToolTask : ToolTask
{
public abstract string TaskPrefix { get; }
+ protected string WorkingDirectory { get; private set; }
+
+ public AndroidToolTask ()
+ {
+ WorkingDirectory = Directory.GetCurrentDirectory();
+ }
+
public override bool Execute ()
{
try {
@@ -22,5 +30,7 @@ public override bool Execute ()
// Most ToolTask's do not override Execute and
// just expect the base to be called
public virtual bool RunTask () => base.Execute ();
+
+ protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory);
}
}
diff --git a/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
index d50b3d9..b467e83 100644
--- a/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
+++ b/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs
@@ -251,18 +251,30 @@ public static void SetDestinationSubPath (this ITaskItem assembly)
///
/// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key
+ /// The `key` should be unique to a project unless it is a global item.
+ /// Ideally the key should be the full path of a file in the project directory structure.
+ /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate
+ /// a project specific key if needed.
///
public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false) =>
engine.RegisterTaskObject ((AssemblyLocation, key), value, lifetime, allowEarlyCollection);
///
/// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key
+ /// The `key` should be unique to a project unless it is a global item.
+ /// Ideally the key should be the full path of a file in the project directory structure.
+ /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate
+ /// a project specific key if needed.
///
public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) =>
engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime);
///
/// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key
+ /// The `key` should be unique to a project unless it is a global item.
+ /// Ideally the key should be the full path of a file in the project directory structure.
+ /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate
+ /// a project specific key if needed.
///
public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime)
where T : class =>
@@ -271,12 +283,20 @@ public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engi
///
/// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key
+ /// The `key` should be unique to a project unless it is a global item.
+ /// Ideally the key should be the full path of a file in the project directory structure.
+ /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate
+ /// a project specific key if needed.
///
public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) =>
engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime);
///
- /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key
+ /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key.
+ /// The `key` should be unique to a project unless it is a global item.
+ /// Ideally the key should be the full path of a file in the project directory structure.
+ /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate
+ /// a project specific key if needed.
///
public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime)
where T : class =>
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs
new file mode 100644
index 0000000..8b35ce5
--- /dev/null
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs
@@ -0,0 +1,100 @@
+using System.IO;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Android.Build.BaseTasks.Tests.Utilities;
+using Microsoft.Android.Build.Tasks;
+using NUnit.Framework;
+using Microsoft.Build.Framework;
+using Xamarin.Build;
+
+namespace Microsoft.Android.Build.BaseTasks.Tests
+{
+ [TestFixture]
+ public class AndroidToolTaskTests
+ {
+ public class MyAndroidTask : AndroidTask {
+ public override string TaskPrefix {get;} = "MAT";
+ public string Key { get; set; }
+ public string Value { get; set; }
+ public bool ProjectSpecific { get; set; } = false;
+ public override bool RunTask ()
+ {
+ var key = ProjectSpecific ? ProjectSpecificTaskObjectKey (Key) : (Key, (object)string.Empty);
+ BuildEngine4.RegisterTaskObjectAssemblyLocal (key, Value, RegisteredTaskObjectLifetime.Build);
+ return true;
+ }
+ }
+
+ public class MyOtherAndroidTask : AndroidTask {
+ public override string TaskPrefix {get;} = "MOAT";
+ public string Key { get; set; }
+ public bool ProjectSpecific { get; set; } = false;
+
+ [Output]
+ public string Value { get; set; }
+ public override bool RunTask ()
+ {
+ var key = ProjectSpecific ? ProjectSpecificTaskObjectKey (Key) : (Key, (object)string.Empty);
+ Value = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (key, RegisteredTaskObjectLifetime.Build);
+ return true;
+ }
+ }
+
+ [Test]
+ [TestCase (true, true, true)]
+ [TestCase (false, false, true)]
+ [TestCase (true, false, false)]
+ [TestCase (false, true, false)]
+ public void TestRegisterTaskObjectCanRetrieveCorrectItem (bool projectSpecificA, bool projectSpecificB, bool expectedResult)
+ {
+ var engine = new MockBuildEngine (TestContext.Out) {
+ };
+ var task = new MyAndroidTask () {
+ BuildEngine = engine,
+ Key = "Foo",
+ Value = "Foo",
+ ProjectSpecific = projectSpecificA,
+ };
+ task.Execute ();
+ var task2 = new MyOtherAndroidTask () {
+ BuildEngine = engine,
+ Key = "Foo",
+ ProjectSpecific = projectSpecificB,
+ };
+ task2.Execute ();
+ Assert.AreEqual (expectedResult, string.Compare (task2.Value, task.Value, ignoreCase: true) == 0);
+ }
+
+ [Test]
+ [TestCase (true, true, false)]
+ [TestCase (false, false, true)]
+ [TestCase (true, false, false)]
+ [TestCase (false, true, false)]
+ public void TestRegisterTaskObjectFailsWhenDirectoryChanges (bool projectSpecificA, bool projectSpecificB, bool expectedResult)
+ {
+ var engine = new MockBuildEngine (TestContext.Out) {
+ };
+ MyAndroidTask task;
+ var currentDir = Directory.GetCurrentDirectory ();
+ Directory.SetCurrentDirectory (Path.Combine (currentDir, ".."));
+ try {
+ task = new MyAndroidTask () {
+ BuildEngine = engine,
+ Key = "Foo",
+ Value = "Foo",
+ ProjectSpecific = projectSpecificA,
+ };
+ } finally {
+ Directory.SetCurrentDirectory (currentDir);
+ }
+ task.Execute ();
+ var task2 = new MyOtherAndroidTask () {
+ BuildEngine = engine,
+ Key = "Foo",
+ ProjectSpecific = projectSpecificB,
+ };
+ task2.Execute ();
+ Assert.AreEqual (expectedResult, string.Compare (task2.Value, task.Value, ignoreCase: true) == 0);
+ }
+ }
+}
diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/Utilites/MockBuildEngine.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/Utilites/MockBuildEngine.cs
new file mode 100644
index 0000000..38155fc
--- /dev/null
+++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/Utilites/MockBuildEngine.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Build.Framework;
+
+namespace Microsoft.Android.Build.BaseTasks.Tests.Utilities {
+ public class MockBuildEngine : IBuildEngine, IBuildEngine2, IBuildEngine3, IBuildEngine4 {
+ public MockBuildEngine (TextWriter output, IList errors = null, IList warnings = null, IList messages = null, IList customEvents = null)
+ {
+ this.Output = output;
+ this.Errors = errors;
+ this.Warnings = warnings;
+ this.Messages = messages;
+ this.CustomEvents = customEvents;
+ }
+
+ private TextWriter Output { get; }
+
+ private IList Errors { get; }
+
+ private IList Warnings { get; }
+
+ private IList Messages { get; }
+
+ private IList CustomEvents { get; }
+
+ int IBuildEngine.ColumnNumberOfTaskNode => -1;
+
+ bool IBuildEngine.ContinueOnError => false;
+
+ int IBuildEngine.LineNumberOfTaskNode => -1;
+
+ string IBuildEngine.ProjectFileOfTaskNode => "this.xml";
+
+ bool IBuildEngine2.IsRunningMultipleNodes => false;
+
+ bool IBuildEngine.BuildProjectFile (string projectFileName, string [] targetNames, IDictionary globalProperties, IDictionary targetOutputs) => true;
+
+ void IBuildEngine.LogCustomEvent (CustomBuildEventArgs e)
+ {
+ this.Output.WriteLine ($"Custom: {e.Message}");
+ if (CustomEvents != null)
+ CustomEvents.Add (e);
+ }
+
+ void IBuildEngine.LogErrorEvent (BuildErrorEventArgs e)
+ {
+ this.Output.WriteLine ($"Error: {e.Message}");
+ if (Errors != null)
+ Errors.Add (e);
+ }
+
+ void IBuildEngine.LogMessageEvent (BuildMessageEventArgs e)
+ {
+ this.Output.WriteLine ($"Message: {e.Message}");
+ if (Messages != null)
+ Messages.Add (e);
+ }
+
+ void IBuildEngine.LogWarningEvent (BuildWarningEventArgs e)
+ {
+ this.Output.WriteLine ($"Warning: {e.Message}");
+ if (Warnings != null)
+ Warnings.Add (e);
+ }
+
+ private Dictionary