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
47 changes: 47 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,53 @@ The repo is at 0.1.0-preview so breaking changes are acceptable, but:
- **Signing**: configured in `eng/Signing.props`. New third-party DLLs need a `3PartySHA2` entry.
- **Version**: defined in `eng/Versions.props` (`VersionPrefix` + `VersionSuffix`). Per-product overrides in `src/{Product}/Version.props`.

## Dependency Updates (darc)

Upstream dependencies are managed via [darc/Maestro](https://github.com/dotnet/arcade/blob/main/Documentation/Darc.md). When updating a dependency:

### Updating Xamarin.Apple.Tools.MaciOS

```bash
darc update-dependencies --channel ".NET 10.0.1xx SDK" --name Xamarin.Apple.Tools.MaciOS
```

This updates both `eng/Version.Details.xml` (SHA + version) and `eng/Versions.props` (`XamarinAppleToolsMaciOSVersion`).

**After every update**, check the commits between the old and new SHA for changes that affect CLI behavior:

```bash
# Compare old..new SHA from Version.Details.xml
gh api repos/dotnet/macios-devtools/compare/{oldSha}...{newSha} --jq '.commits[] | .commit.message | split("\n")[0]'
```

Look for:
- New public APIs on `EnvironmentChecker`, `AppleInstaller`, `SimulatorService`, `RuntimeService` that the CLI could leverage
- Bug fixes to simctl JSON parsing, stdout pollution, or ILMerge compatibility that previously required workarounds in our code
- Breaking changes to method signatures the CLI calls (would require code updates)

If the upstream fix removes the need for a workaround in our code, simplify the CLI code accordingly in the same PR.

### Post-Update Smoke Tests (macOS only)

After updating `Xamarin.Apple.Tools.MaciOS` or making changes to `src/Cli/.../Providers/Apple/` or `src/Cli/.../Commands/AppleCommands.cs`, **run the Apple CLI smoke tests** on macOS to verify nothing regressed:

```bash
./eng/smoke-tests/apple-cli-smoke-test.sh
```

The script builds the CLI and runs these checks:
1. `maui apple xcode list --json` — detects installed Xcode
2. `maui apple runtime list --json` — lists simulator runtimes
3. `maui apple simulator list --json` — lists available simulators
4. `maui apple simulator start <name> --json` — boots a simulator
5. `maui apple simulator stop <name> --json` — shuts it down
6. `maui --dry-run apple install --json` — validates install flow (iOS default)
7. `maui --dry-run apple install --platform all --json` — validates all-platform install

You can also pass a pre-built binary: `./eng/smoke-tests/apple-cli-smoke-test.sh path/to/maui`

> **Note**: These tests require macOS with Xcode installed. They are skipped automatically on other platforms. If you are not on macOS, skip this step — CI on macOS runners will catch regressions.

## CI/CD — New Product Checklist

When adding a new product to this repo you **must** set up two CI surfaces: a GitHub Actions workflow for PR validation and a build job + publish stage in the Azure DevOps official pipeline for signing and NuGet.org publishing.
Expand Down
4 changes: 2 additions & 2 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>73b3b5ac0e4a5658c7a0555b67d91a22ad39de4b</Sha>
</Dependency>
<Dependency Name="Xamarin.Apple.Tools.MaciOS" Version="1.0.0-preview.1.26201.1">
<Dependency Name="Xamarin.Apple.Tools.MaciOS" Version="1.0.0-preview.1.26230.1">
<Uri>https://github.com/dotnet/macios-devtools</Uri>
<Sha>14efcb9735fab369066fa3c99130d076aa0dfdc9</Sha>
<Sha>11fad7fe464a7de7dd809542e4bb352310236052</Sha>
</Dependency>
</ProductDependencies>
<ToolsetDependencies>
Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
</PropertyGroup>
<!-- Apple platform tooling (managed via darc/maestro — see eng/Version.Details.xml) -->
<PropertyGroup Label="Apple Tools">
<XamarinAppleToolsMaciOSVersion>1.0.0-preview.1.26201.1</XamarinAppleToolsMaciOSVersion>
<XamarinAppleToolsMaciOSVersion>1.0.0-preview.1.26230.1</XamarinAppleToolsMaciOSVersion>
</PropertyGroup>
<PropertyGroup Label="Platform Extensions">
<PlatformMauiMacOSVersion>0.3.0</PlatformMauiMacOSVersion>
Expand Down
171 changes: 171 additions & 0 deletions eng/smoke-tests/apple-cli-smoke-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/bin/bash
# Apple CLI Smoke Tests
# ---------------------
# Validates the `maui apple` CLI commands work correctly on macOS.
# Run after changes to src/Cli/.../Providers/Apple/ or after updating
# the Xamarin.Apple.Tools.MaciOS package version.
#
# Prerequisites:
# - macOS with Xcode installed
# - .NET SDK (version per global.json)
# - At least one iOS simulator runtime installed
#
# Usage:
# ./eng/smoke-tests/apple-cli-smoke-test.sh [path-to-maui-binary]
#
# If no binary path is provided, builds the CLI in Debug mode first.

set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

PASS=0
FAIL=0
SKIP=0

pass() { echo -e " ${GREEN}✅ PASS${NC}: $1"; PASS=$((PASS + 1)); }
fail() { echo -e " ${RED}❌ FAIL${NC}: $1 — $2"; FAIL=$((FAIL + 1)); }
skip() { echo -e " ${YELLOW}⏭️ SKIP${NC}: $1 — $2"; SKIP=$((SKIP + 1)); }

REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$REPO_ROOT"

# Determine the CLI binary path
if [[ $# -ge 1 ]]; then
MAUI="$1"
else
echo "Building CLI in Debug mode..."
dotnet build src/Cli/Microsoft.Maui.Cli/Microsoft.Maui.Cli.csproj -c Debug --nologo -v q
MAUI="$REPO_ROOT/artifacts/bin/Microsoft.Maui.Cli/Debug/net10.0/maui"
fi

if [[ ! -x "$MAUI" ]]; then
echo -e "${RED}ERROR${NC}: CLI binary not found or not executable at: $MAUI"
exit 1
fi

echo ""
echo "========================================"
echo " Apple CLI Smoke Tests"
echo " Binary: $MAUI"
echo "========================================"
echo ""

# Check we're on macOS
if [[ "$(uname)" != "Darwin" ]]; then
echo -e "${RED}ERROR${NC}: These smoke tests require macOS."
exit 1
fi

# --- Test 1: Xcode List ---
echo "Test 1: maui apple xcode list --json"
OUTPUT=$($MAUI apple xcode list --json 2>&1) || true
if echo "$OUTPUT" | grep -q '"path"'; then
XCODE_VER=$(echo "$OUTPUT" | grep '"version"' | head -1 | sed 's/.*: "//;s/".*//')
pass "Xcode found (version: $XCODE_VER)"
else
fail "Xcode list" "No Xcode installations found in JSON output"
fi

# --- Test 2: Runtime List ---
echo "Test 2: maui apple runtime list --json"
OUTPUT=$($MAUI apple runtime list --json 2>&1) || true
if echo "$OUTPUT" | grep -q '"identifier"'; then
RUNTIME_COUNT=$(echo "$OUTPUT" | grep -c '"identifier"' || true)
pass "Runtimes listed ($RUNTIME_COUNT runtime(s))"
else
fail "Runtime list" "No runtimes found in JSON output"
fi

# --- Test 3: Simulator List ---
echo "Test 3: maui apple simulator list --json"
SIM_OUTPUT=$($MAUI apple simulator list --json 2>&1) || true
if echo "$SIM_OUTPUT" | grep -q '"udid"'; then
SIM_COUNT=$(echo "$SIM_OUTPUT" | grep -c '"udid"' || true)
pass "Simulators listed ($SIM_COUNT simulator(s))"
else
fail "Simulator list" "No simulators found in JSON output"
fi

# --- Test 4: Simulator Start ---
echo "Test 4: maui apple simulator start (boot a simulator)"
# Find the first available iPhone simulator name
SIM_NAME=$(echo "$SIM_OUTPUT" | python3 -c "
import sys, json

text = sys.stdin.read()
lines = text.split('\n')
json_start = next((i for i, l in enumerate(lines) if l.strip().startswith('[')), None)
if json_start is None:
sys.exit(1)
json_text = '\n'.join(lines[json_start:])
# Find end of array
bracket_count = 0
end = 0
for i, ch in enumerate(json_text):
if ch == '[': bracket_count += 1
elif ch == ']': bracket_count -= 1
if bracket_count == 0:
end = i + 1
break
data = json.loads(json_text[:end])
for d in data:
if d.get('is_available') and not d.get('is_booted') and 'iPhone' in d.get('name', ''):
print(d['name'])
sys.exit(0)
sys.exit(1)
" 2>/dev/null) || SIM_NAME=""

if [[ -z "$SIM_NAME" ]]; then
skip "Simulator start" "No available iPhone simulator found to boot"
else
START_OUTPUT=$($MAUI apple simulator start "$SIM_NAME" --json 2>&1) || true
if echo "$START_OUTPUT" | grep -q '"success"' || echo "$START_OUTPUT" | grep -q '"status": "success"'; then
pass "Simulator '$SIM_NAME' booted"

# --- Test 5: Simulator Stop ---
echo "Test 5: maui apple simulator stop (shut down simulator)"
STOP_OUTPUT=$($MAUI apple simulator stop "$SIM_NAME" --json 2>&1) || true
if echo "$STOP_OUTPUT" | grep -q '"success"' || echo "$STOP_OUTPUT" | grep -q '"status": "success"'; then
pass "Simulator '$SIM_NAME' stopped"
else
fail "Simulator stop" "Failed to shut down '$SIM_NAME'"
fi
else
fail "Simulator start" "Failed to boot '$SIM_NAME'"
SKIP=$((SKIP + 1)) # skip the stop test
fi
fi

# --- Test 6: Install (dry-run, default platform = iOS) ---
echo "Test 6: maui --dry-run apple install --json"
INSTALL_OUTPUT=$($MAUI --dry-run apple install --json 2>&1) || true
if echo "$INSTALL_OUTPUT" | grep -q '"status"'; then
STATUS=$(echo "$INSTALL_OUTPUT" | grep '"status"' | head -1 | sed 's/.*: "//;s/".*//')
pass "Install dry-run completed (status: $STATUS)"
else
fail "Install dry-run" "No status in JSON output"
fi

# --- Test 7: Install with --platform all (dry-run) ---
echo "Test 7: maui --dry-run apple install --platform all --json"
INSTALL_ALL_OUTPUT=$($MAUI --dry-run apple install --platform all --json 2>&1) || true
if echo "$INSTALL_ALL_OUTPUT" | grep -q '"status"'; then
pass "Install --platform all dry-run completed"
else
fail "Install --platform all dry-run" "No status in JSON output"
fi

# --- Summary ---
echo ""
echo "========================================"
echo -e " Results: ${GREEN}$PASS passed${NC}, ${RED}$FAIL failed${NC}, ${YELLOW}$SKIP skipped${NC}"
echo "========================================"

if [[ $FAIL -gt 0 ]]; then
exit 1
fi
exit 0
Loading
Loading