Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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 CLT version string (e.g. "16.2.0.0.1.1733547573"), or null if not installed.</summary>
public string? Version { get; set; }

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

public override string ToString () => IsInstalled ? $"CLT {Version} at {Path}" : "CLT not installed";
Comment thread
rmarinho marked this conversation as resolved.
Outdated
}
}
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