From c324cce8a3363a0850d16541f39710c880688eaa Mon Sep 17 00:00:00 2001 From: "sung-su.kim" Date: Fri, 12 Dec 2025 18:49:23 +0900 Subject: [PATCH 01/11] Remove myget from action (#298) --- .github/workflows/build-workload.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/build-workload.yml b/.github/workflows/build-workload.yml index c9694a69c..c72c680cd 100644 --- a/.github/workflows/build-workload.yml +++ b/.github/workflows/build-workload.yml @@ -70,9 +70,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 From a3ebdd333693d1110dc896e32fee5a58b19f0d02 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 30 Mar 2026 17:22:23 +0200 Subject: [PATCH 02/11] Add support for .NET SDKs 8, 9 and 10 in script (#302) * Add support for .NET SDKs 8 and 10 in script * Add .NET 8, 9, and 10 support to workload-install.sh Agent-Logs-Url: https://github.com/mattleibow/Tizen.NET/sessions/601b2219-5ec3-487e-92cd-e5cf0ce1e812 Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> --- workload/scripts/workload-install.ps1 | 3 ++- workload/scripts/workload-install.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/workload/scripts/workload-install.ps1 b/workload/scripts/workload-install.ps1 index 271841b60..54cf66636 100644 --- a/workload/scripts/workload-install.ps1 +++ b/workload/scripts/workload-install.ps1 @@ -62,6 +62,7 @@ $LatestVersionMap = [ordered]@{ "$ManifestBaseName-9.0.100" = "10.0.104"; "$ManifestBaseName-9.0.200" = "10.0.110"; "$ManifestBaseName-9.0.300" = "10.0.111"; + "$ManifestBaseName-10.0.100" = "10.0.123"; } function New-TemporaryDirectory { @@ -297,7 +298,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..37877ac10 100755 --- a/workload/scripts/workload-install.sh +++ b/workload/scripts/workload-install.sh @@ -46,6 +46,7 @@ LatestVersionMap=( "$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-10.0.100=10.0.123" ) while [ $# -ne 0 ]; do @@ -251,7 +252,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 From 533064eff2c43d7a0862c725cc2dbdbde07dd01d Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Fri, 24 Apr 2026 13:58:02 +0900 Subject: [PATCH 03/11] Add Uninstall Script --- workload/NuGet.config | 1 - workload/scripts/README.md | 36 ++++ workload/scripts/workload-uninstall.ps1 | 223 ++++++++++++++++++++++++ 3 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 workload/scripts/workload-uninstall.ps1 diff --git a/workload/NuGet.config b/workload/NuGet.config index 5976aac53..0700f15d7 100644 --- a/workload/NuGet.config +++ b/workload/NuGet.config @@ -3,7 +3,6 @@ - diff --git a/workload/scripts/README.md b/workload/scripts/README.md index 86591351a..42bf318a4 100644 --- a/workload/scripts/README.md +++ b/workload/scripts/README.md @@ -27,3 +27,39 @@ or ``` curl -sSL https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | bash -s -- -v -d ``` + +## workload-uninstall (Windows only) + +This script removes the Tizen workload from the installed .NET SDK, including the HKLM registry entry that `workload-install.ps1` writes on MSI-installed SDKs to make `dotnet workload list` show `tizen`. + +### When to use it + +On Windows, running `dotnet workload install/restore/update` for other workloads while the Tizen workload is installed can fail with an error like: + +``` +Version X.Y.Z of package samsung.tizen.sdk.msi.x64 is not found in NuGet feeds +``` + +and cause the entire transaction to roll back, including other workloads installed in the same session. The root cause is that the Tizen workload is currently distributed as raw NuGet packages via an install script rather than signed MSI NuGet packages, but the install script registers a Windows Installer entry so that `dotnet workload list` can see it. The SDK then treats Tizen as an MSI-installed workload and fails when it cannot find the MSI payload. + +As a workaround, run `workload-uninstall.ps1` before `dotnet workload` operations for other workloads, then reinstall the Tizen workload with `workload-install.ps1`: + +``` +# 1) Uninstall Tizen workload (run PowerShell as Administrator) +.\workload-uninstall.ps1 + +# 2) Run your dotnet workload operations +dotnet workload install maui +# or dotnet workload restore / update, etc. + +# 3) Reinstall the Tizen workload +.\workload-install.ps1 +``` + +### Usage + +``` +workload-uninstall.ps1 [-d ] [-t ] +``` + +Must be run with administrator privileges when the .NET SDK is installed under `%ProgramFiles%\dotnet` (the default for MSI-installed SDKs). 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" From fbdc3f5a1d6a5ecd6712b74ee8b427e340ed17bf Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Mon, 27 Apr 2026 10:46:46 +0900 Subject: [PATCH 04/11] update backup version --- workload/scripts/workload-install.ps1 | 6 +++--- workload/scripts/workload-install.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/workload/scripts/workload-install.ps1 b/workload/scripts/workload-install.ps1 index 54cf66636..a30a58b20 100644 --- a/workload/scripts/workload-install.ps1 +++ b/workload/scripts/workload-install.ps1 @@ -41,7 +41,7 @@ $LatestVersionMap = [ordered]@{ "$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-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"; @@ -55,13 +55,13 @@ $LatestVersionMap = [ordered]@{ "$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-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.300" = "10.0.121"; "$ManifestBaseName-10.0.100" = "10.0.123"; } diff --git a/workload/scripts/workload-install.sh b/workload/scripts/workload-install.sh index 37877ac10..7defaa60f 100755 --- a/workload/scripts/workload-install.sh +++ b/workload/scripts/workload-install.sh @@ -25,7 +25,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,7 +39,7 @@ 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" From 00fbc88d8b5b41a9506eaa263b758208be6b1a72 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Tue, 28 Apr 2026 10:11:28 +0900 Subject: [PATCH 05/11] Add tizen-doctor agent skill (.agents/skills/) Provides an agentskills.io-compatible diagnostic skill for AI coding assistants. Covers NETSDK1139, missing Tizen Workload, net*-tizen*.0 TFM resolution issues, sdb / certificate setup. Cross-platform collectors (sh / ps1) automatically inspect the WorkloadManifest.json this repo produces and emit a structured environment report. Originally developed at JoonghyunCho/tizen-skills as a marketplace plugin (v0.1.0). Migrating here for closer co-location with the Workload manifest itself, since references/tfm-workload-matrix.md tracks the manifest's Samsung.Tizen.Ref.API* pack list. The marketplace install path (claude plugin install tizen) is preserved via an automated mirror back to JoonghyunCho/tizen-skills. --- .agents/skills/tizen-doctor/SKILL.md | 137 ++++++++++++++ .../references/tfm-workload-matrix.md | 167 +++++++++++++++++ .../tizen-doctor/scripts/collect-env.ps1 | 172 ++++++++++++++++++ .../tizen-doctor/scripts/collect-env.sh | 160 ++++++++++++++++ README.md | 20 ++ 5 files changed, 656 insertions(+) create mode 100644 .agents/skills/tizen-doctor/SKILL.md create mode 100644 .agents/skills/tizen-doctor/references/tfm-workload-matrix.md create mode 100644 .agents/skills/tizen-doctor/scripts/collect-env.ps1 create mode 100755 .agents/skills/tizen-doctor/scripts/collect-env.sh 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/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 From bdd657f2580c9645d6d178b9a83a4d45db691dee Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Tue, 28 Apr 2026 11:41:35 +0900 Subject: [PATCH 06/11] Add Code Assist (#306) Co-authored-by: Jay Cho --- .gemini/config.yaml | 35 +++++++++++++++++++++++++++++++++++ .gemini/styleguide.md | 20 ++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 .gemini/config.yaml create mode 100644 .gemini/styleguide.md 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. From 1516909c92b53a3fa506cecacb505061a8be9324 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Mon, 11 May 2026 20:57:16 +0900 Subject: [PATCH 07/11] feat: install script SSOT (version-map.json + generator + CI + 9.0.300 drift fix + tizen7.0 cleanup) --- .github/workflows/validate-version-map.yml | 48 ++++ workload/scripts/Generate-InstallScripts.ps1 | 208 ++++++++++++++++++ workload/scripts/README.md | 48 ++-- workload/scripts/version-map.json | 144 ++++++++++++ workload/scripts/workload-install.ps1 | 48 ++-- workload/scripts/workload-install.sh | 4 +- .../targets/Samsung.Tizen.Sdk.NuGet.targets | 1 - .../Samsung.Tizen.Sdk.Versions.targets | 1 - 8 files changed, 449 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/validate-version-map.yml create mode 100644 workload/scripts/Generate-InstallScripts.ps1 create mode 100644 workload/scripts/version-map.json 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/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 42bf318a4..207f4c852 100644 --- a/workload/scripts/README.md +++ b/workload/scripts/README.md @@ -28,38 +28,32 @@ or curl -sSL https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | bash -s -- -v -d ``` -## workload-uninstall (Windows only) +## Editing the version map -This script removes the Tizen workload from the installed .NET SDK, including the HKLM registry entry that `workload-install.ps1` writes on MSI-installed SDKs to make `dotnet workload list` show `tizen`. +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). -### When to use it +To add or update an entry: -On Windows, running `dotnet workload install/restore/update` for other workloads while the Tizen workload is installed can fail with an error like: +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** ``` -Version X.Y.Z of package samsung.tizen.sdk.msi.x64 is not found in NuGet feeds +# 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`. -and cause the entire transaction to roll back, including other workloads installed in the same session. The root cause is that the Tizen workload is currently distributed as raw NuGet packages via an install script rather than signed MSI NuGet packages, but the install script registers a Windows Installer entry so that `dotnet workload list` can see it. The SDK then treats Tizen as an MSI-installed workload and fails when it cannot find the MSI payload. +### Why -As a workaround, run `workload-uninstall.ps1` before `dotnet workload` operations for other workloads, then reinstall the Tizen workload with `workload-install.ps1`: - -``` -# 1) Uninstall Tizen workload (run PowerShell as Administrator) -.\workload-uninstall.ps1 - -# 2) Run your dotnet workload operations -dotnet workload install maui -# or dotnet workload restore / update, etc. - -# 3) Reinstall the Tizen workload -.\workload-install.ps1 -``` - -### Usage - -``` -workload-uninstall.ps1 [-d ] [-t ] -``` - -Must be run with administrator privileges when the .NET SDK is installed under `%ProgramFiles%\dotnet` (the default for MSI-installed SDKs). +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/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 a30a58b20..e977f8b82 100644 --- a/workload/scripts/workload-install.ps1 +++ b/workload/scripts/workload-install.ps1 @@ -29,41 +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.119"; - "$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.120"; - "$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.121"; - "$ManifestBaseName-10.0.100" = "10.0.123"; + "$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() diff --git a/workload/scripts/workload-install.sh b/workload/scripts/workload-install.sh index 7defaa60f..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" @@ -45,9 +46,10 @@ LatestVersionMap=( "$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 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. - From 152d2a7858f84d2d22d213223e53d3a93ca28a9b Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Mon, 11 May 2026 21:13:34 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20next-workload-version.py=20?= =?UTF-8?q?=E2=80=94=20global=20sequential=20bump=20from=20NuGet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- workload/Makefile | 12 ++ workload/scripts/next-workload-version.py | 207 ++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 workload/scripts/next-workload-version.py diff --git a/workload/Makefile b/workload/Makefile index 202e48f3a..01e764264 100644 --- a/workload/Makefile +++ b/workload/Makefile @@ -115,3 +115,15 @@ 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 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()) From 02fb268e8008d85be4d492263e3b4d2f4cde274d Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Wed, 13 May 2026 21:01:15 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20next-workload-version.py=20?= =?UTF-8?q?=E2=80=94=20global=20sequential=20bump=20from=20NuGet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- workload/Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/workload/Makefile b/workload/Makefile index 01e764264..6f9ece478 100644 --- a/workload/Makefile +++ b/workload/Makefile @@ -127,3 +127,15 @@ next-workload-version: .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 From fc24deb54354cc9e4e96bf67d7b36911aeeb630e Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Wed, 13 May 2026 21:07:51 +0900 Subject: [PATCH 10/11] chore: add GitHub Packages NuGet source for TizenFX ref packs --- workload/NuGet.config | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/workload/NuGet.config b/workload/NuGet.config index 0700f15d7..a621573f8 100644 --- a/workload/NuGet.config +++ b/workload/NuGet.config @@ -8,6 +8,14 @@ + + + From 712c484114353bcfe6b1a046cfe94721fb95c106 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Wed, 13 May 2026 21:15:36 +0900 Subject: [PATCH 11/11] ci: integrate GH Packages auth + auto-bump TizenWorkloadVersion --- .github/workflows/build-workload.yml | 12 ++++++++++++ .github/workflows/release-workload.yml | 27 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/.github/workflows/build-workload.yml b/.github/workflows/build-workload.yml index c72c680cd..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 }} 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