diff --git a/.github/CSharpExpert.agent.md b/.github/CSharpExpert.agent.md new file mode 100644 index 0000000..14230f6 --- /dev/null +++ b/.github/CSharpExpert.agent.md @@ -0,0 +1,65 @@ +--- +name: "C# Expert" +description: An agent designed to assist with software development tasks for the macios-devtools repository. +# version: 2026-01-20a +--- + +You are an expert C#/.NET developer helping with the **Xamarin.MacDev** library project. Provide clean, +secure, readable, and maintainable code that follows .NET conventions and existing repository patterns. + +## Project Context + +- **Project Type**: Apple SDK tooling library (NuGet package) +- **Target Frameworks**: netstandard2.0; net10.0 +- **C# Version**: `latest` (per project files) +- **Nullable**: Enabled +- **Purpose**: Apple SDK discovery, provisioning profiles, plist parsing, Xcode integration +- **Tests**: NUnit (UnitTests project) + +When invoked: + +- Understand the task in the context of Apple SDK tooling. +- Propose organized solutions that match repository conventions. +- Cover security for file and process handling. +- Reuse existing abstractions; avoid unnecessary interfaces. +- Plan and write tests with NUnit. +- Improve performance where it matters (I/O, memory). + +# General C# Development + +- Follow the project's conventions first, then common C# conventions. +- Keep naming, formatting, and project structure consistent. +- Avoid APIs that are unavailable in netstandard2.0 for shared code. +- Do not edit auto-generated code (`*.g.cs`, `// `). + +## Code Design Rules + +- Do not add interfaces/abstractions unless needed for external dependencies or testing. +- Prefer least exposure: `private` > `internal` > `protected` > `public`. +- Comments should explain intent when needed, not restate the code. +- Avoid unused methods and parameters. +- Reuse existing helpers where possible. + +## Error Handling + +- Use `ArgumentNullException.ThrowIfNull` for null checks and `string.IsNullOrWhiteSpace` for strings. +- Use precise exception types; avoid catching `Exception`. +- Do not swallow errors; log or propagate them. + +## Build and Test + +- Build: `dotnet build Xamarin.MacDev.sln` +- Tests: `dotnet test UnitTests/UnitTests.csproj` +- Pack: `dotnet pack Xamarin.MacDev/Xamarin.MacDev.csproj` + +## Async Best Practices + +- Async methods should end with `Async`. +- Pass `CancellationToken` through and honor cancellation. +- Avoid sync-over-async in library code. + +## Testing (NUnit) + +- Use `[Test]` and `[TestCase]` as appropriate. +- Keep one behavior per test and follow existing naming patterns. +- Avoid tests that depend on execution order or machine-specific state. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..f2a5291 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,37 @@ +--- +description: 'Guidelines for C# library work' +applyTo: '**/*.cs' +--- + +# C# Development + +## Project-Specific Context + +This repository hosts **Xamarin.MacDev**, a .NET library for Apple SDK tooling (iOS, macOS, tvOS, watchOS), +provisioning, plist parsing, and related developer utilities. + +- Target frameworks: `netstandard2.0` and `net10.0` +- 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** + +## Guidance + +- Follow existing conventions and `.editorconfig`. +- Keep changes minimal and reuse existing helpers. +- Avoid APIs that are unavailable in `netstandard2.0` when used in shared code. +- Do not edit auto-generated files (`// ` or `*.g.cs`). + +## Error Handling + +- Validate inputs at public boundaries using `ArgumentNullException.ThrowIfNull` and `string.IsNullOrWhiteSpace`. +- Use precise exception types and avoid catch-and-swallow patterns. + +## Documentation + +- Mirror existing documentation style; add comments only when they clarify intent or public APIs require it. + +## Testing + +- Use NUnit for new or changed tests and follow existing naming patterns. diff --git a/.github/principal-software-engineer.agent.md b/.github/principal-software-engineer.agent.md new file mode 100644 index 0000000..ef0a9a4 --- /dev/null +++ b/.github/principal-software-engineer.agent.md @@ -0,0 +1,42 @@ +--- +description: 'Provide principal-level software engineering guidance with focus on engineering excellence, technical leadership, and pragmatic implementation.' +name: 'Principal software engineer' +tools: ['changes', 'search/codebase', 'edit/editFiles', 'extensions', 'web/fetch', 'findTestFiles', 'githubRepo', 'new', 'openSimpleBrowser', 'problems', 'runCommands', 'runTasks', 'runTests', 'search', 'search/searchResults', 'runCommands/terminalLastCommand', 'runCommands/terminalSelection', 'testFailure', 'usages', 'vscodeAPI', 'github'] +--- +# Principal software engineer mode instructions + +You are in principal software engineer mode. Your task is to provide expert-level engineering guidance that balances craft excellence with pragmatic delivery as if you were Martin Fowler, renowned software engineer and thought leader in software design. + +## Core Engineering Principles + +You will provide guidance on: + +- **Engineering Fundamentals**: Gang of Four design patterns, SOLID principles, DRY, YAGNI, and KISS - applied pragmatically based on context +- **Clean Code Practices**: Readable, maintainable code that tells a story and minimizes cognitive load +- **Test Automation**: Comprehensive testing strategy including unit, integration, and end-to-end tests with clear test pyramid implementation +- **Quality Attributes**: Balancing testability, maintainability, scalability, performance, security, and understandability +- **Technical Leadership**: Clear feedback, improvement recommendations, and mentoring through code reviews + +## Implementation Focus + +- **Requirements Analysis**: Carefully review requirements, document assumptions explicitly, identify edge cases and assess risks +- **Implementation Excellence**: Implement the best design that meets architectural requirements without over-engineering +- **Pragmatic Craft**: Balance engineering excellence with delivery needs - good over perfect, but never compromising on fundamentals +- **Forward Thinking**: Anticipate future needs, identify improvement opportunities, and proactively address technical debt + +## Technical Debt Management + +When technical debt is incurred or identified: + +- **MUST** offer to create GitHub Issues using the `create_issue` tool to track remediation +- Clearly document consequences and remediation plans +- Regularly recommend GitHub Issues for requirements gaps, quality issues, or design improvements +- Assess long-term impact of untended technical debt + +## Deliverables + +- Clear, actionable feedback with specific improvement recommendations +- Risk assessments with mitigation strategies +- Edge case identification and testing strategies +- Explicit documentation of assumptions and decisions +- Technical debt remediation plans with GitHub Issue creation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5ba329f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,87 @@ +name: Build and Test + +on: + push: + branches: [ main, release/* ] + pull_request: + branches: [ main, release/* ] + +env: + DOTNET_VERSION: '10.0.x' + +jobs: + build: + name: Build and Test + strategy: + matrix: + os: [macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history for version calculation + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Prepare artifacts directories + shell: bash + run: | + mkdir -p "${{ github.workspace }}/artifacts" + mkdir -p "${{ github.workspace }}/artifacts/test-results" + mkdir -p "${{ github.workspace }}/artifacts/packages" + - name: Build (Windows) + if: runner.os == 'Windows' + run: dotnet build Xamarin.MacDev/Xamarin.MacDev.csproj -c Release -bl:${{ github.workspace }}/artifacts/build.binlog + + - name: Build (macOS) + if: runner.os == 'macOS' + run: dotnet build Xamarin.MacDev/Xamarin.MacDev.csproj -c Release -bl:${{ github.workspace }}/artifacts/build.binlog + + - 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 + + - 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 + + - name: Calculate Version + shell: bash + run: | + HASH_OF_LAST_VERSION_CHANGE=$(git log --follow -1 --pretty=%H nuget.version) + COMMITS_SINCE_VERSION_CHANGE=$(git rev-list --count "$HASH_OF_LAST_VERSION_CHANGE..HEAD") + MAJOR_MINOR=$(cat nuget.version) + VERSION="$MAJOR_MINOR.$COMMITS_SINCE_VERSION_CHANGE" + echo "Package version: $VERSION" + echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Pack NuGet + if: runner.os == 'Windows' + run: dotnet pack Xamarin.MacDev/Xamarin.MacDev.csproj -c Release --no-build -p:PackageVersion=${{ env.PACKAGE_VERSION }} -o ${{ github.workspace }}/artifacts/packages + + - name: Upload Build Artifacts + uses: actions/upload-artifact@v4 + with: + name: build-output-${{ matrix.os }} + path: bin/Release/ + + - name: Upload NuGet Package + uses: actions/upload-artifact@v4 + if: runner.os == 'Windows' + with: + name: nuget-package-${{ matrix.os }} + path: ${{ github.workspace }}/artifacts/packages/*.nupkg + + - name: Upload Logs + uses: actions/upload-artifact@v4 + if: always() + with: + name: logs-${{ matrix.os }} + path: | + ${{ github.workspace }}/artifacts/*.binlog + ${{ github.workspace }}/artifacts/test-results/ diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..dd7c732 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + Debug + https://github.com/dotnet/macios-devtools + git + + + + $(MSBuildThisFileDirectory)bin\ + $(BaseOutputPath)$(Configuration)\ + $(BaseOutputPath)Test$(Configuration)\ + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ec6a519 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +CONFIGURATION := Debug +V ?= 0 + +include eng/scripts/msbuild.mk + +all: + $(MSBUILD) $(MSBUILD_FLAGS) Xamarin.MacDev.sln + +clean: + -$(MSBUILD) $(MSBUILD_FLAGS) /t:Clean Xamarin.MacDev.sln + +run-all-tests: + dotnet test -l "console;verbosity=detailed" -l trx \ + UnitTests/UnitTests.csproj + +pack: + dotnet pack Xamarin.MacDev/Xamarin.MacDev.csproj $(MSBUILD_FLAGS) diff --git a/README.md b/README.md index aa98c19..ad9f03a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This module is a support repository used by both **Xamarin.iOS** and **Xamarin.M It contains support libraries used during msbuild builds. -See [xamarin-macios](https://github.com/xamarin/xamarin-macios/blob/master/README.md#contributing) for more information on contributing to and providing feedback on Xamarin.iOS and Xamarin.Mac. +See [dotnet/macios](https://github.com/dotnet/macios) for more information on contributing to and providing feedback on Xamarin.iOS and Xamarin.Mac. ## License diff --git a/UnitTests/PListObjectTests.cs b/UnitTests/PListObjectTests.cs index 1df301c..5204a0c 100644 --- a/UnitTests/PListObjectTests.cs +++ b/UnitTests/PListObjectTests.cs @@ -96,6 +96,8 @@ public void TestIntegerXmlSerialization () expected = Encoding.UTF8.GetString (buffer); } + output = output.Replace ("\r\n", "\n").Replace ("\r", "\n"); + expected = expected.Replace ("\r\n", "\n").Replace ("\r", "\n"); Assert.That (output, Is.EqualTo (expected)); } diff --git a/UnitTests/TestHelper.cs b/UnitTests/TestHelper.cs index 52004a7..9af1a70 100644 --- a/UnitTests/TestHelper.cs +++ b/UnitTests/TestHelper.cs @@ -52,8 +52,19 @@ static TestHelper () var dir = Path.GetDirectoryName (codeBase); - while (Path.GetFileName (dir) != "UnitTests") - dir = Path.GetFullPath (Path.Combine (dir, "..")); + while (!string.Equals (Path.GetFileName (dir), "UnitTests", StringComparison.Ordinal)) { + var candidate = Path.Combine (dir, "UnitTests"); + if (Directory.Exists (candidate)) { + dir = candidate; + break; + } + + var parent = Path.GetDirectoryName (dir); + if (string.IsNullOrEmpty (parent)) + throw new DirectoryNotFoundException ($"Unable to locate UnitTests directory from '{codeBase}'."); + + dir = parent; + } ProjectDir = Path.GetFullPath (dir); } diff --git a/Xamarin.MacDev/Xamarin.MacDev.csproj b/Xamarin.MacDev/Xamarin.MacDev.csproj index d5a2639..cc10fbd 100644 --- a/Xamarin.MacDev/Xamarin.MacDev.csproj +++ b/Xamarin.MacDev/Xamarin.MacDev.csproj @@ -8,15 +8,50 @@ true enable + + + + Xamarin.MacDev + Microsoft + MIT + https://github.com/dotnet/macios-devtools + Tools for interacting with Apple SDKs. + Copyright © Microsoft + macOS;iOS;Apple + README.md + + + + + $(ToolOutputFullPath) + + + + + true + true + + + + + + + + + runtime; build; native; contentfiles; analyzers all + - - - Microsoft400 - - + + + + + Microsoft400 + + + diff --git a/azure-pipelines.yaml b/azure-pipelines.yaml new file mode 100644 index 0000000..58a9cd4 --- /dev/null +++ b/azure-pipelines.yaml @@ -0,0 +1,91 @@ +name: macios-devtools $(Rev:r) + +trigger: +- main +- release/* + +pr: +- main +- release/* + +variables: + - name: DotNetCoreVersion + value: 10.0.x + +jobs: +- job: build + displayName: Build and Test + timeoutInMinutes: 60 + strategy: + matrix: + macOS: + vmImage: macOS-15 + windows: + vmImage: windows-2022 + pool: + vmImage: $(vmImage) + workspace: + clean: all + steps: + - checkout: self + clean: true + fetchDepth: 0 + + - task: UseDotNet@2 + displayName: Use .NET Core $(DotNetCoreVersion) + inputs: + version: $(DotNetCoreVersion) + + - task: DotNetCoreCLI@2 + displayName: Build Xamarin.MacDev + inputs: + command: build + projects: Xamarin.MacDev/Xamarin.MacDev.csproj + arguments: -c Release -bl:$(Build.ArtifactStagingDirectory)/build.binlog + + - task: DotNetCoreCLI@2 + displayName: Run Tests (Windows) + inputs: + command: test + projects: UnitTests/UnitTests.csproj + arguments: -c Release --logger "console;verbosity=detailed" --logger trx --results-directory $(Build.ArtifactStagingDirectory)/test-results + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + + - task: DotNetCoreCLI@2 + displayName: Run Tests (macOS) + inputs: + command: test + projects: UnitTests/UnitTests.csproj + arguments: -c Release -f net10.0 --logger "console;verbosity=detailed" --logger trx --results-directory $(Build.ArtifactStagingDirectory)/test-results + condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) + + - powershell: | + $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" + $commitsSinceVersionChange = & "git" "rev-list" "--count" "$hashOfLastVersionChange..HEAD" + $majorMinor = Get-Content "nuget.version" + $version = "$majorMinor.$commitsSinceVersionChange" + Write-Host "##vso[task.setvariable variable=xmd.nuget.version]$version" + displayName: Calculate Version (Windows) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + + - task: DotNetCoreCLI@2 + displayName: Build NuGet (Windows) + inputs: + command: custom + projects: Xamarin.MacDev/Xamarin.MacDev.csproj + custom: pack + arguments: -c Release --no-build -p:PackageVersion=$(xmd.nuget.version) -p:PackageOutputPath=$(Build.ArtifactStagingDirectory)/packages -bl:$(Build.ArtifactStagingDirectory)/pack.binlog + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + + - task: PublishPipelineArtifact@1 + displayName: Upload Build Output + inputs: + path: bin/Release + artifactName: Output - $(System.JobName) + + - task: PublishPipelineArtifact@1 + displayName: Upload Artifacts + inputs: + path: $(Build.ArtifactStagingDirectory) + artifactName: Artifacts - $(System.JobName) + condition: always() diff --git a/eng/scripts/msbuild.mk b/eng/scripts/msbuild.mk new file mode 100644 index 0000000..e86a101 --- /dev/null +++ b/eng/scripts/msbuild.mk @@ -0,0 +1,29 @@ +# +# MSBuild Abstraction. +# +# Makefile targets which need to invoke MSBuild should use `$(MSBUILD)`, +# not some specific MSBuild program such as `dotnet build` or `msbuild`. +# +# Typical use will also include `$(MSBUILD_FLAGS)`, which provides the +# Configuration and logging verbosity, as per $(CONFIGURATION) and $(V): +# +# $(MSBUILD) $(MSBUILD_FLAGS) path/to/Project.csproj +# +# Inputs: +# +# $(CONFIGURATION): Build configuration name, e.g. Debug or Release +# $(MSBUILD): The MSBuild program to use. +# $(MSBUILD_ARGS): Extra arguments to pass to $(MSBUILD); embedded into $(MSBUILD_FLAGS) +# $(V): Build verbosity +# +# Outputs: +# +# $(MSBUILD): The MSBuild program to use. Defaults to `dotnet build`. +# $(MSBUILD_FLAGS): Additional MSBuild flags; contains $(CONFIGURATION), $(V), $(MSBUILD_ARGS). + +MSBUILD = dotnet build +MSBUILD_FLAGS = /p:Configuration=$(CONFIGURATION) $(MSBUILD_ARGS) + +ifneq ($(V),0) +MSBUILD_FLAGS += /v:diag +endif # $(V) != 0 diff --git a/nuget.version b/nuget.version new file mode 100644 index 0000000..d69d4fa --- /dev/null +++ b/nuget.version @@ -0,0 +1 @@ +1.0