From 29264fca9fbb40253d55cbb70a2df383c440d88e Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 17 Nov 2025 16:58:40 -0800 Subject: [PATCH] Fix relative path in AspireWithMaui.slnx --- eng/restore-toolset.ps1 | 172 ++++- eng/restore-toolset.sh | 113 ++- playground/AspireWithMaui/AspireWithMaui.slnx | 667 +++++++++--------- 3 files changed, 562 insertions(+), 390 deletions(-) diff --git a/eng/restore-toolset.ps1 b/eng/restore-toolset.ps1 index 8d493c0690f..bf64b047733 100644 --- a/eng/restore-toolset.ps1 +++ b/eng/restore-toolset.ps1 @@ -3,18 +3,138 @@ Set-StrictMode -Off +function Get-FileUri { + param([string]$path) + + $fullPath = [System.IO.Path]::GetFullPath($path) + $builder = New-Object System.UriBuilder + $builder.Scheme = 'file' + $builder.Host = '' + $builder.Path = $fullPath + return $builder.Uri +} + +function Get-RelativeSolutionPath { + param( + [string]$pathValue, + [string]$sourceDir, + [string]$targetDir + ) + + if ([string]::IsNullOrWhiteSpace($pathValue)) { + return $pathValue + } + + $normalized = $pathValue.Replace('/', [System.IO.Path]::DirectorySeparatorChar) + $absolute = if ([System.IO.Path]::IsPathRooted($normalized)) { + [System.IO.Path]::GetFullPath($normalized) + } + else { + [System.IO.Path]::GetFullPath((Join-Path $sourceDir $normalized)) + } + + $targetDirWithSep = if ($targetDir.EndsWith([System.IO.Path]::DirectorySeparatorChar)) { + $targetDir + } + else { + $targetDir + [System.IO.Path]::DirectorySeparatorChar + } + + $targetUri = Get-FileUri -path $targetDirWithSep + $absoluteUri = Get-FileUri -path $absolute + $relative = $targetUri.MakeRelativeUri($absoluteUri).ToString() + $relative = [System.Uri]::UnescapeDataString($relative) + return $relative.Replace('\', '/') +} + +function Update-PathAttributes { + param( + [xml]$xml, + [string]$sourceDir, + [string]$targetDir + ) + + $nodes = $xml.SelectNodes("//Project[@Path] | //File[@Path]") + foreach ($node in $nodes) { + $attribute = $node.Attributes["Path"] + if ($null -ne $attribute) { + $attribute.Value = Get-RelativeSolutionPath -pathValue $attribute.Value -sourceDir $sourceDir -targetDir $targetDir + } + } +} + +function Ensure-MauiTestsProject { + param([xml]$xml) + + $testsFolder = $xml.SelectSingleNode("//Folder[@Name='/tests/Hosting/']") + if ($null -eq $testsFolder) { + return + } + + $desiredPath = "tests/Aspire.Hosting.Maui.Tests/Aspire.Hosting.Maui.Tests.csproj" + if ($null -ne $testsFolder.SelectSingleNode("Project[@Path='$desiredPath']")) { + return + } + + $projectNode = $xml.CreateElement("Project") + $pathAttribute = $xml.CreateAttribute("Path") + $pathAttribute.Value = $desiredPath + $projectNode.Attributes.Append($pathAttribute) | Out-Null + + $inserted = $false + $projectNodes = $testsFolder.SelectNodes("Project") + foreach ($existing in $projectNodes) { + if ([string]::Compare($desiredPath, $existing.Attributes["Path"].Value, $true) -lt 0) { + $testsFolder.InsertBefore($projectNode, $existing) | Out-Null + $inserted = $true + break + } + } + + if (-not $inserted) { + $testsFolder.AppendChild($projectNode) | Out-Null + } +} + +function Add-MauiFolder { + param( + [xml]$xml, + [System.Xml.XmlElement]$solutionElement + ) + + if ($null -ne $solutionElement.SelectSingleNode("Folder[@Name='/playground/AspireWithMaui/']")) { + return + } + + $mauiFolderXml = @" + + + + + + + +"@ + + $tempDoc = [xml]"$mauiFolderXml" + $mauiNode = $tempDoc.DocumentElement.FirstChild + $importedNode = $xml.ImportNode($mauiNode, $true) + $solutionElement.AppendChild($importedNode) | Out-Null +} + if ($restoreMaui) { $isWindowsOrMac = ($IsWindows -or $IsMacOS -or (-not (Get-Variable -Name IsWindows -ErrorAction SilentlyContinue))) - + if ($isWindowsOrMac) { Write-Host "Installing MAUI workload..." - + $dotnetCmd = if ($IsWindows -or (-not (Get-Variable -Name IsWindows -ErrorAction SilentlyContinue))) { Join-Path $RepoRoot "dotnet.cmd" - } else { + } + else { Join-Path $RepoRoot "dotnet.sh" } - + & $dotnetCmd workload install maui 2>&1 | Out-Host if ($LASTEXITCODE -ne 0) { Write-Host "" @@ -32,45 +152,39 @@ if ($restoreMaui) { else { Write-Host "Skipping MAUI workload installation on Linux (not supported)." } - + # Generate AspireWithMaui.slnx from the base Aspire.slnx Write-Host "Generating AspireWithMaui.slnx..." $sourceSlnx = Join-Path $RepoRoot "Aspire.slnx" $outputPath = Join-Path $RepoRoot "playground/AspireWithMaui" $outputSlnx = Join-Path $outputPath "AspireWithMaui.slnx" - + if (-not (Test-Path $sourceSlnx)) { Write-Warning "Source solution file not found: $sourceSlnx" - } else { - # Read and parse the source XML + } + else { + if (-not (Test-Path $outputPath)) { + New-Item -ItemType Directory -Force -Path $outputPath | Out-Null + } + [xml]$xml = Get-Content $sourceSlnx $solutionElement = $xml.DocumentElement - - # Create the Maui folder element - $mauiFolderXml = @" - - - - - - - -"@ - - # Parse the Maui folder element - $tempDoc = [xml]"$mauiFolderXml" - $mauiNode = $tempDoc.DocumentElement.FirstChild - $importedNode = $xml.ImportNode($mauiNode, $true) - $solutionElement.AppendChild($importedNode) | Out-Null - - # Write the XML with proper formatting + + Add-MauiFolder -xml $xml -solutionElement $solutionElement + Ensure-MauiTestsProject -xml $xml + + $sourceDir = Split-Path $sourceSlnx -Parent + $targetDir = Split-Path $outputSlnx -Parent + Update-PathAttributes -xml $xml -sourceDir $sourceDir -targetDir $targetDir + $settings = New-Object System.Xml.XmlWriterSettings $settings.Indent = $true $settings.IndentChars = " " $settings.NewLineChars = [System.Environment]::NewLine $settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace $settings.OmitXmlDeclaration = $true - + $settings.Encoding = New-Object System.Text.UTF8Encoding($true) + $writer = [System.Xml.XmlWriter]::Create($outputSlnx, $settings) try { $xml.WriteTo($writer) @@ -78,7 +192,7 @@ if ($restoreMaui) { finally { $writer.Dispose() } - + Write-Host "Generated AspireWithMaui.slnx at: $outputSlnx" } } diff --git a/eng/restore-toolset.sh b/eng/restore-toolset.sh index 9b856b2334c..8a7bb526c06 100644 --- a/eng/restore-toolset.sh +++ b/eng/restore-toolset.sh @@ -8,9 +8,9 @@ if [[ "$restore_maui" == true ]]; then if [[ "$(uname -s)" == "Darwin" ]]; then echo "" echo "Installing MAUI workload..." - + dotnet_sh="$repo_root/dotnet.sh" - + if "$dotnet_sh" workload install maui; then echo "MAUI workload installed successfully." echo "" @@ -25,41 +25,98 @@ if [[ "$restore_maui" == true ]]; then else echo "Skipping MAUI workload installation on Linux (not supported)." fi - + + if ! command -v python3 >/dev/null 2>&1; then + echo "python3 is required to generate AspireWithMaui.slnx" + exit 1 + fi + # Generate AspireWithMaui.slnx from the base Aspire.slnx echo "" echo "Generating AspireWithMaui.slnx..." - + source_slnx="$repo_root/Aspire.slnx" output_path="$repo_root/playground/AspireWithMaui" output_slnx="$output_path/AspireWithMaui.slnx" - + if [ ! -f "$source_slnx" ]; then echo "WARNING: Source solution file not found: $source_slnx" else - # Create a temporary file - temp_file=$(mktemp) - trap "rm -f $temp_file" EXIT - - # Copy the source file - cp "$source_slnx" "$temp_file" - - # Insert the Maui folder before the closing tag - # Using a here-doc approach for BSD sed compatibility - sed -i.bak '/<\/Solution>/i\ - \ - \ - \ - \ - \ - \ - -' "$temp_file" - rm -f "$temp_file.bak" - # Write UTF-8 BOM and append temp file to output location - printf '\xEF\xBB\xBF' > "$output_slnx" - cat "$temp_file" >> "$output_slnx" - + mkdir -p "$output_path" + + python3 <<'PY' "$source_slnx" "$output_slnx" +import codecs +import os +import re +import sys + +source = os.path.abspath(sys.argv[1]) +target = os.path.abspath(sys.argv[2]) +source_dir = os.path.dirname(source) +target_dir = os.path.dirname(target) + +with open(source, 'rb') as handle: + text = handle.read().decode('utf-8-sig') + +maui_folder_marker = 'playground/AspireWithMaui/AspireWithMaui.AppHost/AspireWithMaui.AppHost.csproj' +if maui_folder_marker not in text: + folder_block = ( + '\r\n \r\n' + ' \r\n' + ' \r\n' + ' \r\n' + ' \r\n' + ' \r\n' + ' \r\n' + ) + text = text.replace('\r\n', f'{folder_block}', 1) + +tests_folder_pattern = re.compile(r'(\r?\n)(.*?)( )', re.DOTALL) +match = tests_folder_pattern.search(text) +desired_line = ' \r\n' +desired_path = 'tests/Aspire.Hosting.Maui.Tests/Aspire.Hosting.Maui.Tests.csproj' +if match: + body = match.group(2) + if desired_path not in body: + lines = body.splitlines(keepends=True) + + def extract_path(line: str) -> str: + hit = re.search(r'Path="([^"]+)"', line) + return hit.group(1) if hit else '' + + inserted = False + for index, line in enumerate(lines): + existing_path = extract_path(line) + if existing_path and desired_path.lower() < existing_path.lower(): + lines.insert(index, desired_line) + inserted = True + break + if not inserted: + lines.append(desired_line) + + new_body = ''.join(lines) + text = text[:match.start(2)] + new_body + text[match.end(2):] + +def resolve_relative(value: str) -> str: + normalized = value.replace('\\', '/').replace('/', os.sep) + if os.path.isabs(normalized): + absolute = os.path.normpath(normalized) + else: + absolute = os.path.normpath(os.path.join(source_dir, normalized)) + relative = os.path.relpath(absolute, target_dir) + return relative.replace(os.sep, '/') + +def substitute(match: re.Match) -> str: + original = match.group(1) + return f'Path="{resolve_relative(original)}"' + +text = re.sub(r'Path="([^"]+)"', substitute, text) + +with open(target, 'wb') as handle: + handle.write(codecs.BOM_UTF8) + handle.write(text.encode('utf-8')) +PY + echo "Generated AspireWithMaui.slnx at: $output_slnx" fi fi diff --git a/playground/AspireWithMaui/AspireWithMaui.slnx b/playground/AspireWithMaui/AspireWithMaui.slnx index 34b188bdbf1..da2aad210a6 100644 --- a/playground/AspireWithMaui/AspireWithMaui.slnx +++ b/playground/AspireWithMaui/AspireWithMaui.slnx @@ -5,471 +5,472 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - + + + - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - - - + + + + - + - - - + + + - + - - - + + + - - + + - - + + - + - - - + + + - - - + + + - - + + - - + + - - + + - - - - + + + + - - - + + + - - + + - - + + - - - - - + + + + + - - + + - - + + - - + + - - + + - - - - - + + + + + - + - - + + - - + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - - + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - + + - - - + + + - - - - - - - - + + + + + + + + - + - - - - - - - + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - + + + + + + - - - + + + - + - + - - - - - + + + + + - + \ No newline at end of file