diff --git a/.agents/skills/tizen-doctor/SKILL.md b/.agents/skills/tizen-doctor/SKILL.md new file mode 100644 index 000000000..6dcb49396 --- /dev/null +++ b/.agents/skills/tizen-doctor/SKILL.md @@ -0,0 +1,137 @@ +--- +name: tizen-doctor +description: Diagnose a Tizen .NET development environment and guide the user to a working build. Use when `dotnet build` fails on a Tizen TFM such as `net6.0-tizen8.0`, `net6.0-tizen9.0`, `net8.0-tizen10.0`, or `net8.0-tizen11.0` (NETSDK1139, NETSDK1147, missing Tizen.NET.Sdk), when setting up a fresh machine for TizenFX / Tizen .NET development, when the Tizen Workload appears installed but target framework resolution still fails, when `sdb` / emulator / certificate tooling is missing, or whenever the user asks "why won't this Tizen project build?". Covers Linux (incl. WSL2), Windows, and macOS. Targets Tizen API Level 11 – 14 (platform 8.0 / 9.0 / 10.0 / 11.0) on .NET 6 and .NET 8 SDK bands. +license: MIT +--- + +# tizen-doctor + +Diagnose and repair a Tizen .NET development environment. + +This skill is the entry point for **"my Tizen build is broken, help me figure out why."** It collects a structured snapshot of the environment, compares it to the known-good configuration matrix, and returns a prioritized list of fixes. + +## When to use this skill + +Trigger this skill when any of the following is true: + +- `dotnet build` against a project with a Tizen TFM — `net6.0-tizen8.0`, `net6.0-tizen9.0`, `net8.0-tizen10.0`, `net8.0-tizen11.0`, or the unversioned `net6.0-tizen` / `net8.0-tizen` fallbacks — fails with: + - `NETSDK1139` — target platform not recognized + - `NETSDK1147` — workload missing + - `The SDK 'Tizen.NET.Sdk' specified could not be found` + - `Package Tizen.NET.API* is not compatible` +- The user is setting up a fresh development machine for TizenFX, a Tizen .NET application, or a Tizen library. +- `dotnet workload list` shows `tizen` as installed but builds still fail. +- Tooling problems around `sdb`, emulator launch, or certificate profile creation. +- The user explicitly asks "what do I need to install to build a Tizen app / TizenFX?" + +## When NOT to use this skill + +- Runtime issues on an already-built app (use Tizen runtime / log skills instead). +- NUI layout or rendering bugs (use a NUI-specific skill if available). +- API Level compatibility questions unrelated to build failure (use `tizen-api` when available). +- Pure C# / .NET language questions with no Tizen component. + +## Inputs + +The skill needs, at minimum: + +1. **Host OS**: Linux (distro + version, or WSL2), Windows, or macOS. +2. **Error signature**: exact message or MSBuild error code if the user has one. +3. **Project kind**: TizenFX itself (uses `build.sh`), a Tizen application, or a library that targets a `net*-tizen` TFM. + +If any of these are missing, ask the user for them before running diagnostics — the script picks different checks based on OS. + +## Workflow + +### Step 1 — Collect environment + +Run the collector script for the host OS. It prints a single YAML-ish report covering .NET SDK, installed workloads, Tizen Studio / `sdb`, Java runtime, certificate profiles, and environment variables. + +```bash +# Linux / macOS / WSL2 +bash scripts/collect-env.sh + +# Windows — built-in PowerShell 5.1 +powershell.exe -ExecutionPolicy Bypass -File scripts/collect-env.ps1 + +# Windows — PowerShell 7+ (if installed) +pwsh -File scripts/collect-env.ps1 +``` + +If `bash` / `pwsh` is not available, fall back to the manual checklist in `references/manual-env-checklist.md` (add only if the scripts cannot run). + +### Step 2 — Diagnose + +Compare the collected report against `references/tfm-workload-matrix.md`. Classify each finding into one of: + +| Severity | Meaning | +|---|---| +| 🔴 **Blocker** | Build will fail until fixed. Examples: missing .NET SDK, missing Tizen Workload, wrong SDK version for the requested TFM. | +| 🟡 **Likely** | Fix is probably needed for the user's goal but build may still partially succeed. Examples: missing `sdb`, no signing certificate, outdated Workload. | +| 🟢 **Optional** | Nice-to-have. Examples: emulator images, IDE extensions. | + +Only flag items the user's stated goal actually needs. Installing the emulator is irrelevant if they only want to run unit tests. + +### Step 3 — Produce the fix list + +Return a **prioritized, copy-pasteable** fix list, grouped by severity. Each fix must include: + +1. The exact command to run. +2. A one-line explanation of why it is needed. +3. A verification command so the user can confirm the fix worked. + +Template: + +```markdown +## 🔴 Blockers + +1. **Install the Tizen Workload** + - Why: `net8.0-tizen11.0` (and sibling Tizen TFMs) require `Tizen.NET.Sdk`, which ships via the Tizen Workload. **The Tizen Workload is not on the public dotnet workload feed — installing it via `dotnet workload install tizen` will not work.** It is bootstrapped from a script in the Samsung/Tizen.NET repo. + - Command (Linux / macOS / WSL): + ```bash + curl -sSL https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | sudo bash + ``` + - Command (Windows PowerShell): + ```powershell + Invoke-WebRequest 'https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.ps1' -OutFile 'workload-install.ps1' + ./workload-install.ps1 + ``` + - Verify: `dotnet workload list` shows `tizen` with a version matching the active SDK band. + - Updating: re-run the same script — `dotnet workload update` does **not** update the Tizen workload. + +2. ... + +## 🟡 Likely + +... + +## 🟢 Optional + +... +``` + +End the response with a single next-command suggestion — the one thing the user should run *right now* to move forward. + +## Common pitfalls + +| Symptom | Root cause | Fix | +|---|---|---| +| `NETSDK1139` on `net8.0-tizen11.0` (or any `net*-tizen*` TFM) | Tizen Workload not installed for the current .NET SDK | Run Samsung's `workload-install.{sh,ps1}` from `Samsung/Tizen.NET/workload/scripts/`. **Note: `dotnet workload install tizen` does NOT work — Tizen workload is not on the public feed.** | +| Workload installed, TFM still unresolved | Mixed .NET SDKs on PATH; `dotnet` resolves to a version without the workload | Check `dotnet --info` and `dotnet --list-sdks`; install workload for the active SDK | +| `build.sh` fails on Windows | TizenFX `build.sh` assumes a POSIX shell | Use WSL2 or Git Bash; native Windows `dotnet build` works for app projects but not for the TizenFX repo itself | +| `Tizen.NET.API*` package restore fails | Project TFM's platform suffix does not match the API package version (e.g., `net6.0-tizen9.0` ↔ API12, `net8.0-tizen11.0` ↔ API14) | Pin the correct TFM or re-run Samsung's `workload-install.{sh,ps1}` to refresh to the latest workload (the standard `dotnet workload update` does not update the Tizen workload) | +| Workload is listed but a specific TFM still fails (observed: .NET 10 SDK + `tizen 10.0.123` + `net8.0-tizen11.0`) | Installed workload's `WorkloadManifest.json` is missing the `Samsung.Tizen.Ref.API` pack for that TFM | Inspect the manifest (see `references/tfm-workload-matrix.md`); install the SDK band whose manifest includes the needed Ref pack | +| `sdb: command not found` | Tizen Studio not installed, or `tools/` not on PATH | Install Tizen Studio; prepend `$TIZEN_STUDIO/tools` to `PATH` | +| Cert signing fails silently | No active certificate profile | Create via Tizen Certificate Manager or `tizen security-profiles add` | + +## Out-of-scope (redirect) + +- "How do I write XAML for NUI?" → not this skill. +- "Which Tizen API Level introduced X?" → use `tizen-api` once available. +- "My app crashes on the device" → use `tizen-runtime` / log skills. + +## Notes for maintainers + +- Keep `references/tfm-workload-matrix.md` current — Tizen Workload versions ship alongside .NET SDK band updates. +- When adding a new error signature, add one row to the **Common pitfalls** table above and one matching entry in `references/error-signatures.md` (create that file if needed). +- Scripts must degrade gracefully: if a command is missing, print `not-found` rather than failing the whole collector. diff --git a/.agents/skills/tizen-doctor/references/tfm-workload-matrix.md b/.agents/skills/tizen-doctor/references/tfm-workload-matrix.md new file mode 100644 index 000000000..d3f5274ec --- /dev/null +++ b/.agents/skills/tizen-doctor/references/tfm-workload-matrix.md @@ -0,0 +1,167 @@ +# Tizen TFM ↔ Workload ↔ API Level matrix + +**Last reviewed:** 2026-04-24 + +Quick lookup for matching a project's target framework moniker (TFM) with the +right .NET SDK band, Tizen Workload version, Tizen platform version, and the +`Tizen.NET.APInn` package it implicitly depends on. + +Use this to diagnose `NETSDK1139` (target platform not recognized) and +`NETSDK1147` (workload missing) errors, and to tell whether a machine's +current install can build a given project. + +## TFM naming convention + +Tizen TFMs have the form: + +``` +net-tizen +``` + +The platform suffix is **required** for formal projects — `net8.0-tizen` without +a version suffix resolves to a generic moniker that does not pin a Tizen.NET.API +version and should be avoided in shipping code. The formal, versioned forms are: + +| TFM | .NET SDK band | Tizen Workload band | Tizen platform | API Level | Implicit `Tizen.NET.API*` package | +|---------------------|---------------|---------------------|----------------|-----------|-----------------------------------| +| `net6.0-tizen8.0` | .NET 6 SDK | `6.0.*` | 8.0 | 11 | `Tizen.NET.API11` | +| `net6.0-tizen9.0` | .NET 6 SDK | `6.0.*` | 9.0 | 12 | `Tizen.NET.API12` | +| `net8.0-tizen10.0` | .NET 8 SDK | `8.0.*` | 10.0 | 13 | `Tizen.NET.API13` | +| `net8.0-tizen11.0` | .NET 8 SDK | `8.0.*` | 11.0 | 14 | `Tizen.NET.API14` | + +Unversioned fallbacks that still resolve (via `Tizen.NET.nuspec` dependency groups) +but are not recommended for new project files: + +| TFM | Resolves to | +|---------------|-------------| +| `net6.0-tizen` | latest `net6.0-tizen*` group — currently `Tizen.NET.API12` | +| `net8.0-tizen` | latest `net8.0-tizen*` group — currently `Tizen.NET.API14` | +| `net6.0` | `Tizen.NET.API12` | +| `net8.0` | `Tizen.NET.API14` | + +(Derived from the `` entries in `pkg/Tizen.NET/Tizen.NET.nuspec`.) + +The Workload version tracks the .NET SDK band — picking `net8.0-tizen11.0` means +the Tizen Workload for the .NET 8 SDK band must be installed; the `tizen11.0` +suffix is a **platform binding**, not a workload selector. + +### Reading `dotnet workload list` output correctly + +The Tizen workload's `Manifest Version` column looks like `10.0.123/10.0.100`. +The leading `10.0.x` is the **.NET SDK feature band the workload manifest ships +with**, not a new TFM. A `tizen 10.0.*` workload on a machine with only a +.NET 10 SDK does **not** automatically mean `net10.0-tizenX.0` TFMs exist — the +manifest may still only expose the TFMs listed in the matrix above. + +To confirm which TFMs a given workload actually exposes, inspect the +`WorkloadManifest.json` on disk. On Windows it lives at: + +``` +%ProgramFiles%\dotnet\sdk-manifests\\samsung.net.sdk.tizen\WorkloadManifest.json +``` + +On Linux / macOS the typical path is: + +``` +/usr/share/dotnet/sdk-manifests//samsung.net.sdk.tizen/WorkloadManifest.json +``` + +### What the manifest tells you + +Inside `WorkloadManifest.json`, the authoritative list is the +`workloads.tizen.packs` array and the `packs` dictionary. Look for entries +of the form `Samsung.Tizen.Ref.API` — each one corresponds to a specific +Tizen platform version: + +| Ref pack | Matching TFM | API Level | +|---------------------------|-----------------------|-----------| +| `Samsung.Tizen.Ref.API11` | `net6.0-tizen8.0` | 11 | +| `Samsung.Tizen.Ref.API12` | `net6.0-tizen9.0` | 12 | +| `Samsung.Tizen.Ref.API13` | `net8.0-tizen10.0` | 13 | +| `Samsung.Tizen.Ref.API14` | `net8.0-tizen11.0` | 14 | + +**If an API Ref pack is missing from the installed workload, the matching +TFM will fail to build on that machine — even if the `tizen` workload row +appears in `dotnet workload list`.** Workload manifests lag nuspec updates, +so a freshly-published TFM in `Tizen.NET.nuspec` may not yet have a +corresponding Ref pack in every SDK band's manifest. + +The `collect-env.sh` / `collect-env.ps1` scripts that ship with this skill +automatically locate and parse every `samsung.net.sdk.tizen` manifest on +disk and print the Ref pack list under `tizen_workload_manifests:`. Use +that output as the authoritative answer for "can this machine build TFM X?". + +### Observed as of 2026-04-24 + +- `.NET 10` SDK band (`samsung.net.sdk.tizen` manifest version `10.0.123`) + ships Ref packs for **API11, API12, API13** only — **API14 is not yet + included**. Projects targeting `net8.0-tizen11.0` therefore cannot be + built with a .NET 10-only install and the .NET 10 band Tizen workload; + the user must install the .NET 8 SDK and its matching Tizen workload. + +## Installing / updating the Workload + +> ⚠️ **The Tizen Workload is NOT on the public dotnet workload feed.** +> The standard commands (`dotnet workload install tizen`, +> `dotnet workload update`) do **not** install or update the Tizen workload. +> You must use the install scripts shipped from the `Samsung/Tizen.NET` repo: + +**Linux / macOS / WSL:** + +```bash +curl -sSL https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | sudo bash +``` + +**Windows (PowerShell):** + +```powershell +Invoke-WebRequest 'https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.ps1' -OutFile 'workload-install.ps1' +./workload-install.ps1 +``` + +**Updating** is the same script re-run — there is no separate update command. + +After install, verify with the standard commands (these *do* work for inspection): + +```bash +dotnet workload list +dotnet --info # confirms which .NET SDK band the workload was installed against +``` + +If the project targets `net8.0-tizen11.0` and the machine has both .NET 6 and +.NET 8 SDKs, the workload must be installed for **the .NET 8 SDK**. The Samsung +install script resolves against the currently active `dotnet` (the one +`dotnet --version` prints) — check `dotnet --info` to confirm which SDK will +be patched before running the script. + +## Common mismatches and their signatures + +| Symptom | Likely cause | +|--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| +| `NETSDK1139 The target platform identifier tizen was not recognized` | Workload for the active SDK band is not installed. | +| `NETSDK1147 To build this project, the following workloads must be installed: tizen` | Same as above; .NET 7+ gives a more explicit message. | +| Build succeeds locally but CI fails with `Tizen.NET.Sdk not found` | CI image is on a different SDK band than local; install the matching workload in the pipeline. | +| `Package Tizen.NET.API13 is not compatible with net6.0-tizen9.0` | API Level package version is tied to the TFM's platform suffix — API13 is for `net8.0-tizen10.0`, not `net6.0-*`. | +| `dotnet workload list` shows `tizen` but `dotnet build` still errors with NETSDK1139 | `dotnet` on PATH resolves to a *different* SDK than the one the workload was installed against. | +| `net8.0-tizen` resolves to the wrong API package | Unversioned TFM — pin to `net8.0-tizen10.0` or `net8.0-tizen11.0` instead. | +| Machine has `tizen 10.0.*` workload but no .NET 6 / .NET 8 SDK, `net8.0-tizen11.0` build still fails | `.NET 10` band manifest (`10.0.123` as of 2026-04) ships Ref packs only up to `Samsung.Tizen.Ref.API13` — no `API14`. Install `.NET 8 SDK` and its tizen workload. | +| Workload listed but a specific TFM (e.g. `net8.0-tizen11.0`) refuses to resolve | Missing `Samsung.Tizen.Ref.API` pack in that workload's manifest — inspect `WorkloadManifest.json` directly, don't trust `dotnet workload list` alone. | + +## Cross-references + +- Agent workflow that uses this table: [`../SKILL.md`](../SKILL.md) +- Upstream Workload release notes: +- TizenFX API Level contract & Tizen.NET.nuspec: + +## Maintenance + +This reference goes stale whenever a new Tizen Workload or platform version +ships. When updating: + +1. Bump the **Last reviewed** date at the top. +2. Cross-check against `pkg/Tizen.NET/Tizen.NET.nuspec` in TizenFX — each + `` entry there is the source of truth for the + TFM ↔ API package mapping. +3. Add a new row to the TFM matrix when a new `net.0-tizen.0` pair is + published. +4. Capture any newly-observed error signatures in the "Common mismatches" table. diff --git a/.agents/skills/tizen-doctor/scripts/collect-env.ps1 b/.agents/skills/tizen-doctor/scripts/collect-env.ps1 new file mode 100644 index 000000000..140cd9c0d --- /dev/null +++ b/.agents/skills/tizen-doctor/scripts/collect-env.ps1 @@ -0,0 +1,172 @@ +# collect-env.ps1 — snapshot a Tizen .NET development environment on Windows. +# +# Output is a single YAML-ish block on stdout so the calling agent can parse it. +# All probes degrade gracefully: missing commands print `not-found` instead of failing. +# +# Usage: pwsh -File collect-env.ps1 +# or: powershell -ExecutionPolicy Bypass -File collect-env.ps1 + +$ErrorActionPreference = 'Continue' + +function Have($cmd) { + $null -ne (Get-Command $cmd -ErrorAction SilentlyContinue) +} + +function Try-Run([scriptblock]$block, [string]$fallback = 'not-found') { + try { + $out = & $block 2>$null + if (-not $out) { return $fallback } + return $out + } catch { + return $fallback + } +} + +# --- Host --------------------------------------------------------------------- +Write-Output "host:" +Write-Output " os: Windows" +Write-Output " version: $([System.Environment]::OSVersion.VersionString)" +Write-Output " arch: $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)" +Write-Output " ps_version: $($PSVersionTable.PSVersion)" + +# --- .NET --------------------------------------------------------------------- +Write-Output "dotnet:" +if (Have dotnet) { + Write-Output " path: $((Get-Command dotnet).Source)" + Write-Output " version: $(Try-Run { dotnet --version })" + Write-Output " sdks:" + $sdks = Try-Run { dotnet --list-sdks } + if ($sdks -eq 'not-found') { + Write-Output " - not-found" + } else { + $sdks -split "`n" | Where-Object { $_ } | ForEach-Object { Write-Output " - $_" } + } + Write-Output " runtimes:" + $rts = Try-Run { dotnet --list-runtimes } + if ($rts -eq 'not-found') { + Write-Output " - not-found" + } else { + $rts -split "`n" | Where-Object { $_ } | ForEach-Object { Write-Output " - $_" } + } + Write-Output " workloads:" + $wl = Try-Run { dotnet workload list } + if ($wl -eq 'not-found') { + Write-Output " not-found" + } else { + $wl -split "`n" | Where-Object { $_ } | ForEach-Object { Write-Output " $_" } + } +} else { + Write-Output " status: not-found" +} + +# --- Tizen Studio / sdb ------------------------------------------------------- +Write-Output "tizen_studio:" +$tz = [System.Environment]::GetEnvironmentVariable('TIZEN_STUDIO') +if ($tz) { + Write-Output " env_TIZEN_STUDIO: $tz" +} else { + Write-Output " env_TIZEN_STUDIO: not-set" +} +if (Have sdb) { + Write-Output " sdb_path: $((Get-Command sdb).Source)" + $sdbv = Try-Run { sdb version } | Select-Object -First 1 + Write-Output " sdb_version: $sdbv" +} else { + Write-Output " sdb_path: not-found" +} +if (Have tizen) { + Write-Output " tizen_cli_path: $((Get-Command tizen).Source)" + $tcv = Try-Run { tizen version } | Select-Object -First 1 + Write-Output " tizen_cli_version: $tcv" +} else { + Write-Output " tizen_cli_path: not-found" +} + +# --- Java (needed by Tizen Studio signer) ------------------------------------- +Write-Output "java:" +if (Have java) { + Write-Output " path: $((Get-Command java).Source)" + # java -version writes to stderr + $jv = Try-Run { (java -version 2>&1) } | Select-Object -First 1 + Write-Output " version: $jv" +} else { + Write-Output " status: not-found" +} + +# --- Tizen workload manifests ------------------------------------------------- +# Authoritative source of which TFMs the installed 'tizen' workload can build. +Write-Output "tizen_workload_manifests:" +$manifestRoots = @( + (Join-Path $env:ProgramFiles 'dotnet\sdk-manifests'), + (Join-Path ${env:ProgramFiles(x86)} 'dotnet\sdk-manifests'), + (Join-Path $env:USERPROFILE '.dotnet\sdk-manifests') +) | Where-Object { $_ -and (Test-Path $_) } + +$found = $false +foreach ($root in $manifestRoots) { + Get-ChildItem -Path $root -Recurse -Filter 'WorkloadManifest.json' -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match 'samsung\.net\.sdk\.tizen' } | + ForEach-Object { + $found = $true + $mf = $_.FullName + Write-Output " - path: $mf" + try { + $json = Get-Content $mf -Raw | ConvertFrom-Json + $ver = if ($json.version) { $json.version } else { 'unknown' } + Write-Output " version: $ver" + Write-Output " api_ref_packs:" + $packNames = @() + if ($json.packs) { + # PSCustomObject — enumerate property names + $packNames = $json.packs.PSObject.Properties.Name | + Where-Object { $_ -match '^Samsung\.Tizen\.Ref\.API' } | + Sort-Object + } + if ($packNames.Count -eq 0) { + Write-Output " - none" + } else { + $packNames | ForEach-Object { Write-Output " - $_" } + } + } catch { + Write-Output " version: parse-error" + Write-Output " api_ref_packs: parse-error" + } + } +} +if (-not $found) { Write-Output " status: not-found" } + +# --- Certificate profiles ----------------------------------------------------- +Write-Output "tizen_certificates:" +$tzData = [System.Environment]::GetEnvironmentVariable('TIZEN_STUDIO_DATA') +if (-not $tzData) { $tzData = Join-Path $env:USERPROFILE 'tizen-studio-data' } +$profilesXml = Join-Path $tzData 'profile\profiles.xml' +if (Test-Path $profilesXml) { + Write-Output " profiles_xml: $profilesXml" + try { + [xml]$xml = Get-Content $profilesXml + $xml.profiles.profile | ForEach-Object { Write-Output " - $($_.name)" } + } catch { + Write-Output " - parse-error" + } +} else { + Write-Output " profiles_xml: not-found" +} + +# --- TizenFX repo hints ------------------------------------------------------- +Write-Output "repo:" +if ((Test-Path .\build.sh) -and (Test-Path .\src)) { + Write-Output " looks_like_tizenfx: true" + Write-Output " note: build.sh assumes POSIX shell; use WSL2 or Git Bash on Windows" +} else { + Write-Output " looks_like_tizenfx: false" +} + +# --- PATH hygiene ------------------------------------------------------------- +Write-Output "path_hygiene:" +Write-Output " dotnet_on_path: $((Have dotnet).ToString().ToLower())" +Write-Output " sdb_on_path: $((Have sdb).ToString().ToLower())" +Write-Output " multiple_dotnet_binaries:" +$env:Path -split ';' | Where-Object { $_ } | ForEach-Object { + $candidate = Join-Path $_ 'dotnet.exe' + if (Test-Path $candidate) { Write-Output " - $candidate" } +} | Sort-Object -Unique diff --git a/.agents/skills/tizen-doctor/scripts/collect-env.sh b/.agents/skills/tizen-doctor/scripts/collect-env.sh new file mode 100755 index 000000000..8e9ccdb48 --- /dev/null +++ b/.agents/skills/tizen-doctor/scripts/collect-env.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# collect-env.sh — snapshot a Tizen .NET development environment on Linux / macOS / WSL2. +# +# Output is a single YAML-ish block on stdout so the calling agent can parse it. +# All probes degrade gracefully: missing commands print `not-found` instead of failing. +# +# Usage: bash collect-env.sh + +set -u + +have() { command -v "$1" >/dev/null 2>&1; } + +print_kv() { + # $1 = key, $2 = value (may contain newlines — will be indented under the key) + local key="$1" + local value="${2:-not-found}" + if [[ "$value" == *$'\n'* ]]; then + echo "${key}: |" + printf '%s\n' "$value" | sed 's/^/ /' + else + echo "${key}: ${value}" + fi +} + +# --- Host --------------------------------------------------------------------- +echo "host:" +echo " os: $(uname -s)" +echo " kernel: $(uname -r)" +echo " arch: $(uname -m)" +if [[ -r /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + echo " distro: ${PRETTY_NAME:-unknown}" +fi +if grep -qi microsoft /proc/version 2>/dev/null; then + echo " wsl: true" +else + echo " wsl: false" +fi + +# --- .NET --------------------------------------------------------------------- +echo "dotnet:" +if have dotnet; then + echo " path: $(command -v dotnet)" + echo " version: $(dotnet --version 2>/dev/null || echo not-found)" + echo " sdks:" + dotnet --list-sdks 2>/dev/null | sed 's/^/ - /' || echo " - not-found" + echo " runtimes:" + dotnet --list-runtimes 2>/dev/null | sed 's/^/ - /' || echo " - not-found" + echo " workloads:" + # `dotnet workload list` requires SDK 6+; swallow errors + dotnet workload list 2>/dev/null | sed 's/^/ /' || echo " not-found" +else + echo " status: not-found" +fi + +# --- Tizen Studio / sdb ------------------------------------------------------- +echo "tizen_studio:" +if [[ -n "${TIZEN_STUDIO:-}" ]]; then + echo " env_TIZEN_STUDIO: ${TIZEN_STUDIO}" +else + echo " env_TIZEN_STUDIO: not-set" +fi +if have sdb; then + echo " sdb_path: $(command -v sdb)" + echo " sdb_version: $(sdb version 2>&1 | head -n1)" +else + echo " sdb_path: not-found" +fi +if have tizen; then + echo " tizen_cli_path: $(command -v tizen)" + echo " tizen_cli_version: $(tizen version 2>&1 | head -n1)" +else + echo " tizen_cli_path: not-found" +fi + +# --- Java (needed by Tizen Studio signer) ------------------------------------- +echo "java:" +if have java; then + echo " path: $(command -v java)" + # java -version writes to stderr + echo " version: $(java -version 2>&1 | head -n1)" +else + echo " status: not-found" +fi + +# --- Tizen workload manifests ------------------------------------------------- +# Authoritative source of which TFMs the installed 'tizen' workload can build. +# We look for `samsung.net.sdk.tizen/WorkloadManifest.json` across the standard +# dotnet SDK manifest roots. Extraction degrades gracefully: jq → python3 → grep. +echo "tizen_workload_manifests:" +_TZ_MANIFEST_PATHS=( + /usr/share/dotnet/sdk-manifests + /usr/local/share/dotnet/sdk-manifests + /usr/lib/dotnet/sdk-manifests + "$HOME/.dotnet/sdk-manifests" +) +_TZ_FOUND=0 +for root in "${_TZ_MANIFEST_PATHS[@]}"; do + [[ -d "$root" ]] || continue + # shellcheck disable=SC2044 + while IFS= read -r -d '' mf; do + _TZ_FOUND=1 + echo " - path: $mf" + # Version + if have jq; then + ver=$(jq -r '.version // "unknown"' "$mf" 2>/dev/null) + elif have python3; then + ver=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('version','unknown'))" "$mf" 2>/dev/null) + else + ver=$(grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' "$mf" | head -n1 | sed -E 's/.*"([^"]+)"$/\1/') + fi + echo " version: ${ver:-unknown}" + # API Ref packs + echo " api_ref_packs:" + if have jq; then + jq -r '.packs | keys[] | select(test("^Samsung\\.Tizen\\.Ref\\.API"))' "$mf" 2>/dev/null | sed 's/^/ - /' + elif have python3; then + python3 -c " +import json,sys +d=json.load(open(sys.argv[1])) +for k in sorted(d.get('packs',{})): + if k.startswith('Samsung.Tizen.Ref.API'): + print(' - '+k)" "$mf" 2>/dev/null + else + # Fallback: grep the pack keys; may miss subtle format changes + grep -oE '"Samsung\.Tizen\.Ref\.API[0-9]+"' "$mf" | sort -u | sed -E 's/"(.+)"/ - \1/' + fi + done < <(find "$root" -type f -name WorkloadManifest.json -path "*samsung.net.sdk.tizen*" -print0 2>/dev/null) +done +[[ $_TZ_FOUND -eq 0 ]] && echo " status: not-found" + +# --- Certificate profiles ----------------------------------------------------- +echo "tizen_certificates:" +PROFILES_XML="${TIZEN_STUDIO_DATA:-$HOME/tizen-studio-data}/profile/profiles.xml" +if [[ -r "$PROFILES_XML" ]]; then + echo " profiles_xml: $PROFILES_XML" + # shellcheck disable=SC2016 + grep -oE 'profile name="[^"]+"' "$PROFILES_XML" 2>/dev/null | sed 's/^/ - /' || true +else + echo " profiles_xml: not-found" +fi + +# --- TizenFX repo hints ------------------------------------------------------- +echo "repo:" +if [[ -f build.sh && -d src ]]; then + echo " looks_like_tizenfx: true" + echo " build_sh_executable: $([[ -x build.sh ]] && echo yes || echo no)" +else + echo " looks_like_tizenfx: false" +fi + +# --- PATH hygiene ------------------------------------------------------------- +echo "path_hygiene:" +echo " dotnet_on_path: $(have dotnet && echo yes || echo no)" +echo " sdb_on_path: $(have sdb && echo yes || echo no)" +echo " multiple_dotnet_binaries:" +# Find every `dotnet` binary reachable via PATH; useful when user has both +# the Microsoft install and a Homebrew / snap copy. +( IFS=:; for p in $PATH; do [[ -x "$p/dotnet" ]] && echo " - $p/dotnet"; done ) | sort -u diff --git a/.gemini/config.yaml b/.gemini/config.yaml new file mode 100644 index 000000000..d8b457f1e --- /dev/null +++ b/.gemini/config.yaml @@ -0,0 +1,35 @@ +# Gemini Code Assist configuration for Samsung/Tizen.NET +# Docs: https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github + +have_fun: false + +code_review: + disable: false + # MEDIUM: only flag meaningful issues. Lower (LOW) = noisy, higher (HIGH) = misses subtle bugs. + comment_severity_threshold: MEDIUM + # Hard cap to avoid drowning contributors in bot noise on large PRs. + max_review_comments: 5 + pull_request_opened: + help: false + summary: true + code_review: true + +# Skip generated, vendored, and non-source files. +ignore_patterns: + # Build / IDE artifacts + - "**/bin/**" + - "**/obj/**" + - "**/.vs/**" + - "**/*.Designer.cs" + - "**/*.generated.cs" + + # Data file (workload version map) — not source code, line-by-line review adds noise. + - "workload/scripts/version-map.json" + + # Docs and assets + - "**/*.md" + - "assets/**" + + # Lockfiles / package metadata + - "**/packages.lock.json" + - "**/NuGet.config" diff --git a/.gemini/styleguide.md b/.gemini/styleguide.md new file mode 100644 index 000000000..97c096c0d --- /dev/null +++ b/.gemini/styleguide.md @@ -0,0 +1,20 @@ +# Tizen.NET Code Review Style Guide + +This guide instructs Gemini Code Assist on how to review pull requests in the +`Samsung/Tizen.NET` repository. The repo provides the **Tizen workload for the +.NET SDK**, so reviews must consider workload manifests, install scripts, and +multi-branch (per-SDK-band) layout — not just C# code style. + +## 1. Project Context + +- This repo ships the **Tizen workload for the .NET SDK**. +- Active branches map to .NET SDK bands (e.g., `main`, `net10.0`, `net9.0`, `net8.0`, `net7.0`, `net6.0`). The same file may have legitimately different content across branches — don't reflexively suggest "syncing" content across branches. +- `workload/scripts/workload-install.sh` and `workload-install.ps1` are end-user-facing install scripts. Behavior changes there have user-visible blast radius. + +## 2. Tone / What NOT to comment on + +- Do **not** nitpick `.md`, `assets/`, or generated files (already in `ignore_patterns`, but reinforced here). +- Do **not** suggest churning existing code style just because newer C# syntax exists. +- Do **not** propose architectural rewrites in a small PR. Keep suggestions scoped to the diff. +- Do **not** reflexively suggest adding tests for trivial constant changes or version bumps. +- Reviews should be terse and technical. No emoji, no congratulatory language. diff --git a/.github/workflows/build-workload.yml b/.github/workflows/build-workload.yml index c9694a69c..5649ff54b 100644 --- a/.github/workflows/build-workload.yml +++ b/.github/workflows/build-workload.yml @@ -18,6 +18,10 @@ on: description: 'Pre-release tag name' required: true +permissions: + contents: read + packages: read + jobs: build: runs-on: ubuntu-22.04 @@ -26,6 +30,14 @@ jobs: with: fetch-depth: 0 + - name: Authenticate GitHub Packages NuGet source + run: | + dotnet nuget update source github \ + --username github-actions \ + --password ${{ secrets.GITHUB_TOKEN }} \ + --store-password-in-clear-text \ + --configfile workload/NuGet.config + - name: Build env: PULLREQUEST_ID: ${{ github.event.number }} @@ -70,9 +82,3 @@ jobs: --repository ${{ github.repository }} \ --retries 3 Samsung.*.nupkg - - name: Push packages to MyGet - run: | - dotnet nuget push Samsung.*.nupkg \ - -k ${{ secrets.MYGET_APIKEY }} \ - -s https://tizen.myget.org/F/dotnet/api/v2/package \ - -t 3000 diff --git a/.github/workflows/release-workload.yml b/.github/workflows/release-workload.yml index 22b17dbf3..6972efa3b 100644 --- a/.github/workflows/release-workload.yml +++ b/.github/workflows/release-workload.yml @@ -12,6 +12,10 @@ on: type: boolean default: false +permissions: + contents: read + packages: read + jobs: release: runs-on: ubuntu-22.04 @@ -20,6 +24,29 @@ jobs: with: fetch-depth: 0 + - name: Authenticate GitHub Packages NuGet source + run: | + dotnet nuget update source github \ + --username github-actions \ + --password ${{ secrets.GITHUB_TOKEN }} \ + --store-password-in-clear-text \ + --configfile workload/NuGet.config + + - name: Bump TizenWorkloadVersion (global sequential, NuGet-derived) + working-directory: ./workload + run: | + set -e + OLD=$(grep -oP '(?<=)[^<]+(?=)' build/Versions.props) + NEW=$(python3 scripts/next-workload-version.py) + echo "Current: $OLD" + echo "Next: $NEW" + if [ "$OLD" = "$NEW" ]; then + echo "ERROR: computed next == current. NuGet already has this version published." + exit 1 + fi + python3 scripts/next-workload-version.py --apply --verbose + echo "::notice ::Bumped TizenWorkloadVersion: $OLD -> $NEW" + - name: Install Wix toolset run: sudo apt-get install -y wixl diff --git a/.github/workflows/validate-version-map.yml b/.github/workflows/validate-version-map.yml new file mode 100644 index 000000000..2373ed29d --- /dev/null +++ b/.github/workflows/validate-version-map.yml @@ -0,0 +1,48 @@ +name: Validate Version Map + +# Runs on every push/PR that touches the install scripts or the version map. +# Fails if workload-install.sh / workload-install.ps1 are out of sync with +# version-map.json (i.e. someone edited the JSON but forgot to regenerate, +# or edited the scripts by hand). +# +# Locally, developers fix drift by running: +# pwsh ./workload/scripts/Generate-InstallScripts.ps1 + +on: + push: + branches: [ main, net7.0, net8.0, net9.0, net10.0 ] + paths: + - 'workload/scripts/version-map.json' + - 'workload/scripts/workload-install.sh' + - 'workload/scripts/workload-install.ps1' + - 'workload/scripts/Generate-InstallScripts.ps1' + - '.github/workflows/validate-version-map.yml' + pull_request: + paths: + - 'workload/scripts/version-map.json' + - 'workload/scripts/workload-install.sh' + - 'workload/scripts/workload-install.ps1' + - 'workload/scripts/Generate-InstallScripts.ps1' + - '.github/workflows/validate-version-map.yml' + workflow_dispatch: + +jobs: + validate: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check drift between version-map.json and install scripts + shell: pwsh + run: | + ./workload/scripts/Generate-InstallScripts.ps1 -Check + + - name: Failure hint + if: failure() + shell: bash + run: | + echo "::error::workload/scripts/version-map.json is out of sync with the install scripts." + echo "::error::Fix it locally by running: pwsh ./workload/scripts/Generate-InstallScripts.ps1" + echo "::error::Then commit the regenerated workload-install.sh / workload-install.ps1." + exit 1 diff --git a/README.md b/README.md index aca9db3a0..214845daa 100644 --- a/README.md +++ b/README.md @@ -97,3 +97,23 @@ dotnet build HelloTizenNet6/HelloTizenNet6.csproj -f net6.0-tizen -t:Run ``` > ℹ️ You need to use Tizen emulator 7.0 or higher version to run .NET 6 based app. + + +## AI agent skills + +This repository ships [agentskills.io](https://agentskills.io)-compatible +diagnostic skills under [`.agents/skills/`](./.agents/skills/). They help AI +coding assistants (Claude Code, GitHub Copilot, Cursor, …) understand the +Tizen .NET environment and build pipeline. + +| Skill | When it triggers | +|---|---| +| [`tizen-doctor`](./.agents/skills/tizen-doctor/) | `dotnet build` failures on `net*-tizen*.0` TFMs (NETSDK1139, missing Tizen Workload, `Tizen.NET.Sdk` not found, etc.); fresh-machine setup; `sdb` / certificate tooling questions. | + +When working **inside this repo** with a compatible agent, these skills +auto-load on relevant prompts. + +When working **on your own Tizen .NET app** elsewhere, install via the +mirrored marketplace: + +```bash diff --git a/workload/Makefile b/workload/Makefile index 202e48f3a..6f9ece478 100644 --- a/workload/Makefile +++ b/workload/Makefile @@ -115,3 +115,27 @@ clean: @rm -fr $(OUTDIR) @rm -fr $(TMPDIR) @rm -fr $(TOP)/build/obj/ + +# Compute the next TizenWorkloadVersion by inspecting every Samsung.NET.Sdk.Tizen.Manifest +# package published to NuGet. Globally sequential — same answer regardless of branch. +# make next-workload-version # print only +# make bump-workload-version # update Versions.props in place +.PHONY: next-workload-version +next-workload-version: + @python3 $(TOP)/scripts/next-workload-version.py + +.PHONY: bump-workload-version +bump-workload-version: + @python3 $(TOP)/scripts/next-workload-version.py --apply --verbose + +# Compute the next TizenWorkloadVersion by inspecting every Samsung.NET.Sdk.Tizen.Manifest +# package published to NuGet. Globally sequential — same answer regardless of branch. +# make next-workload-version # print only +# make bump-workload-version # update Versions.props in place +.PHONY: next-workload-version +next-workload-version: + @python3 $(TOP)/scripts/next-workload-version.py + +.PHONY: bump-workload-version +bump-workload-version: + @python3 $(TOP)/scripts/next-workload-version.py --apply --verbose diff --git a/workload/NuGet.config b/workload/NuGet.config index 5976aac53..a621573f8 100644 --- a/workload/NuGet.config +++ b/workload/NuGet.config @@ -3,12 +3,19 @@ - + + + diff --git a/workload/scripts/Generate-InstallScripts.ps1 b/workload/scripts/Generate-InstallScripts.ps1 new file mode 100644 index 000000000..390d37c7a --- /dev/null +++ b/workload/scripts/Generate-InstallScripts.ps1 @@ -0,0 +1,208 @@ +<# +.SYNOPSIS + Regenerates the LatestVersionMap sections of workload-install.sh / workload-install.ps1 + from the single source of truth (version-map.json). + +.DESCRIPTION + The pair of install scripts used to duplicate a 36+ entry version map manually. + This script owns that duplication: edit version-map.json, run this script, commit. + CI uses `-Check` mode to fail the build if the scripts drift from the JSON. + +.PARAMETER Check + Do not modify files. Exits with code 1 if either script differs from what would + be generated, printing a unified diff. Used by validate-version-map.yml. + +.EXAMPLE + # Regenerate (developer workflow): + pwsh ./workload/scripts/Generate-InstallScripts.ps1 + + # CI drift detection: + pwsh ./workload/scripts/Generate-InstallScripts.ps1 -Check + +.NOTES + Compatible with PowerShell 7.2+ (pwsh) on Linux/macOS/Windows. +#> +[CmdletBinding()] +param( + [switch]$Check +) + +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $PSCommandPath +$MapPath = Join-Path $ScriptDir 'version-map.json' +$ShPath = Join-Path $ScriptDir 'workload-install.sh' +$Ps1Path = Join-Path $ScriptDir 'workload-install.ps1' + +$BeginMarker = '# BEGIN AUTO-GENERATED VERSION MAP -- edit version-map.json and rerun Generate-InstallScripts.ps1' +$EndMarker = '# END AUTO-GENERATED VERSION MAP' + +function Read-Map { + param([string]$Path) + if (-not (Test-Path $Path)) { + throw "version-map.json not found at $Path" + } + $raw = Get-Content -Raw -LiteralPath $Path + $obj = $raw | ConvertFrom-Json + if (-not $obj.manifestBaseName) { throw "manifestBaseName missing in $Path" } + if (-not $obj.entries) { throw "entries[] missing in $Path" } + + # Sanity: no duplicate sdkBand + $bands = $obj.entries | ForEach-Object { $_.sdkBand } + $dupes = $bands | Group-Object | Where-Object Count -gt 1 + if ($dupes) { + throw "Duplicate sdkBand entries: $($dupes.Name -join ', ')" + } + + return $obj +} + +function Get-PaddedColumn { + param([object[]]$Entries, [string]$Property) + $max = 0 + foreach ($e in $Entries) { + $len = $e.$Property.Length + if ($len -gt $max) { $max = $len } + } + return $max +} + +function Build-ShBlock { + # IMPORTANT: no column padding here. bash array entries are plain strings and + # the runtime lookup uses `${index%%=*}`, so any trailing whitespace between + # the key and `=` would end up inside the key and break matching. + param($Map) + $lines = New-Object System.Collections.Generic.List[string] + $lines.Add($BeginMarker) | Out-Null + $lines.Add('LatestVersionMap=(') | Out-Null + foreach ($e in $Map.entries) { + $lines.Add((' "$MANIFEST_BASE_NAME-{0}={1}"' -f $e.sdkBand, $e.workloadVersion)) | Out-Null + } + $lines.Add(' )') | Out-Null + $lines.Add($EndMarker) | Out-Null + return ($lines -join "`n") +} + +function Build-Ps1Block { + param($Map) + $lines = New-Object System.Collections.Generic.List[string] + $lines.Add($BeginMarker) | Out-Null + $lines.Add('$LatestVersionMap = [ordered]@{') | Out-Null + $padBand = Get-PaddedColumn -Entries $Map.entries -Property sdkBand + $max = $Map.entries.Count + for ($i = 0; $i -lt $max; $i++) { + $e = $Map.entries[$i] + $left = '"$ManifestBaseName-{0}"' -f $e.sdkBand + $pad = ' ' * ($padBand - $e.sdkBand.Length) + $sep = if ($i -lt $max - 1) { ';' } else { '' } + $lines.Add((' {0}{1} = "{2}"{3}' -f $left, $pad, $e.workloadVersion, $sep)) | Out-Null + } + $lines.Add('}') | Out-Null + $lines.Add($EndMarker) | Out-Null + return ($lines -join "`n") +} + +function Replace-Block { + param( + [string]$FilePath, + [string]$NewBlock, + [string]$LineEnding # "LF" or "CRLF" + ) + if (-not (Test-Path $FilePath)) { + throw "Target script not found: $FilePath" + } + $raw = Get-Content -Raw -LiteralPath $FilePath + + $escBegin = [regex]::Escape($BeginMarker) + $escEnd = [regex]::Escape($EndMarker) + # Match begin marker up to and including end marker, spanning lines. + $pattern = "(?s)$escBegin.*?$escEnd" + + if (-not ($raw -match $pattern)) { + throw @" +Markers not found in $FilePath. +Expected a block delimited by: + $BeginMarker + ... + $EndMarker +Please add these markers around the existing LatestVersionMap block, then rerun. +"@ + } + + $ending = if ($LineEnding -eq 'CRLF') { "`r`n" } else { "`n" } + $normalized = $NewBlock -replace "`r`n", "`n" -replace "`n", $ending + + # Preserve original file's trailing newline behavior. + $replaced = [regex]::Replace($raw, $pattern, [System.Text.RegularExpressions.MatchEvaluator]{ param($m) $normalized }, 1) + return $replaced +} + +function Detect-LineEnding { + param([string]$Path) + $bytes = [System.IO.File]::ReadAllBytes($Path) + for ($i = 0; $i -lt [Math]::Min($bytes.Length, 8192); $i++) { + if ($bytes[$i] -eq 0x0D) { return 'CRLF' } + if ($bytes[$i] -eq 0x0A) { return 'LF' } + } + return 'LF' +} + +function Write-Or-Check { + param( + [string]$Path, + [string]$NewContent, + [switch]$CheckOnly + ) + $current = Get-Content -Raw -LiteralPath $Path + if ($current -eq $NewContent) { + Write-Host " OK: $Path is up-to-date." -ForegroundColor Green + return $true + } + if ($CheckOnly) { + Write-Host " DRIFT: $Path is out of sync with version-map.json" -ForegroundColor Red + # Print a minimal diff hint: show only the changed region lines + $curLines = $current -split "`r?`n" + $newLines = $NewContent -split "`r?`n" + $curStart = ($curLines | Select-String -Pattern ([regex]::Escape($BeginMarker)) | Select-Object -First 1).LineNumber + $newStart = ($newLines | Select-String -Pattern ([regex]::Escape($BeginMarker)) | Select-Object -First 1).LineNumber + if ($curStart -and $newStart) { + Write-Host " Expected block around line $newStart (generated) vs actual around line $curStart." + } + return $false + } + # Preserve the file's line ending when writing + $ending = Detect-LineEnding -Path $Path + $nl = if ($ending -eq 'CRLF') { "`r`n" } else { "`n" } + $toWrite = $NewContent -replace "`r`n", "`n" -replace "`n", $nl + [System.IO.File]::WriteAllText($Path, $toWrite, [System.Text.UTF8Encoding]::new($false)) + Write-Host " UPDATED: $Path" -ForegroundColor Yellow + return $true +} + +# ----------------------------------------------------------------------------- + +Write-Host "Loading version-map from: $MapPath" +$map = Read-Map -Path $MapPath +Write-Host ("Loaded {0} entries." -f $map.entries.Count) + +$shEnding = Detect-LineEnding -Path $ShPath +$ps1Ending = Detect-LineEnding -Path $Ps1Path + +$shBlock = Build-ShBlock -Map $map +$ps1Block = Build-Ps1Block -Map $map + +$shNew = Replace-Block -FilePath $ShPath -NewBlock $shBlock -LineEnding $shEnding +$ps1New = Replace-Block -FilePath $Ps1Path -NewBlock $ps1Block -LineEnding $ps1Ending + +$okSh = Write-Or-Check -Path $ShPath -NewContent $shNew -CheckOnly:$Check +$okPs1 = Write-Or-Check -Path $Ps1Path -NewContent $ps1New -CheckOnly:$Check + +if ($Check -and (-not $okSh -or -not $okPs1)) { + Write-Host "" + Write-Host "version-map.json and the install scripts are out of sync." -ForegroundColor Red + Write-Host "Run locally to fix:" + Write-Host " pwsh ./workload/scripts/Generate-InstallScripts.ps1" + exit 1 +} + +exit 0 diff --git a/workload/scripts/README.md b/workload/scripts/README.md index 86591351a..207f4c852 100644 --- a/workload/scripts/README.md +++ b/workload/scripts/README.md @@ -27,3 +27,33 @@ or ``` curl -sSL https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | bash -s -- -v -d ``` + +## Editing the version map + +The `LatestVersionMap` table used inside `workload-install.sh` and `workload-install.ps1` +is **generated** from a single source of truth: [`version-map.json`](./version-map.json). + +To add or update an entry: + +1. Edit `workload/scripts/version-map.json`. Only add the new `(sdkBand, workloadVersion)` pair. +2. Regenerate the install scripts: + ``` + pwsh ./workload/scripts/Generate-InstallScripts.ps1 + ``` +3. Commit all three files together (`version-map.json`, `workload-install.sh`, `workload-install.ps1`). + +**Do not hand-edit the blocks delimited by** +``` +# BEGIN AUTO-GENERATED VERSION MAP +... +# END AUTO-GENERATED VERSION MAP +``` +The CI workflow `validate-version-map.yml` runs `Generate-InstallScripts.ps1 -Check` +on every PR and fails if the two scripts have drifted from `version-map.json`. + +### Why + +Previously, the same ~36 entries were maintained by hand in two different languages +(bash array and PowerShell ordered hashtable). This was a common source of mistakes — +see e.g. commit `f43fb9d Revert updating version map for 7.0.400`, and divergences +between `.sh` and `.ps1` for the same SDK band. diff --git a/workload/scripts/next-workload-version.py b/workload/scripts/next-workload-version.py new file mode 100644 index 000000000..bfc3f8a1f --- /dev/null +++ b/workload/scripts/next-workload-version.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# +# Copyright (c) Samsung Electronics. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# +""" +Compute the next TizenWorkloadVersion by looking at every Samsung.NET.Sdk.Tizen.Manifest +package published on NuGet, finding the largest globally-sequential build counter, +and returning it incremented by 1. + +Rationale +========= +TizenWorkloadVersion uses the format `..` where +`buildSeq` increments by 1 per release REGARDLESS of which .NET SDK band the release +targets. Every active branch (main, net7.0, net8.0, net9.0, net10.0) draws from the +same `buildSeq` pool. NuGet is the source of truth: the next number must be 1 more +than the largest `buildSeq` ever published, irrespective of sdkBand or branch. + +Algorithm +========= +1. Query NuGet's search index for all packages whose id starts with + `samsung.net.sdk.tizen.manifest-`. This returns one entry per sdkBand (one + package per band, e.g. ...-7.0.400, ...-8.0.100-rtm, etc.). +2. For each package id, fetch the full version list from the flatcontainer endpoint. +3. Parse each version into (major, minor, buildSeq), ignoring pre-release suffixes + that follow `-`. Build counters are integers regardless of pre-release tag. +4. Determine the global maximum `(major, minor, buildSeq)` triple. +5. Print `..` on stdout (single line, no extras). + +Usage +===== + # Print only: + python3 workload/scripts/next-workload-version.py + + # Update Versions.props in place: + python3 workload/scripts/next-workload-version.py --apply + + # Show diagnostics on stderr: + python3 workload/scripts/next-workload-version.py --verbose + +Notes +===== +- Reads no local files; relies only on what is published to nuget.org. This is + intentional: published artifacts are the authoritative shared sequence; uncommitted + Versions.props bumps cannot influence the answer. +- If the network is unreachable, exit code 2. +- `--apply` writes Versions.props only when the new value is strictly greater than + what is already there; otherwise it warns and leaves the file alone. +""" +from __future__ import annotations +import argparse +import json +import re +import sys +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path + +SEARCH_URL = ( + "https://azuresearch-usnc.nuget.org/query" + "?q=packageid:samsung.net.sdk.tizen.manifest" + "&prerelease=true&semVerLevel=2.0.0&take=200" +) +SEARCH_FALLBACK_URL = ( + "https://azuresearch-usnc.nuget.org/query" + "?q=samsung.net.sdk.tizen.manifest" + "&prerelease=true&semVerLevel=2.0.0&take=200" +) +FLATCONTAINER_FMT = "https://api.nuget.org/v3-flatcontainer/{id}/index.json" + +VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$") + + +def fetch_json(url: str, timeout: int = 15) -> dict: + try: + with urllib.request.urlopen(url, timeout=timeout) as r: + return json.loads(r.read().decode("utf-8")) + except (urllib.error.URLError, urllib.error.HTTPError) as e: + raise SystemExit(f"network error fetching {url}: {e}") + + +def find_manifest_package_ids(verbose: bool = False) -> list[str]: + """Return lowercase ids of every published samsung.net.sdk.tizen.manifest-* package.""" + out: set[str] = set() + for url in (SEARCH_URL, SEARCH_FALLBACK_URL): + try: + data = fetch_json(url) + except SystemExit: + continue + for entry in data.get("data", []): + pid = entry.get("id", "").lower() + # Accept both exact 'samsung.net.sdk.tizen.manifest' (no suffix, unusual) + # and the typical '-' suffix forms. + if pid.startswith("samsung.net.sdk.tizen.manifest"): + out.add(pid) + if out: + break + if verbose: + print(f"[verbose] discovered {len(out)} manifest package ids", file=sys.stderr) + return sorted(out) + + +def fetch_versions(pid: str, verbose: bool = False) -> list[str]: + url = FLATCONTAINER_FMT.format(id=pid) + try: + data = fetch_json(url) + except SystemExit: + if verbose: + print(f"[verbose] failed to fetch versions for {pid}", file=sys.stderr) + return [] + return data.get("versions", []) + + +def parse_version(v: str) -> tuple[int, int, int] | None: + m = VERSION_RE.match(v) + return (int(m.group(1)), int(m.group(2)), int(m.group(3))) if m else None + + +def find_max_triple(verbose: bool = False) -> tuple[int, int, int]: + pkgs = find_manifest_package_ids(verbose=verbose) + if not pkgs: + raise SystemExit("no Samsung.NET.Sdk.Tizen.Manifest packages found on NuGet") + + best: tuple[int, int, int] | None = None + best_pkg = best_ver = "" + for pid in pkgs: + for v in fetch_versions(pid, verbose=verbose): + t = parse_version(v) + if t is None: + continue + if best is None or t > best: + best, best_pkg, best_ver = t, pid, v + + if best is None: + raise SystemExit("no parseable versions found across all manifest packages") + + if verbose: + print(f"[verbose] max = {best} (from {best_pkg} version={best_ver})", file=sys.stderr) + return best + + +def update_versions_props(versions_props: Path, new_value: str, verbose: bool = False) -> int: + text = versions_props.read_text(encoding="utf-8") + m = re.search(r"([^<]+)", text) + if not m: + raise SystemExit(f" not found in {versions_props}") + current = m.group(1).strip() + + def to_tuple(s: str) -> tuple[int, int, int] | None: + return parse_version(s) + + cur_t, new_t = to_tuple(current), to_tuple(new_value) + if cur_t is None: + raise SystemExit(f"unparseable current version: {current!r}") + if new_t is None: + raise SystemExit(f"unparseable new version: {new_value!r}") + + if new_t <= cur_t: + print( + f"refusing to write: current TizenWorkloadVersion ({current}) is >= candidate ({new_value}). " + f"Versions.props left unchanged.", + file=sys.stderr, + ) + return 1 + + new_text = re.sub( + r"[^<]+", + f"{new_value}", + text, + count=1, + ) + versions_props.write_text(new_text, encoding="utf-8") + if verbose: + print(f"[verbose] {versions_props}: {current} -> {new_value}", file=sys.stderr) + return 0 + + +def main() -> int: + p = argparse.ArgumentParser(description=__doc__.splitlines()[1] if __doc__ else "") + p.add_argument("--apply", action="store_true", + help="Update workload/build/Versions.props in place (only if strictly greater).") + p.add_argument("--versions-props", default=None, + help="Path to Versions.props (default: workload/build/Versions.props relative to this script).") + p.add_argument("--verbose", action="store_true", help="Print diagnostics on stderr.") + args = p.parse_args() + + major, minor, build = find_max_triple(verbose=args.verbose) + next_value = f"{major}.{minor}.{build + 1}" + + if args.apply: + if args.versions_props: + vp = Path(args.versions_props) + else: + # script is in workload/scripts/, so Versions.props is sibling-of-parent/build/ + vp = Path(__file__).resolve().parents[1] / "build" / "Versions.props" + rc = update_versions_props(vp, next_value, verbose=args.verbose) + # Always echo the computed value so CI/scripts can capture it. + print(next_value) + return rc + + print(next_value) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/workload/scripts/version-map.json b/workload/scripts/version-map.json new file mode 100644 index 000000000..5f0ca9a69 --- /dev/null +++ b/workload/scripts/version-map.json @@ -0,0 +1,144 @@ +{ + "_comment": [ + "Single source of truth for the Tizen workload version map.", + "After editing this file, regenerate the install scripts by running:", + " pwsh ./workload/scripts/Generate-InstallScripts.ps1", + "CI will fail if the generated sections in workload-install.sh / workload-install.ps1", + "are out of sync with this file." + ], + "manifestBaseName": "samsung.net.sdk.tizen.manifest", + "entries": [ + { + "sdkBand": "6.0.100", + "workloadVersion": "7.0.101" + }, + { + "sdkBand": "6.0.200", + "workloadVersion": "7.0.100-preview.13.6" + }, + { + "sdkBand": "6.0.300", + "workloadVersion": "8.0.133" + }, + { + "sdkBand": "6.0.400", + "workloadVersion": "9.0.104" + }, + { + "sdkBand": "7.0.100-preview.6", + "workloadVersion": "7.0.100-preview.6.14" + }, + { + "sdkBand": "7.0.100-preview.7", + "workloadVersion": "7.0.100-preview.7.20" + }, + { + "sdkBand": "7.0.100-rc.1", + "workloadVersion": "7.0.100-rc.1.22" + }, + { + "sdkBand": "7.0.100-rc.2", + "workloadVersion": "7.0.100-rc.2.24" + }, + { + "sdkBand": "7.0.100", + "workloadVersion": "7.0.103" + }, + { + "sdkBand": "7.0.200", + "workloadVersion": "7.0.105" + }, + { + "sdkBand": "7.0.300", + "workloadVersion": "7.0.120" + }, + { + "sdkBand": "7.0.400", + "workloadVersion": "10.0.119" + }, + { + "sdkBand": "8.0.100-alpha.1", + "workloadVersion": "7.0.104" + }, + { + "sdkBand": "8.0.100-preview.2", + "workloadVersion": "7.0.106" + }, + { + "sdkBand": "8.0.100-preview.3", + "workloadVersion": "7.0.107" + }, + { + "sdkBand": "8.0.100-preview.4", + "workloadVersion": "7.0.108" + }, + { + "sdkBand": "8.0.100-preview.5", + "workloadVersion": "7.0.110" + }, + { + "sdkBand": "8.0.100-preview.6", + "workloadVersion": "7.0.121" + }, + { + "sdkBand": "8.0.100-preview.7", + "workloadVersion": "7.0.122" + }, + { + "sdkBand": "8.0.100-rc.1", + "workloadVersion": "7.0.124" + }, + { + "sdkBand": "8.0.100-rc.2", + "workloadVersion": "7.0.125" + }, + { + "sdkBand": "8.0.100-rtm", + "workloadVersion": "7.0.127" + }, + { + "sdkBand": "8.0.100", + "workloadVersion": "8.0.144" + }, + { + "sdkBand": "8.0.200", + "workloadVersion": "8.0.157" + }, + { + "sdkBand": "8.0.300", + "workloadVersion": "8.0.156" + }, + { + "sdkBand": "8.0.400", + "workloadVersion": "10.0.120" + }, + { + "sdkBand": "9.0.100-alpha.1", + "workloadVersion": "8.0.134" + }, + { + "sdkBand": "9.0.100-preview.1", + "workloadVersion": "8.0.135" + }, + { + "sdkBand": "9.0.100-preview.2", + "workloadVersion": "8.0.137" + }, + { + "sdkBand": "9.0.100", + "workloadVersion": "10.0.104" + }, + { + "sdkBand": "9.0.200", + "workloadVersion": "10.0.110" + }, + { + "sdkBand": "9.0.300", + "workloadVersion": "10.0.121" + }, + { + "sdkBand": "10.0.100", + "workloadVersion": "10.0.123" + } + ] +} diff --git a/workload/scripts/workload-install.ps1 b/workload/scripts/workload-install.ps1 index 271841b60..e977f8b82 100644 --- a/workload/scripts/workload-install.ps1 +++ b/workload/scripts/workload-install.ps1 @@ -29,40 +29,43 @@ $ProgressPreference = "SilentlyContinue" $ManifestBaseName = "Samsung.NET.Sdk.Tizen.Manifest" $global:FallbackId = "" +# BEGIN AUTO-GENERATED VERSION MAP -- edit version-map.json and rerun Generate-InstallScripts.ps1 $LatestVersionMap = [ordered]@{ - "$ManifestBaseName-6.0.100" = "7.0.101"; - "$ManifestBaseName-6.0.200" = "7.0.100-preview.13.6"; - "$ManifestBaseName-6.0.300" = "8.0.133"; - "$ManifestBaseName-6.0.400" = "9.0.104"; + "$ManifestBaseName-6.0.100" = "7.0.101"; + "$ManifestBaseName-6.0.200" = "7.0.100-preview.13.6"; + "$ManifestBaseName-6.0.300" = "8.0.133"; + "$ManifestBaseName-6.0.400" = "9.0.104"; "$ManifestBaseName-7.0.100-preview.6" = "7.0.100-preview.6.14"; "$ManifestBaseName-7.0.100-preview.7" = "7.0.100-preview.7.20"; - "$ManifestBaseName-7.0.100-rc.1" = "7.0.100-rc.1.22"; - "$ManifestBaseName-7.0.100-rc.2" = "7.0.100-rc.2.24"; - "$ManifestBaseName-7.0.100" = "7.0.103"; - "$ManifestBaseName-7.0.200" = "7.0.105"; - "$ManifestBaseName-7.0.300" = "7.0.120"; - "$ManifestBaseName-7.0.400" = "10.0.106"; - "$ManifestBaseName-8.0.100-alpha.1" = "7.0.104"; + "$ManifestBaseName-7.0.100-rc.1" = "7.0.100-rc.1.22"; + "$ManifestBaseName-7.0.100-rc.2" = "7.0.100-rc.2.24"; + "$ManifestBaseName-7.0.100" = "7.0.103"; + "$ManifestBaseName-7.0.200" = "7.0.105"; + "$ManifestBaseName-7.0.300" = "7.0.120"; + "$ManifestBaseName-7.0.400" = "10.0.119"; + "$ManifestBaseName-8.0.100-alpha.1" = "7.0.104"; "$ManifestBaseName-8.0.100-preview.2" = "7.0.106"; "$ManifestBaseName-8.0.100-preview.3" = "7.0.107"; "$ManifestBaseName-8.0.100-preview.4" = "7.0.108"; "$ManifestBaseName-8.0.100-preview.5" = "7.0.110"; "$ManifestBaseName-8.0.100-preview.6" = "7.0.121"; "$ManifestBaseName-8.0.100-preview.7" = "7.0.122"; - "$ManifestBaseName-8.0.100-rc.1" = "7.0.124"; - "$ManifestBaseName-8.0.100-rc.2" = "7.0.125"; - "$ManifestBaseName-8.0.100-rtm" = "7.0.127"; - "$ManifestBaseName-8.0.100" = "8.0.144"; - "$ManifestBaseName-8.0.200" = "8.0.157"; - "$ManifestBaseName-8.0.300" = "8.0.156"; - "$ManifestBaseName-8.0.400" = "10.0.109"; - "$ManifestBaseName-9.0.100-alpha.1" = "8.0.134"; + "$ManifestBaseName-8.0.100-rc.1" = "7.0.124"; + "$ManifestBaseName-8.0.100-rc.2" = "7.0.125"; + "$ManifestBaseName-8.0.100-rtm" = "7.0.127"; + "$ManifestBaseName-8.0.100" = "8.0.144"; + "$ManifestBaseName-8.0.200" = "8.0.157"; + "$ManifestBaseName-8.0.300" = "8.0.156"; + "$ManifestBaseName-8.0.400" = "10.0.120"; + "$ManifestBaseName-9.0.100-alpha.1" = "8.0.134"; "$ManifestBaseName-9.0.100-preview.1" = "8.0.135"; "$ManifestBaseName-9.0.100-preview.2" = "8.0.137"; - "$ManifestBaseName-9.0.100" = "10.0.104"; - "$ManifestBaseName-9.0.200" = "10.0.110"; - "$ManifestBaseName-9.0.300" = "10.0.111"; + "$ManifestBaseName-9.0.100" = "10.0.104"; + "$ManifestBaseName-9.0.200" = "10.0.110"; + "$ManifestBaseName-9.0.300" = "10.0.121"; + "$ManifestBaseName-10.0.100" = "10.0.123" } +# END AUTO-GENERATED VERSION MAP function New-TemporaryDirectory { $parent = [System.IO.Path]::GetTempPath() @@ -297,7 +300,7 @@ if (Get-Command $DotnetCommand -ErrorAction SilentlyContinue) { if ($UpdateAllWorkloads.IsPresent) { - $InstalledDotnetSdks = Invoke-Expression "& '$DotnetCommand' --list-sdks | Select-String -Pattern '^6|^7'" | ForEach-Object {$_ -replace (" \[.*","")} + $InstalledDotnetSdks = Invoke-Expression "& '$DotnetCommand' --list-sdks | Select-String -Pattern '^6|^7|^8|^9|^10'" | ForEach-Object {$_ -replace (" \[.*","")} } else { diff --git a/workload/scripts/workload-install.sh b/workload/scripts/workload-install.sh index cac45880d..e18c9beb3 100755 --- a/workload/scripts/workload-install.sh +++ b/workload/scripts/workload-install.sh @@ -13,6 +13,7 @@ DOTNET_DEFAULT_PATH_LINUX="/usr/share/dotnet" DOTNET_DEFAULT_PATH_MACOS="/usr/local/share/dotnet" UPDATE_ALL_WORKLOADS="false" +# BEGIN AUTO-GENERATED VERSION MAP -- edit version-map.json and rerun Generate-InstallScripts.ps1 LatestVersionMap=( "$MANIFEST_BASE_NAME-6.0.100=7.0.101" "$MANIFEST_BASE_NAME-6.0.200=7.0.100-preview.13.6" @@ -25,7 +26,7 @@ LatestVersionMap=( "$MANIFEST_BASE_NAME-7.0.100=7.0.103" "$MANIFEST_BASE_NAME-7.0.200=7.0.105" "$MANIFEST_BASE_NAME-7.0.300=7.0.120" - "$MANIFEST_BASE_NAME-7.0.400=10.0.106" + "$MANIFEST_BASE_NAME-7.0.400=10.0.119" "$MANIFEST_BASE_NAME-8.0.100-alpha.1=7.0.104" "$MANIFEST_BASE_NAME-8.0.100-preview.2=7.0.106" "$MANIFEST_BASE_NAME-8.0.100-preview.3=7.0.107" @@ -39,14 +40,16 @@ LatestVersionMap=( "$MANIFEST_BASE_NAME-8.0.100=8.0.144" "$MANIFEST_BASE_NAME-8.0.200=8.0.157" "$MANIFEST_BASE_NAME-8.0.300=8.0.156" - "$MANIFEST_BASE_NAME-8.0.400=10.0.109" + "$MANIFEST_BASE_NAME-8.0.400=10.0.120" "$MANIFEST_BASE_NAME-9.0.100-alpha.1=8.0.134" "$MANIFEST_BASE_NAME-9.0.100-preview.1=8.0.135" "$MANIFEST_BASE_NAME-9.0.100-preview.2=8.0.137" "$MANIFEST_BASE_NAME-9.0.100=10.0.104" "$MANIFEST_BASE_NAME-9.0.200=10.0.110" - "$MANIFEST_BASE_NAME-9.0.300=10.0.111" + "$MANIFEST_BASE_NAME-9.0.300=10.0.121" + "$MANIFEST_BASE_NAME-10.0.100=10.0.123" ) +# END AUTO-GENERATED VERSION MAP while [ $# -ne 0 ]; do name=$1 @@ -251,7 +254,7 @@ function install_tizenworkload() { } if [[ "$UPDATE_ALL_WORKLOADS" == "true" ]]; then - INSTALLED_DOTNET_SDKS=$($DOTNET_COMMAND --list-sdks | sed -n '/^6\|^7/p' | sed 's/ \[.*//g') + INSTALLED_DOTNET_SDKS=$($DOTNET_COMMAND --list-sdks | sed -n '/^6\|^7\|^8\|^9\|^10/p' | sed 's/ \[.*//g') else INSTALLED_DOTNET_SDKS=$($DOTNET_COMMAND --version) fi diff --git a/workload/scripts/workload-uninstall.ps1 b/workload/scripts/workload-uninstall.ps1 new file mode 100644 index 000000000..e0933c9a9 --- /dev/null +++ b/workload/scripts/workload-uninstall.ps1 @@ -0,0 +1,223 @@ +# +# Copyright (c) Samsung Electronics. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +<# +.SYNOPSIS +Uninstalls Tizen workload. +.DESCRIPTION +Removes the Tizen WorkloadManifest, workload packs, the file-based workload +marker, and (on MSI-installed .NET SDKs) the HKLM registry entry that +workload-install.ps1 creates so that 'dotnet workload list' shows 'tizen'. + +Use this script before running 'dotnet workload install/restore/update' for +other workloads to avoid the MSI lookup failure that causes those operations +to roll back. After you are done, reinstall the Tizen workload with +workload-install.ps1. + +Must be run with administrator privileges when the .NET SDK was installed +via the Windows MSI (the default location under %ProgramFiles%\dotnet). +.PARAMETER DotnetInstallDir +Dotnet SDK location (default: $env:DOTNET_ROOT, or %ProgramFiles%\dotnet). +.PARAMETER DotnetTargetVersionBand +Explicit SDK version band to uninstall from (e.g. '8.0.400'). If omitted, +the script iterates over every installed .NET 6+ SDK and removes the Tizen +workload from each matching band. +#> + +[cmdletbinding()] +param( + [Alias('d')][string]$DotnetInstallDir="", + [Alias('t')][string]$DotnetTargetVersionBand="" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +$ManifestBaseName = "Samsung.NET.Sdk.Tizen.Manifest" + +function Ensure-Writable([string]$TestDir) { + if (-Not (Test-Path $TestDir)) { return } + Try { + [io.file]::OpenWrite($(Join-Path -Path $TestDir -ChildPath ".test-write-access")).Close() + Remove-Item -Path $(Join-Path -Path $TestDir -ChildPath ".test-write-access") -Force + } + Catch [System.UnauthorizedAccessException] { + Write-Error "No permission to uninstall from '$TestDir'. Try run with administrator mode." + } +} + +function Remove-Pack([string]$Id, [string]$Version, [string]$Kind) { + switch ($Kind) { + "manifest" { + if (Test-Path $TizenManifestDir) { + Write-Host "Removing manifest $Id/$Version..." + Remove-Item -Path $TizenManifestDir -Recurse -Force + } + } + {($_ -eq "sdk") -or ($_ -eq "framework")} { + if ( ($Kind -eq "sdk") -and ($Id -match ".net[0-9]+$")) { + $Id = $Id -replace (".net[0-9]+", "") + } + $TargetDirectory = $(Join-Path -Path $DotnetInstallDir -ChildPath "packs\$Id\$Version") + if (Test-Path $TargetDirectory) { + Write-Host "Removing pack $Id/$Version..." + Remove-Item -Path $TargetDirectory -Recurse -Force + # Remove the pack's parent folder if it becomes empty. + $ParentDir = Split-Path -Parent $TargetDirectory + if ((Test-Path $ParentDir) -and ((Get-ChildItem -Path $ParentDir -Force | Measure-Object).Count -eq 0)) { + Remove-Item -Path $ParentDir -Force + } + } + } + "template" { + $TargetFileName = "$Id.$Version.nupkg".ToLower() + $TargetFile = $(Join-Path -Path $DotnetInstallDir -ChildPath "template-packs\$TargetFileName") + if (Test-Path $TargetFile) { + Write-Host "Removing template $Id/$Version..." + Remove-Item -Path $TargetFile -Force + } + } + } +} + +function Uninstall-TizenWorkload([string]$DotnetVersion) +{ + $VersionSplitSymbol = '.' + $SplitVersion = $DotnetVersion.Split($VersionSplitSymbol) + + $CurrentDotnetVersion = [Version]"$($SplitVersion[0]).$($SplitVersion[1])" + $DotnetVersionBand = $SplitVersion[0] + $VersionSplitSymbol + $SplitVersion[1] + $VersionSplitSymbol + $SplitVersion[2][0] + "00" + $ManifestName = "$ManifestBaseName-$DotnetVersionBand" + + # Resolve target band. A locally-scoped copy of the script-level parameter. + $TargetBand = $DotnetTargetVersionBand + if ($TargetBand -eq "") { + if ($CurrentDotnetVersion -ge "7.0") + { + $IsPreviewVersion = $DotnetVersion.Contains("-preview") -or $DotnetVersion.Contains("-rc") -or $DotnetVersion.Contains("-alpha") + if ($IsPreviewVersion -and ($SplitVersion.Count -ge 4)) { + $TargetBand = $DotnetVersionBand + $SplitVersion[2].SubString(3) + $VersionSplitSymbol + $($SplitVersion[3]) + $ManifestName = "$ManifestBaseName-$TargetBand" + } + elseif ($DotnetVersion.Contains("-rtm") -and ($SplitVersion.Count -ge 3)) { + $TargetBand = $DotnetVersionBand + $SplitVersion[2].SubString(3) + $ManifestName = "$ManifestBaseName-$TargetBand" + } + else { + $TargetBand = $DotnetVersionBand + } + } + else { + $TargetBand = $DotnetVersionBand + } + } + + # Locate Tizen workload manifest. + $ManifestDir = Join-Path -Path $DotnetInstallDir -ChildPath "sdk-manifests" | Join-Path -ChildPath $TargetBand + $TizenManifestDir = Join-Path -Path $ManifestDir -ChildPath "samsung.net.sdk.tizen" + $TizenManifestFile = Join-Path -Path $TizenManifestDir -ChildPath "WorkloadManifest.json" + + $Found = $false + + # Remove packs declared by the manifest, then the manifest itself. + if (Test-Path $TizenManifestFile) { + $Found = $true + Ensure-Writable $ManifestDir + Try { + $ManifestJson = $(Get-Content $TizenManifestFile | ConvertFrom-Json) + $InstalledVersion = $ManifestJson.version + $ManifestJson.packs.PSObject.Properties | ForEach-Object { + Remove-Pack -Id $_.Name -Version $_.Value.version -Kind $_.Value.kind + } + Remove-Pack -Id $ManifestName -Version $InstalledVersion -Kind "manifest" + } + Catch { + Write-Host "Could not parse manifest at $TizenManifestFile, falling back to directory removal." + Write-Host "$_" + Remove-Item -Path $TizenManifestDir -Recurse -Force + } + } + elseif (Test-Path $TizenManifestDir) { + # Manifest folder exists but WorkloadManifest.json is missing (partial install). + $Found = $true + Write-Host "Manifest file missing; removing partial manifest directory $TizenManifestDir..." + Remove-Item -Path $TizenManifestDir -Recurse -Force + } + + # Remove the file-based workload marker (independent of manifest band, + # this marker is always under the non-preview band path). + $FileBasedMarker = Join-Path -Path $DotnetInstallDir -ChildPath "metadata\workloads\$DotnetVersionBand\InstalledWorkloads\tizen" + if (Test-Path $FileBasedMarker) { + $Found = $true + Write-Host "Removing file-based workload marker..." + Remove-Item -Path $FileBasedMarker -Force + } + + # Remove HKLM registry entry written by workload-install.ps1 on MSI-installed SDKs. + $RegPath = "HKLM:\SOFTWARE\Microsoft\dotnet\InstalledWorkloads\Standalone\x64\$TargetBand\tizen" + if (Test-Path $RegPath) { + $Found = $true + Write-Host "Removing registry entry $RegPath..." + Try { + Remove-Item -Path $RegPath -Force + } + Catch [System.UnauthorizedAccessException] { + Write-Error "No permission to remove registry entry. Try run with administrator mode." + } + } + + if (-Not $Found) { + Write-Host "Tizen workload not found for band $TargetBand. Nothing to remove." + } + else { + Write-Host "Done uninstalling Tizen workload for band $TargetBand" + } +} + +# Resolve dotnet install directory. +if ($DotnetInstallDir -eq "") { + if ($Env:DOTNET_ROOT -And $(Test-Path "$Env:DOTNET_ROOT")) { + $DotnetInstallDir = $Env:DOTNET_ROOT + } else { + $DotnetInstallDir = Join-Path -Path $Env:Programfiles -ChildPath "dotnet" + } +} +if (-Not $(Test-Path "$DotnetInstallDir")) { + Write-Error "No installed dotnet '$DotnetInstallDir'." +} + +# Enumerate installed SDKs and uninstall Tizen workload from each. +$DotnetCommand = "$DotnetInstallDir\dotnet" +if (Get-Command $DotnetCommand -ErrorAction SilentlyContinue) +{ + $InstalledDotnetSdks = Invoke-Expression "& '$DotnetCommand' --list-sdks | Select-String -Pattern '^6|^7|^8|^9|^10'" | ForEach-Object {$_ -replace (" \[.*","")} +} +else +{ + Write-Error "'$DotnetCommand' occurs an error." +} + +if (-Not $InstalledDotnetSdks) +{ + Write-Host "`nNo .NET SDK (6+) found. Nothing to uninstall." +} +else +{ + foreach ($DotnetSdk in $InstalledDotnetSdks) + { + try { + Write-Host "`nCheck Tizen Workload for sdk $DotnetSdk" + Uninstall-TizenWorkload -DotnetVersion $DotnetSdk + } + catch { + Write-Host "Failed to uninstall Tizen Workload for sdk $DotnetSdk" + Write-Host "$_" + Continue + } + } +} + +Write-Host "`nDone" diff --git a/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.NuGet.targets b/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.NuGet.targets index c39c67e67..61e308fa6 100644 --- a/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.NuGet.targets +++ b/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.NuGet.targets @@ -19,7 +19,6 @@ Copyright (c) Samsung All rights reserved. net6.0-tizen9.0; net6.0-tizen8.0; - net6.0-tizen7.0; net6.0-tizen6.5; tizen10.0; tizen90; diff --git a/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.Versions.targets b/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.Versions.targets index 2ebe7ddf7..c0f94cc49 100644 --- a/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.Versions.targets +++ b/workload/src/Samsung.Tizen.Sdk/targets/Samsung.Tizen.Sdk.Versions.targets @@ -45,7 +45,6 @@ Copyright (c) Samsung All rights reserved. -