diff --git a/src/Microsoft.Android.Run/Program.cs b/src/Microsoft.Android.Run/Program.cs
index b61ce341b2a..cc61504f00d 100644
--- a/src/Microsoft.Android.Run/Program.cs
+++ b/src/Microsoft.Android.Run/Program.cs
@@ -6,6 +6,7 @@
const string VersionsFileName = "Microsoft.Android.versions.txt";
string? adbPath = null;
+string? adbTarget = null;
string? package = null;
string? activity = null;
bool verbose = false;
@@ -38,6 +39,9 @@ int Run (string[] args)
"Path to the {ADB} executable. If not specified, will attempt to locate " +
"the Android SDK automatically.",
v => adbPath = v },
+ { "adb-target=",
+ "The {TARGET} device/emulator for adb commands (e.g., '-s emulator-5554').",
+ v => adbTarget = v },
{ "p|package=",
"The Android application {PACKAGE} name (e.g., com.example.myapp). Required.",
v => package = v },
@@ -123,6 +127,8 @@ int Run (string[] args)
if (verbose) {
Console.WriteLine ($"Using adb: {adbPath}");
+ if (!string.IsNullOrEmpty (adbTarget))
+ Console.WriteLine ($"Target: {adbTarget}");
Console.WriteLine ($"Package: {package}");
if (!string.IsNullOrEmpty (activity))
Console.WriteLine ($"Activity: {activity}");
@@ -224,13 +230,15 @@ void StartLogcat ()
if (!string.IsNullOrEmpty (logcatArgs))
logcatArguments += $" {logcatArgs}";
+ var fullArguments = string.IsNullOrEmpty (adbTarget) ? logcatArguments : $"{adbTarget} {logcatArguments}";
+
if (verbose)
- Console.WriteLine ($"Running: adb {logcatArguments}");
+ Console.WriteLine ($"Running: adb {fullArguments}");
var locker = new Lock();
var psi = new ProcessStartInfo {
FileName = adbPath,
- Arguments = logcatArguments,
+ Arguments = fullArguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
@@ -320,12 +328,14 @@ void StopApp ()
(int ExitCode, string Output, string Error) RunAdb (string arguments)
{
+ var fullArguments = string.IsNullOrEmpty (adbTarget) ? arguments : $"{adbTarget} {arguments}";
+
if (verbose)
- Console.WriteLine ($"Running: adb {arguments}");
+ Console.WriteLine ($"Running: adb {fullArguments}");
var psi = new ProcessStartInfo {
FileName = adbPath,
- Arguments = arguments,
+ Arguments = fullArguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Application.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Application.targets
index 3eddd2ac6c1..d6efbf7b5ee 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Application.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Application.targets
@@ -68,8 +68,9 @@ This file contains targets specific for Android application projects.
<_AndroidRunPath Condition=" '$(_AndroidRunPath)' == '' ">$(MSBuildThisFileDirectory)..\tools\Microsoft.Android.Run.dll
<_AndroidRunLogcatArgs Condition=" '$(_AndroidRunLogcatArgs)' == '' ">monodroid-assembly:S
+ <_AndroidRunAdbTargetArg Condition=" '$(AdbTarget)' != '' ">--adb-target "$(AdbTarget)"
dotnet
- exec "$(_AndroidRunPath)" --adb "$(_AdbToolPath)" --package "$(_AndroidPackage)" --activity "$(AndroidLaunchActivity)" --logcat-args "$(_AndroidRunLogcatArgs)" $(_AndroidRunExtraArgs)
+ exec "$(_AndroidRunPath)" --adb "$(_AdbToolPath)" $(_AndroidRunAdbTargetArg) --package "$(_AndroidPackage)" --activity "$(AndroidLaunchActivity)" --logcat-args "$(_AndroidRunLogcatArgs)" $(_AndroidRunExtraArgs)
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs
index 6961acbba1d..32135f406c8 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs
@@ -132,7 +132,7 @@ public bool Publish (string target = null, string runtimeIdentifier = null, stri
return Execute (arguments.ToArray ());
}
- public bool Run (bool waitForExit = false)
+ public bool Run (bool waitForExit = false, string [] parameters = null)
{
string binlog = Path.Combine (Path.GetDirectoryName (projectOrSolution), "run.binlog");
var arguments = new List {
@@ -142,6 +142,11 @@ public bool Run (bool waitForExit = false)
$"/bl:\"{binlog}\"",
$"/p:WaitForExit={waitForExit.ToString (CultureInfo.InvariantCulture)}"
};
+ if (parameters != null) {
+ foreach (var parameter in parameters) {
+ arguments.Add ($"/p:{parameter}");
+ }
+ }
return Execute (arguments.ToArray ());
}
@@ -149,8 +154,9 @@ public bool Run (bool waitForExit = false)
/// Starts `dotnet run` and returns a running Process that can be monitored and killed.
///
/// Whether to use Microsoft.Android.Run tool which waits for app exit and streams logcat.
+ /// Optional MSBuild properties to pass (e.g., "Device=emulator-5554").
/// A running Process instance. Caller is responsible for disposing.
- public Process StartRun (bool waitForExit = true)
+ public Process StartRun (bool waitForExit = true, string [] parameters = null)
{
string binlog = Path.Combine (Path.GetDirectoryName (projectOrSolution), "run.binlog");
var arguments = new List {
@@ -160,6 +166,11 @@ public Process StartRun (bool waitForExit = true)
$"/bl:\"{binlog}\"",
$"/p:WaitForExit={waitForExit.ToString (CultureInfo.InvariantCulture)}"
};
+ if (parameters != null) {
+ foreach (var parameter in parameters) {
+ arguments.Add ($"/p:{parameter}");
+ }
+ }
return ExecuteProcess (arguments.ToArray ());
}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index 45a98f688d6..ee75d41fb97 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -298,6 +298,11 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_AndroidStripNativeLibraries Condition=" '$(_AndroidStripNativeLibraries)' != 'true' ">false
+
+
+ -s $(Device)
+
+
@@ -819,11 +824,6 @@ because xbuild doesn't support framework reference assemblies.
android.app.Application
-
-
-
- -s $(Device)
-
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index 5e4c4a5e1b8..3c1e2691dd1 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -121,6 +121,82 @@ public void DotNetRunWaitForExit ()
Assert.IsTrue (foundMessage, $"Expected message '{logcatMessage}' was not found in output. See {logPath} for details.");
}
+ [Test]
+ public void DotNetRunWithDeviceParameter ()
+ {
+ const string logcatMessage = "DOTNET_RUN_DEVICE_TEST_67890";
+ var proj = new XamarinAndroidApplicationProject ();
+
+ // Enable verbose output from Microsoft.Android.Run for debugging
+ proj.SetProperty ("_AndroidRunExtraArgs", "--verbose");
+
+ // Add a Console.WriteLine that will appear in logcat
+ proj.MainActivity = proj.DefaultMainActivity.Replace (
+ "//${AFTER_ONCREATE}",
+ $"Console.WriteLine (\"{logcatMessage}\");");
+
+ using var builder = CreateApkBuilder ();
+ builder.Save (proj);
+
+ var dotnet = new DotNetCLI (Path.Combine (Root, builder.ProjectDirectory, proj.ProjectFilePath));
+ Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");
+
+ // Get the attached device serial
+ var serial = GetAttachedDeviceSerial ();
+
+ // Start dotnet run with Device parameter, which should set $(AdbTarget)
+ using var process = dotnet.StartRun (waitForExit: true, parameters: [$"Device={serial}"]);
+
+ var locker = new Lock ();
+ var output = new StringBuilder ();
+ var outputReceived = new ManualResetEventSlim (false);
+ bool foundMessage = false;
+ bool foundAdbTarget = false;
+
+ process.OutputDataReceived += (sender, e) => {
+ if (e.Data != null) {
+ lock (locker) {
+ output.AppendLine (e.Data);
+ // Check for the --adb-target argument in verbose output
+ if (e.Data.Contains ($"Target: -s {serial}")) {
+ foundAdbTarget = true;
+ }
+ if (e.Data.Contains (logcatMessage)) {
+ foundMessage = true;
+ outputReceived.Set ();
+ }
+ }
+ }
+ };
+ process.ErrorDataReceived += (sender, e) => {
+ if (e.Data != null) {
+ lock (locker) {
+ output.AppendLine ($"STDERR: {e.Data}");
+ }
+ }
+ };
+
+ process.BeginOutputReadLine ();
+ process.BeginErrorReadLine ();
+
+ // Wait for the expected message or timeout
+ bool messageFound = outputReceived.Wait (TimeSpan.FromSeconds (60));
+
+ // Kill the process (simulating Ctrl+C)
+ if (!process.HasExited) {
+ process.Kill (entireProcessTree: true);
+ process.WaitForExit ();
+ }
+
+ // Write the output to a log file for debugging
+ string logPath = Path.Combine (Root, builder.ProjectDirectory, "dotnet-run-device-output.log");
+ File.WriteAllText (logPath, output.ToString ());
+ TestContext.AddTestAttachment (logPath);
+
+ Assert.IsTrue (foundAdbTarget, $"Expected --adb-target argument with serial '{serial}' was not found in verbose output. See {logPath} for details.");
+ Assert.IsTrue (foundMessage, $"Expected message '{logcatMessage}' was not found in output. See {logPath} for details.");
+ }
+
[Test]
[TestCase (true)]
[TestCase (false)]