Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/Microsoft.Android.Run/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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}");
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ This file contains targets specific for Android application projects.
<PropertyGroup Condition=" '$(WaitForExit)' != 'false' ">
<_AndroidRunPath Condition=" '$(_AndroidRunPath)' == '' ">$(MSBuildThisFileDirectory)..\tools\Microsoft.Android.Run.dll</_AndroidRunPath>
<_AndroidRunLogcatArgs Condition=" '$(_AndroidRunLogcatArgs)' == '' ">monodroid-assembly:S</_AndroidRunLogcatArgs>
<_AndroidRunAdbTargetArg Condition=" '$(AdbTarget)' != '' ">--adb-target &quot;$(AdbTarget)&quot;</_AndroidRunAdbTargetArg>
<RunCommand>dotnet</RunCommand>
<RunArguments>exec &quot;$(_AndroidRunPath)&quot; --adb &quot;$(_AdbToolPath)&quot; --package &quot;$(_AndroidPackage)&quot; --activity &quot;$(AndroidLaunchActivity)&quot; --logcat-args &quot;$(_AndroidRunLogcatArgs)&quot; $(_AndroidRunExtraArgs)</RunArguments>
<RunArguments>exec &quot;$(_AndroidRunPath)&quot; --adb &quot;$(_AdbToolPath)&quot; $(_AndroidRunAdbTargetArg) --package &quot;$(_AndroidPackage)&quot; --activity &quot;$(AndroidLaunchActivity)&quot; --logcat-args &quot;$(_AndroidRunLogcatArgs)&quot; $(_AndroidRunExtraArgs)</RunArguments>
</PropertyGroup>
<!-- When WaitForExit is false, use direct adb command (no logcat streaming) -->
<PropertyGroup Condition=" '$(WaitForExit)' == 'false' ">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
Expand All @@ -142,15 +142,21 @@ 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 ());
}

/// <summary>
/// Starts `dotnet run` and returns a running Process that can be monitored and killed.
/// </summary>
/// <param name="waitForExit">Whether to use Microsoft.Android.Run tool which waits for app exit and streams logcat.</param>
/// <param name="parameters">Optional MSBuild properties to pass (e.g., "Device=emulator-5554").</param>
/// <returns>A running Process instance. Caller is responsible for disposing.</returns>
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<string> {
Expand All @@ -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 ());
}
Expand Down
10 changes: 5 additions & 5 deletions src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_AndroidStripNativeLibraries Condition=" '$(_AndroidStripNativeLibraries)' != 'true' ">false</_AndroidStripNativeLibraries>
</PropertyGroup>

<!-- Setup $(AdbTarget) from $(Device) property for device selection support -->
<PropertyGroup Condition=" '$(Device)' != '' ">
<AdbTarget>-s $(Device)</AdbTarget>
</PropertyGroup>

<Choose>
<When Condition=" '$(DebugSymbols)' == 'True' And '$(DebugType)' != '' And ('$(EmbedAssembliesIntoApk)' == 'False' Or '$(Optimize)' != 'True') ">
<PropertyGroup>
Expand Down Expand Up @@ -819,11 +824,6 @@ because xbuild doesn't support framework reference assemblies.
<AndroidApplicationJavaClass Condition="'$(AndroidApplicationJavaClass)' == ''">android.app.Application</AndroidApplicationJavaClass>
</PropertyGroup>
<Message Text="Application Java class: $(AndroidApplicationJavaClass)" />

<!-- Setup $(AdbTarget) from $(Device) property for device selection support -->
<PropertyGroup Condition=" '$(Device)' != '' ">
<AdbTarget>-s $(Device)</AdbTarget>
</PropertyGroup>
</Target>

<Target Name="AndroidPrepareForBuild" DependsOnTargets="$(_OnResolveMonoAndroidSdks);$(AndroidPrepareForBuildDependsOn)" />
Expand Down
76 changes: 76 additions & 0 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down