Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/CSharpExpert.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ secure, readable, and maintainable code that follows .NET conventions and existi
- **C# Version**: `latest` (per project files)
- **Nullable**: Enabled
- **Purpose**: Apple SDK discovery, provisioning profiles, plist parsing, Xcode integration
- **Tests**: NUnit (UnitTests project)
- **Tests**: NUnit (tests project)

When invoked:

Expand Down Expand Up @@ -49,7 +49,7 @@ When invoked:
## Build and Test

- Build: `dotnet build Xamarin.MacDev.sln`
- Tests: `dotnet test UnitTests/UnitTests.csproj`
- Tests: `dotnet test tests/tests.csproj`
- Pack: `dotnet pack Xamarin.MacDev/Xamarin.MacDev.csproj`

## Async Best Practices
Expand Down
25 changes: 24 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ provisioning, plist parsing, and related developer utilities.
- C# Language Version: `latest` (per project files)
- Nullable reference types are enabled
- SDK-style projects built with `dotnet build` (or `make`)
- Tests: `UnitTests` project using **NUnit**
- Tests: `tests` project using **NUnit**

## Guidance

Expand All @@ -23,10 +23,32 @@ provisioning, plist parsing, and related developer utilities.
- Avoid APIs that are unavailable in `netstandard2.0` when used in shared code.
- Do not edit auto-generated files (`// <auto-generated>` or `*.g.cs`).

## Code Style

- Use tabs for indentation (size 4).
- Use `namespace Xamarin.MacDev {` (brace on same line) with a **blank line after the opening brace**.
- One class per file.
- Keep classes under ~700 lines; extract helpers/parsers into separate files when needed.
- Private/internal fields: `camelCase`. Types/methods/properties: `PascalCase`.
- Space before method call parentheses: `Method ()`, not `Method()`.
- License header on new files: `// Copyright (c) Microsoft Corporation.` / `// Licensed under the MIT License.`
- Add `#nullable enable` at the top of new files.

## Error Handling

- Validate inputs at public boundaries using `ArgumentNullException.ThrowIfNull` and `string.IsNullOrWhiteSpace`.
- Use precise exception types and avoid catch-and-swallow patterns.
- Catch specific exceptions (`catch (UnauthorizedAccessException)`) — never bare `catch (Exception)`.

## Resource Management

- Use `using` on **all** disposables — including `StringWriter`, `Process`, `StreamReader`, etc.
- Prefer the Null Object Pattern (e.g. `static readonly NullProgress`) over null-checking everywhere.

## .NET SDK Patterns

- Prefer `Path.GetRandomFileName()` for temp file names.
- Use `File.Create(path, bufferSize, FileOptions.DeleteOnClose)` when appropriate for transient files.

## Documentation

Expand All @@ -35,3 +57,4 @@ provisioning, plist parsing, and related developer utilities.
## Testing

- Use NUnit for new or changed tests and follow existing naming patterns.
- Every new utility method **must** have test coverage — no exceptions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ jobs:

- name: Test (Windows)
if: runner.os == 'Windows'
run: dotnet test UnitTests/UnitTests.csproj -c Release -l "console;verbosity=detailed" -l trx --results-directory ${{ github.workspace }}/artifacts/test-results
run: dotnet test tests/tests.csproj -c Release -l "console;verbosity=detailed" -l trx --results-directory ${{ github.workspace }}/artifacts/test-results

- name: Test (macOS)
if: runner.os == 'macOS'
run: dotnet test UnitTests/UnitTests.csproj -c Release -f net10.0 -l "console;verbosity=detailed" -l trx --results-directory ${{ github.workspace }}/artifacts/test-results
run: dotnet test tests/tests.csproj -c Release -f net10.0 -l "console;verbosity=detailed" -l trx --results-directory ${{ github.workspace }}/artifacts/test-results

- name: Calculate Version
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion Xamarin.MacDev.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.MacDev", "Xamarin.MacDev\Xamarin.MacDev.csproj", "{CC3D9353-20C4-467A-8522-A9DED6F0C753}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{39DBAAF8-57A5-49A3-9E9A-11B545906AED}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\tests.csproj", "{39DBAAF8-57A5-49A3-9E9A-11B545906AED}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
23 changes: 23 additions & 0 deletions Xamarin.MacDev/Models/CommandLineToolsInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

namespace Xamarin.MacDev.Models {

/// <summary>
/// Information about Xcode Command Line Tools installation.
/// </summary>
public class CommandLineToolsInfo {
/// <summary>Whether the Command Line Tools are installed.</summary>
public bool IsInstalled { get; set; }

/// <summary>The Command Line Tools version string (e.g. "16.2.0.0.1.1733547573"), or null if not installed.</summary>
public string? Version { get; set; }

/// <summary>The Command Line Tools install path (e.g. "/Library/Developer/CommandLineTools"), or null if not installed.</summary>
public string? Path { get; set; }

public override string ToString () => IsInstalled ? $"Command Line Tools {Version} at {Path}" : "Command Line Tools not installed";
}
}
59 changes: 59 additions & 0 deletions Xamarin.MacDev/Models/EnvironmentCheckResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;

#nullable enable

namespace Xamarin.MacDev.Models {

/// <summary>
/// Overall status of the Apple development environment.
/// </summary>
public enum EnvironmentStatus {
/// <summary>All required components are present.</summary>
Ok,
/// <summary>Some optional components are missing.</summary>
Partial,
/// <summary>Required components are missing.</summary>
Missing,
}

/// <summary>
/// Result of a comprehensive Apple environment check.
/// </summary>
public class EnvironmentCheckResult {
/// <summary>Information about the active Xcode installation, or null if none found.</summary>
public XcodeInfo? Xcode { get; set; }

/// <summary>Information about the Command Line Tools.</summary>
public CommandLineToolsInfo CommandLineTools { get; set; } = new CommandLineToolsInfo ();

/// <summary>Installed simulator runtimes.</summary>
public List<SimulatorRuntimeInfo> Runtimes { get; set; } = new List<SimulatorRuntimeInfo> ();

/// <summary>Enabled development platforms (e.g. "iOS", "macOS").</summary>
public List<string> Platforms { get; set; } = new List<string> ();

/// <summary>Overall environment status.</summary>
public EnvironmentStatus Status { get; set; } = EnvironmentStatus.Missing;

/// <summary>
/// Derives the <see cref="Status"/> from the current state of the environment.
/// </summary>
public void DeriveStatus ()
{
if (Xcode is null || !CommandLineTools.IsInstalled) {
Status = EnvironmentStatus.Missing;
return;
}

if (Runtimes.Count == 0) {
Status = EnvironmentStatus.Partial;
return;
}

Status = EnvironmentStatus.Ok;
}
}
}
34 changes: 34 additions & 0 deletions Xamarin.MacDev/Models/SimulatorDeviceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

namespace Xamarin.MacDev.Models {

/// <summary>
/// Information about a simulator device from xcrun simctl.
/// </summary>
public class SimulatorDeviceInfo {
/// <summary>The simulator display name (e.g. "iPhone 16 Pro").</summary>
public string Name { get; set; } = "";

/// <summary>The simulator UDID.</summary>
public string Udid { get; set; } = "";

/// <summary>The device state (e.g. "Shutdown", "Booted").</summary>
public string State { get; set; } = "";

/// <summary>The runtime identifier (e.g. "com.apple.CoreSimulator.SimRuntime.iOS-18-2").</summary>
public string RuntimeIdentifier { get; set; } = "";

/// <summary>The device type identifier (e.g. "com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro").</summary>
public string DeviceTypeIdentifier { get; set; } = "";

/// <summary>Whether this simulator is available.</summary>
public bool IsAvailable { get; set; }

public bool IsBooted => State == "Booted";

public override string ToString () => $"{Name} ({Udid}) [{State}]";
}
}
35 changes: 35 additions & 0 deletions Xamarin.MacDev/Models/SimulatorRuntimeInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

namespace Xamarin.MacDev.Models {

/// <summary>
/// Information about a simulator runtime from xcrun simctl.
/// </summary>
public class SimulatorRuntimeInfo {
/// <summary>The platform name (e.g. "iOS", "tvOS", "watchOS", "visionOS").</summary>
public string Platform { get; set; } = "";

/// <summary>The runtime version (e.g. "18.2").</summary>
public string Version { get; set; } = "";

/// <summary>The build version (e.g. "22C150").</summary>
public string BuildVersion { get; set; } = "";

/// <summary>The runtime identifier (e.g. "com.apple.CoreSimulator.SimRuntime.iOS-18-2").</summary>
public string Identifier { get; set; } = "";

/// <summary>The display name (e.g. "iOS 18.2").</summary>
public string Name { get; set; } = "";

/// <summary>Whether this runtime is available for use.</summary>
public bool IsAvailable { get; set; }

/// <summary>Whether this runtime is bundled with Xcode (vs downloaded separately).</summary>
public bool IsBundled { get; set; }

public override string ToString () => $"{Name} ({Identifier})";
}
}
34 changes: 34 additions & 0 deletions Xamarin.MacDev/Models/XcodeInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

#nullable enable

namespace Xamarin.MacDev.Models {

/// <summary>
/// Information about an Xcode installation.
/// </summary>
public class XcodeInfo {
/// <summary>The path to the Xcode.app bundle (e.g. /Applications/Xcode.app).</summary>
public string Path { get; set; } = "";

/// <summary>The Xcode version (e.g. 16.2).</summary>
public Version Version { get; set; } = new Version (0, 0);

/// <summary>The Xcode build number (e.g. 16C5032a).</summary>
public string Build { get; set; } = "";

/// <summary>The DTXcode value from the version plist.</summary>
public string DTXcode { get; set; } = "";

/// <summary>Whether this is the currently selected Xcode (via xcode-select).</summary>
public bool IsSelected { get; set; }

/// <summary>Whether the Xcode path is or contains a symlink.</summary>
public bool IsSymlink { get; set; }

public override string ToString () => $"{Path} ({Version}, {Build})";
}
}
Loading